Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the default server and a TLS Secret for the default server #170

Merged
merged 1 commit into from
Aug 21, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ nginx-plus-ingress
# Visual Studio Code settings
.vscode

# Default certificate and key
default.pem

3 changes: 3 additions & 0 deletions nginx-controller/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ COPY nginx-ingress nginx/templates/nginx.ingress.tmpl nginx/templates/nginx.tmpl

RUN rm /etc/nginx/conf.d/*

RUN mkdir -p /etc/nginx/ssl
ADD default.pem /etc/nginx/ssl

ENTRYPOINT ["/nginx-ingress"]
3 changes: 3 additions & 0 deletions nginx-controller/DockerfileForAlpine
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ COPY nginx-ingress nginx/templates/nginx.ingress.tmpl nginx/templates/nginx.tmpl

RUN rm /etc/nginx/conf.d/*

RUN mkdir -p /etc/nginx/ssl
ADD default.pem /etc/nginx/ssl

ENTRYPOINT ["/nginx-ingress"]
3 changes: 3 additions & 0 deletions nginx-controller/DockerfileForPlus
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,7 @@ RUN mkdir -p /var/lib/nginx/state && chown -R nginx:nginx /var/lib/nginx
COPY nginx-ingress nginx/templates/nginx-plus.ingress.tmpl nginx/templates/nginx-plus.tmpl /
RUN rm /etc/nginx/conf.d/*

RUN mkdir -p /etc/nginx/ssl
ADD default.pem /etc/nginx/ssl

ENTRYPOINT ["/nginx-ingress"]
5 changes: 4 additions & 1 deletion nginx-controller/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ else
go test ./...
endif

container: test nginx-ingress
certificate-and-key:
./generate_default_cert_and_key.sh

container: test nginx-ingress certificate-and-key
docker build -f $(DOCKERFILE) -t $(PREFIX):$(TAG) .

push: container
Expand Down
49 changes: 40 additions & 9 deletions nginx-controller/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,19 @@ type LoadBalancerController struct {
watchNginxConfigMaps bool
nginxPlus bool
recorder record.EventRecorder
defaultServerSecret string
}

var keyFunc = cache.DeletionHandlingMetaNamespaceKeyFunc

// NewLoadBalancerController creates a controller
func NewLoadBalancerController(kubeClient kubernetes.Interface, resyncPeriod time.Duration, namespace string, cnf *nginx.Configurator, nginxConfigMaps string, nginxPlus bool) (*LoadBalancerController, error) {
func NewLoadBalancerController(kubeClient kubernetes.Interface, resyncPeriod time.Duration, namespace string, cnf *nginx.Configurator, nginxConfigMaps string, defaultServerSecret string, nginxPlus bool) (*LoadBalancerController, error) {
lbc := LoadBalancerController{
client: kubeClient,
stopCh: make(chan struct{}),
cnf: cnf,
nginxPlus: nginxPlus,
client: kubeClient,
stopCh: make(chan struct{}),
cnf: cnf,
defaultServerSecret: defaultServerSecret,
nginxPlus: nginxPlus,
}

eventBroadcaster := record.NewBroadcaster()
Expand Down Expand Up @@ -203,6 +205,14 @@ func NewLoadBalancerController(kubeClient kubernetes.Interface, resyncPeriod tim
&api_v1.Endpoints{}, resyncPeriod, endpHandlers)

secrHandlers := cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
secr := obj.(*api_v1.Secret)
nsname := secr.Namespace + "/" + secr.Name
if nsname == lbc.defaultServerSecret {
glog.V(3).Infof("Adding default server Secret: %v", secr.Name)
lbc.syncQueue.enqueue(obj)
}
},
DeleteFunc: func(obj interface{}) {
remSecr, isSecr := obj.(*api_v1.Secret)
if !isSecr {
Expand Down Expand Up @@ -244,7 +254,7 @@ func NewLoadBalancerController(kubeClient kubernetes.Interface, resyncPeriod tim
&api_v1.Secret{}, resyncPeriod, secrHandlers)

if nginxConfigMaps != "" {
nginxConfigMapsNS, nginxConfigMapsName, err := parseNamespaceName(nginxConfigMaps)
nginxConfigMapsNS, nginxConfigMapsName, err := ParseNamespaceName(nginxConfigMaps)
if err != nil {
glog.Warning(err)
} else {
Expand Down Expand Up @@ -644,7 +654,7 @@ func (lbc *LoadBalancerController) syncSecret(task Task) {
return
}

_, name, err := parseNamespaceName(key)
_, name, err := ParseNamespaceName(key)
if err != nil {
glog.Warningf("Secret key %v is invalid: %v", key, err)
return
Expand All @@ -669,11 +679,32 @@ func (lbc *LoadBalancerController) syncSecret(task Task) {
lbc.syncQueue.enqueue(&ing)
lbc.recorder.Eventf(&ing, api_v1.EventTypeWarning, "Rejected", "%v/%v was rejected due to deleted Secret %v: %v", ing.Namespace, ing.Name, key)
}

if key == lbc.defaultServerSecret {
glog.Warningf("The default server Secret %v was removed. Retaining the Secret.")
}
} else {
glog.V(2).Infof("Updating Secret: %v\n", key)

secret := obj.(*api_v1.Secret)

if key == lbc.defaultServerSecret {
err := ValidateTLSSecret(secret)
if err != nil {
glog.Errorf("Couldn't validate the default server Secret %v: %v", key, err)
lbc.recorder.Eventf(secret, api_v1.EventTypeWarning, "Rejected", "the default server Secret %v was rejected, using the previous version: %v", key, err)
} else {
err := lbc.cnf.AddOrUpdateDefaultServerTLSSecret(secret)
if err != nil {
glog.Errorf("Error when updating the default server Secret %v: %v", key, err)
lbc.recorder.Eventf(secret, api_v1.EventTypeWarning, "UpdatedWithError", "the default server Secret %v was updated, but not applied: %v", key, err)

} else {
lbc.recorder.Eventf(secret, api_v1.EventTypeNormal, "Updated", "the default server Secret %v was updated", key)
}
}
}

if len(ings) > 0 {
err := ValidateTLSSecret(secret)
if err != nil {
Expand Down Expand Up @@ -919,10 +950,10 @@ func (lbc *LoadBalancerController) getServiceForIngressBackend(backend *extensio
return nil, fmt.Errorf("service %s doesn't exists", svcKey)
}

func parseNamespaceName(value string) (ns string, name string, err error) {
func ParseNamespaceName(value string) (ns string, name string, err error) {
res := strings.Split(value, "/")
if len(res) != 2 {
return "", "", fmt.Errorf("%v must follow the format <namespace>/<name>", value)
return "", "", fmt.Errorf("%q must follow the format <namespace>/<name>", value)
}
return res[0], res[1], nil
}
Expand Down
5 changes: 5 additions & 0 deletions nginx-controller/generate_default_cert_and_key.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout default.key -out default.crt -subj "/CN=NGINXIngressController"
cat default.key default.crt > default.pem
rm default.key default.crt
27 changes: 26 additions & 1 deletion nginx-controller/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/nginxinc/kubernetes-ingress/nginx-controller/controller"
"github.com/nginxinc/kubernetes-ingress/nginx-controller/nginx"
"github.com/nginxinc/kubernetes-ingress/nginx-controller/nginx/plus"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/pkg/api"
"k8s.io/client-go/rest"
Expand Down Expand Up @@ -43,6 +44,11 @@ var (

nginxPlus = flag.Bool("nginx-plus", false,
`Enables support for NGINX Plus.`)

defaultServerSecret = flag.String("default-server-tls-secret", "",
`Specifies a secret with a TLS certificate and key for SSL termination of
the default server. The value must follow the following format: <namespace>/<name>.
If not specified, the key and the cert from /etc/nginx/default.pem is used.`)
)

func main() {
Expand Down Expand Up @@ -84,6 +90,25 @@ func main() {
nginxIngressTemplatePath = "nginx-plus.ingress.tmpl"
}
ngxc, _ := nginx.NewNginxController("/etc/nginx/", local, *healthStatus, nginxConfTemplatePath, nginxIngressTemplatePath)

if *defaultServerSecret != "" {
ns, name, err := controller.ParseNamespaceName(*defaultServerSecret)
if err != nil {
glog.Fatalf("Error parsing the default-server-tls-secret argument: %v", err)
}
secret, err := kubeClient.CoreV1().Secrets(ns).Get(name, meta_v1.GetOptions{})
if err != nil {
glog.Fatalf("Error when getting %v: %v", *defaultServerSecret, err)
}
err = controller.ValidateTLSSecret(secret)
if err != nil {
glog.Fatalf("%v is invalid: %v", *defaultServerSecret, err)
}

bytes := nginx.GenerateCertAndKeyFileContent(secret)
ngxc.AddOrUpdatePemFile(nginx.DefaultServerPemName, bytes)
}

nginxDone := make(chan error, 1)
ngxc.Start(nginxDone)

Expand All @@ -98,7 +123,7 @@ func main() {
}
cnf := nginx.NewConfigurator(ngxc, nginxConfig, nginxAPI)

lbc, _ := controller.NewLoadBalancerController(kubeClient, 30*time.Second, *watchNamespace, cnf, *nginxConfigMaps, *nginxPlus)
lbc, _ := controller.NewLoadBalancerController(kubeClient, 30*time.Second, *watchNamespace, cnf, *nginxConfigMaps, *defaultServerSecret, *nginxPlus)
go handleTermination(lbc, ngxc, nginxDone)
lbc.Run()

Expand Down
19 changes: 17 additions & 2 deletions nginx-controller/nginx/configurator.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
)

const emptyHost = ""
const DefaultServerPemName = "default"

// Configurator transforms an Ingress resource into NGINX Configuration
type Configurator struct {
Expand Down Expand Up @@ -445,11 +446,22 @@ func (cnf *Configurator) AddOrUpdateTLSSecret(secret *api_v1.Secret, reload bool

func (cnf *Configurator) addOrUpdateTLSSecret(secret *api_v1.Secret) string {
name := objectMetaToFileName(&secret.ObjectMeta)
data := generateCertAndKeyFileContent(secret)
data := GenerateCertAndKeyFileContent(secret)
return cnf.nginx.AddOrUpdatePemFile(name, data)
}

func generateCertAndKeyFileContent(secret *api_v1.Secret) []byte {
func (cnf *Configurator) AddOrUpdateDefaultServerTLSSecret(secret *api_v1.Secret) error {
data := GenerateCertAndKeyFileContent(secret)
cnf.nginx.AddOrUpdatePemFile(DefaultServerPemName, data)

if err := cnf.nginx.Reload(); err != nil {
return fmt.Errorf("Error when reloading NGINX when updating the default server Secret: %v", err)
}
return nil
}

// GenerateCertAndKeyFileContent generates a pem file content from the secret
func GenerateCertAndKeyFileContent(secret *api_v1.Secret) []byte {
var res bytes.Buffer

res.Write(secret.Data[api_v1.TLSCertKey])
Expand Down Expand Up @@ -539,6 +551,9 @@ func (cnf *Configurator) UpdateConfig(config *Config, ingExes []*IngressEx) erro
SSLCiphers: config.MainServerSSLCiphers,
SSLDHParam: config.MainServerSSLDHParam,
SSLPreferServerCiphers: config.MainServerSSLPreferServerCiphers,
HTTP2: config.HTTP2,
ServerTokens: config.ServerTokens,
ProxyProtocol: config.ProxyProtocol,
}

cnf.nginx.UpdateMainConfigFile(mainCfg)
Expand Down
10 changes: 6 additions & 4 deletions nginx-controller/nginx/nginx.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ type NginxMainConfig struct {
SSLPreferServerCiphers bool
SSLCiphers string
SSLDHParam string
HTTP2 bool
ServerTokens string
ProxyProtocol bool
}

// NewUpstreamWithDefaultServer creates an upstream with the default server.
Expand All @@ -121,11 +124,10 @@ func NewNginxController(nginxConfPath string, local bool, healthStatus bool, ngi
nginxIngressTempatePath: nginxIngressTemplatePath,
}

if !local {
createDir(ngxc.nginxCertsPath)
cfg := &NginxMainConfig{
ServerNamesHashMaxSize: NewDefaultConfig().MainServerNamesHashMaxSize,
ServerTokens: NewDefaultConfig().ServerTokens,
}

cfg := &NginxMainConfig{ServerNamesHashMaxSize: NewDefaultConfig().MainServerNamesHashMaxSize}
ngxc.UpdateMainConfigFile(cfg)

return &ngxc, nil
Expand Down
20 changes: 16 additions & 4 deletions nginx-controller/nginx/templates/nginx-plus.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,30 @@ http {
{{if .SSLPreferServerCiphers}}ssl_prefer_server_ciphers on;{{end}}
{{if .SSLDHParam}}ssl_dhparam {{.SSLDHParam}};{{end}}

{{if .HealthStatus}}

server {
listen 80 default_server;
listen 80 default_server{{if .ProxyProtocol}} proxy_protocol{{end}};
listen 443 ssl default_server{{if .HTTP2}} http2{{end}}{{if .ProxyProtocol}} proxy_protocol{{end}};

ssl_certificate /etc/nginx/ssl/default.pem;
ssl_certificate_key /etc/nginx/ssl/default.pem;

server_name _;
server_tokens "{{.ServerTokens}}";
access_log off;

{{if .HealthStatus}}
location /nginx-health {
access_log off;
default_type text/plain;
return 200 "healthy\n";
}
{{end}}

location / {
return 404;
}
}
{{end}}


# status and on-the-fly reconfiguration APIs
server {
Expand Down
18 changes: 14 additions & 4 deletions nginx-controller/nginx/templates/nginx.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,28 @@ http {
{{if .SSLPreferServerCiphers}}ssl_prefer_server_ciphers on;{{end}}
{{if .SSLDHParam}}ssl_dhparam {{.SSLDHParam}};{{end}}

{{if .HealthStatus}}
server {
listen 80 default_server;
listen 80 default_server{{if .ProxyProtocol}} proxy_protocol{{end}};
listen 443 ssl default_server{{if .HTTP2}} http2{{end}}{{if .ProxyProtocol}} proxy_protocol{{end}};

ssl_certificate /etc/nginx/ssl/default.pem;
ssl_certificate_key /etc/nginx/ssl/default.pem;

server_name _;
server_tokens "{{.ServerTokens}}";
access_log off;

{{if .HealthStatus}}
location /nginx-health {
access_log off;
default_type text/plain;
return 200 "healthy\n";
}
{{end}}

location / {
return 404;
}
}
{{end}}

include /etc/nginx/conf.d/*.conf;
}