diff --git a/controllers/nginx/README.md b/controllers/nginx/README.md index f2729a52b..d3a4ea977 100644 --- a/controllers/nginx/README.md +++ b/controllers/nginx/README.md @@ -140,6 +140,74 @@ NGINX provides the option [default_server](http://nginx.org/en/docs/http/server_ In case of HTTPS NGINX requires a certificate. For this reason the Ingress controller provides the flag `--default-ssl-certificate`. The secret behind this flag contains the default certificate to be used in the mentioned case. If this flag is not provided NGINX will reject the request with the HTTP code 444. +Running without the flag `--default-ssl-certificate`: + +``` +$ curl -v https://10.2.78.7:443 +* Rebuilt URL to: https://10.2.78.7:443/ +* Trying 10.2.78.7... +* Connected to 10.2.78.7 (10.2.78.7) port 443 (#0) +* ALPN, offering http/1.1 +* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH +* successfully set certificate verify locations: +* CAfile: /etc/ssl/certs/ca-certificates.crt + CApath: /etc/ssl/certs +* TLSv1.2 (OUT), TLS header, Certificate Status (22): +* TLSv1.2 (OUT), TLS handshake, Client hello (1): +* Unknown SSL protocol error in connection to 10.2.78.7:443 +* Closing connection 0 +curl: (35) Unknown SSL protocol error in connection to 10.2.78.7:443 +``` + +Specifyng `--default-ssl-certificate=default/foo-tls`: + +``` +core@localhost ~ $ curl -v https://10.2.78.7:443 -k +* Rebuilt URL to: https://10.2.78.7:443/ +* Trying 10.2.78.7... +* Connected to 10.2.78.7 (10.2.78.7) port 443 (#0) +* ALPN, offering http/1.1 +* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH +* successfully set certificate verify locations: +* CAfile: /etc/ssl/certs/ca-certificates.crt + CApath: /etc/ssl/certs +* TLSv1.2 (OUT), TLS header, Certificate Status (22): +* TLSv1.2 (OUT), TLS handshake, Client hello (1): +* TLSv1.2 (IN), TLS handshake, Server hello (2): +* TLSv1.2 (IN), TLS handshake, Certificate (11): +* TLSv1.2 (IN), TLS handshake, Server key exchange (12): +* TLSv1.2 (IN), TLS handshake, Server finished (14): +* TLSv1.2 (OUT), TLS handshake, Client key exchange (16): +* TLSv1.2 (OUT), TLS change cipher, Client hello (1): +* TLSv1.2 (OUT), TLS handshake, Finished (20): +* TLSv1.2 (IN), TLS change cipher, Client hello (1): +* TLSv1.2 (IN), TLS handshake, Finished (20): +* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256 +* ALPN, server accepted to use http/1.1 +* Server certificate: +* subject: CN=foo.bar.com +* start date: Apr 13 00:50:56 2016 GMT +* expire date: Apr 13 00:50:56 2017 GMT +* issuer: CN=foo.bar.com +* SSL certificate verify result: self signed certificate (18), continuing anyway. +> GET / HTTP/1.1 +> Host: 10.2.78.7 +> User-Agent: curl/7.47.1 +> Accept: */* +> +< HTTP/1.1 404 Not Found +< Server: nginx/1.11.1 +< Date: Mon, 18 Jul 2016 21:02:59 GMT +< Content-Type: text/html +< Transfer-Encoding: chunked +< Connection: keep-alive +< Strict-Transport-Security: max-age=15724800; includeSubDomains; preload +< +The page you're looking for could not be found. + +* Connection #0 to host 10.2.78.7 left intact +``` + ### Server-side HTTPS enforcement diff --git a/controllers/nginx/controller.go b/controllers/nginx/controller.go index bfe7e06a0..5b27d6670 100644 --- a/controllers/nginx/controller.go +++ b/controllers/nginx/controller.go @@ -88,23 +88,24 @@ func (npm namedPortMapping) getPortMappings() map[string]string { // loadBalancerController watches the kubernetes api and adds/removes services // from the loadbalancer type loadBalancerController struct { - client *client.Client - ingController *framework.Controller - endpController *framework.Controller - svcController *framework.Controller - secrController *framework.Controller - mapController *framework.Controller - ingLister StoreToIngressLister - svcLister cache.StoreToServiceLister - endpLister cache.StoreToEndpointsLister - secrLister StoreToSecretsLister - mapLister StoreToConfigmapLister - nginx *nginx.Manager - podInfo *podInfo - defaultSvc string - nxgConfigMap string - tcpConfigMap string - udpConfigMap string + client *client.Client + ingController *framework.Controller + endpController *framework.Controller + svcController *framework.Controller + secrController *framework.Controller + mapController *framework.Controller + ingLister StoreToIngressLister + svcLister cache.StoreToServiceLister + endpLister cache.StoreToEndpointsLister + secrLister StoreToSecretsLister + mapLister StoreToConfigmapLister + nginx *nginx.Manager + podInfo *podInfo + defaultSvc string + nxgConfigMap string + tcpConfigMap string + udpConfigMap string + defSSLCertificate string recorder record.EventRecorder @@ -123,22 +124,24 @@ type loadBalancerController struct { } // newLoadBalancerController creates a controller for nginx loadbalancer -func newLoadBalancerController(kubeClient *client.Client, resyncPeriod time.Duration, defaultSvc, - namespace, nxgConfigMapName, tcpConfigMapName, udpConfigMapName string, runtimeInfo *podInfo) (*loadBalancerController, error) { +func newLoadBalancerController(kubeClient *client.Client, resyncPeriod time.Duration, + defaultSvc, namespace, nxgConfigMapName, tcpConfigMapName, udpConfigMapName, + defSSLCertificate string, runtimeInfo *podInfo) (*loadBalancerController, error) { eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartLogging(glog.Infof) eventBroadcaster.StartRecordingToSink(kubeClient.Events(namespace)) lbc := loadBalancerController{ - client: kubeClient, - stopCh: make(chan struct{}), - podInfo: runtimeInfo, - nginx: nginx.NewManager(kubeClient), - nxgConfigMap: nxgConfigMapName, - tcpConfigMap: tcpConfigMapName, - udpConfigMap: udpConfigMapName, - defaultSvc: defaultSvc, + client: kubeClient, + stopCh: make(chan struct{}), + podInfo: runtimeInfo, + nginx: nginx.NewManager(kubeClient), + nxgConfigMap: nxgConfigMapName, + tcpConfigMap: tcpConfigMapName, + udpConfigMap: udpConfigMapName, + defSSLCertificate: defSSLCertificate, + defaultSvc: defaultSvc, recorder: eventBroadcaster.NewRecorder(api.EventSource{ Component: "nginx-ingress-controller", }), @@ -855,6 +858,14 @@ func (lbc *loadBalancerController) createServers(data []interface{}) map[string] servers := make(map[string]*nginx.Server) pems := lbc.getPemsFromIngress(data) + if lbc.defSSLCertificate != "" { + ngxCert, err := lbc.getPemCertificate(lbc.defSSLCertificate) + if err == nil { + pems["_"] = ngxCert + } else { + glog.Warningf("%v", err) + } + } for _, ingIf := range data { ing := ingIf.(*extensions.Ingress) @@ -896,41 +907,10 @@ func (lbc *loadBalancerController) getPemsFromIngress(data []interface{}) map[st for _, tls := range ing.Spec.TLS { secretName := tls.SecretName secretKey := fmt.Sprintf("%s/%s", ing.Namespace, secretName) - secretInterface, exists, err := lbc.secrLister.Store.GetByKey(secretKey) + + ngxCert, err := lbc.getPemCertificate(secretKey) if err != nil { - glog.Warningf("Error retriveing secret %v for ing %v: %v", secretName, ing.Name, err) - continue - } - if !exists { - glog.Warningf("Secret %v is not existing", secretKey) - continue - } - secret := secretInterface.(*api.Secret) - cert, ok := secret.Data[api.TLSCertKey] - if !ok { - glog.Warningf("Secret %v has no private key", secretName) - continue - } - key, ok := secret.Data[api.TLSPrivateKeyKey] - if !ok { - glog.Warningf("Secret %v has no cert", secretName) - continue - } - - ngxCert, err := lbc.nginx.AddOrUpdateCertAndKey(fmt.Sprintf("%v-%v", ing.Namespace, secretName), string(cert), string(key)) - if err != nil { - glog.Errorf("No valid SSL certificate found in secret %v: %v", secretName, err) - continue - } - - if len(tls.Hosts) == 0 { - if _, ok := pems["_"]; ok { - glog.Warningf("It is not possible to use %v secret for default SSL certificate because there is one already defined", secretName) - continue - } - - pems["_"] = ngxCert - glog.Infof("Using the secret %v as source for the default SSL certificate", secretName) + glog.Warningf("%v", err) continue } @@ -947,6 +927,29 @@ func (lbc *loadBalancerController) getPemsFromIngress(data []interface{}) map[st return pems } +func (lbc *loadBalancerController) getPemCertificate(secretName string) (nginx.SSLCert, error) { + secretInterface, exists, err := lbc.secrLister.Store.GetByKey(secretName) + if err != nil { + return nginx.SSLCert{}, fmt.Errorf("Error retriveing secret %v: %v", secretName, err) + } + if !exists { + return nginx.SSLCert{}, fmt.Errorf("Secret %v does not exists", secretName) + } + + secret := secretInterface.(*api.Secret) + cert, ok := secret.Data[api.TLSCertKey] + if !ok { + return nginx.SSLCert{}, fmt.Errorf("Secret %v has no private key", secretName) + } + key, ok := secret.Data[api.TLSPrivateKeyKey] + if !ok { + return nginx.SSLCert{}, fmt.Errorf("Secret %v has no cert", secretName) + } + + nsSecName := strings.Replace(secretName, "/", "-", -1) + return lbc.nginx.AddOrUpdateCertAndKey(nsSecName, string(cert), string(key)) +} + // check if secret is referenced in this controller's config func (lbc *loadBalancerController) secrReferenced(namespace string, name string) bool { for _, ingIf := range lbc.ingLister.Store.List() { diff --git a/controllers/nginx/main.go b/controllers/nginx/main.go index a30d0117a..47aad386f 100644 --- a/controllers/nginx/main.go +++ b/controllers/nginx/main.go @@ -140,7 +140,9 @@ func main() { } } - lbc, err := newLoadBalancerController(kubeClient, *resyncPeriod, *defaultSvc, *watchNamespace, *nxgConfigMap, *tcpConfigMapName, *udpConfigMapName, runtimePodInfo) + lbc, err := newLoadBalancerController(kubeClient, *resyncPeriod, + *defaultSvc, *watchNamespace, *nxgConfigMap, *tcpConfigMapName, + *udpConfigMapName, *defSSLCertificate, runtimePodInfo) if err != nil { glog.Fatalf("%v", err) } diff --git a/controllers/nginx/nginx.tmpl b/controllers/nginx/nginx.tmpl index 1f178a636..f5b94a7d6 100644 --- a/controllers/nginx/nginx.tmpl +++ b/controllers/nginx/nginx.tmpl @@ -180,6 +180,16 @@ http { {{ end }} {{ range $server := .servers }} + {{/* Check for default SSL backend */}} + {{ if and (eq $server.Name "_") (not $server.SSL) -}} + server { + server_name {{ $server.Name }}; + listen 443; + # return protocol error. + return 444; + } + {{ end }} + server { server_name {{ $server.Name }}; listen 80{{ if $cfg.useProxyProtocol }} proxy_protocol{{ end }};