From 39643bc4f6dd6d55ffe752d2899f44d0e6056d6e Mon Sep 17 00:00:00 2001 From: Ricardo Pchevuzinske Katz Date: Mon, 6 Feb 2017 16:16:36 -0200 Subject: [PATCH] Adds correct support for TLS Muthual autentication --- .../rootfs/etc/nginx/template/nginx.tmpl | 5 +- core/pkg/ingress/controller/backend_ssl.go | 25 ++++++--- core/pkg/net/ssl/ssl.go | 52 +++++++++++++++++ examples/tls-authentication/nginx/README.md | 56 +++++++++++++++++++ .../nginx/nginx-tls-auth.yaml | 23 ++++++++ 5 files changed, 151 insertions(+), 10 deletions(-) create mode 100644 examples/tls-authentication/nginx/README.md create mode 100644 examples/tls-authentication/nginx/nginx-tls-auth.yaml diff --git a/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl b/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl index c4f4c497f..5b3aefa0d 100644 --- a/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl +++ b/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl @@ -221,10 +221,13 @@ http { {{ $path := buildLocation $location }} {{ $authPath := buildAuthLocation $location }} - {{ if not (empty $location.CertificateAuth.CertFileName) }} + {{ if not (empty $location.CertificateAuth.CAFileName) }} # PEM sha: {{ $location.CertificateAuth.PemSHA }} ssl_client_certificate {{ $location.CertificateAuth.CAFileName }}; ssl_verify_client on; + ssl_verify_depth 10; + proxy_set_header SSL_CLIENT_CERT $ssl_client_cert; + {{ end }} {{ if not (empty $authPath) }} diff --git a/core/pkg/ingress/controller/backend_ssl.go b/core/pkg/ingress/controller/backend_ssl.go index 92c32fc8c..8fc15d3dd 100644 --- a/core/pkg/ingress/controller/backend_ssl.go +++ b/core/pkg/ingress/controller/backend_ssl.go @@ -98,6 +98,8 @@ func (ic *GenericController) syncSecret(k interface{}) error { return nil } +// getPemCertificate receives a secret, and creates a ingress.SSLCert as return. +// It parses the secret and verifies if it's a keypair, or a 'ca.crt' secret only. func (ic *GenericController) getPemCertificate(secretName string) (*ingress.SSLCert, error) { secretInterface, exists, err := ic.secrLister.Store.GetByKey(secretName) if err != nil { @@ -108,19 +110,24 @@ func (ic *GenericController) getPemCertificate(secretName string) (*ingress.SSLC } secret := secretInterface.(*api.Secret) - cert, ok := secret.Data[api.TLSCertKey] - if !ok { - return nil, fmt.Errorf("secret named %v has no private key", secretName) - } - key, ok := secret.Data[api.TLSPrivateKeyKey] - if !ok { - return nil, fmt.Errorf("secret named %v has no cert", secretName) - } + cert, okcert := secret.Data[api.TLSCertKey] + key, okkey := secret.Data[api.TLSPrivateKeyKey] ca := secret.Data["ca.crt"] nsSecName := strings.Replace(secretName, "/", "-", -1) - s, err := ssl.AddOrUpdateCertAndKey(nsSecName, cert, key, ca) + + var s *ingress.SSLCert + if okcert && okkey { + glog.V(3).Infof("Found certificate and private key, configuring %v as a TLS Secret", secretName) + s, err = ssl.AddOrUpdateCertAndKey(nsSecName, cert, key, ca) + } else if ca != nil { + glog.V(3).Infof("Found only ca.crt, configuring %v as an AuthCert secret", secretName) + s, err = ssl.AddOrUpdateCertAuth(nsSecName, ca) + } else { + return nil, fmt.Errorf("No keypair or CA cert could be found in %v", secretName) + } + if err != nil { return nil, err } diff --git a/core/pkg/net/ssl/ssl.go b/core/pkg/net/ssl/ssl.go index 5907d5f5e..4d4e59d84 100644 --- a/core/pkg/net/ssl/ssl.go +++ b/core/pkg/net/ssl/ssl.go @@ -37,6 +37,8 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert, pemFileName := fmt.Sprintf("%v/%v", ingress.DefaultSSLDirectory, pemName) tempPemFile, err := ioutil.TempFile(ingress.DefaultSSLDirectory, pemName) + + glog.V(3).Infof("AddOrUpdateCertAndKey: Creating temp file %v for Keypair: %v", tempPemFile.Name(), pemName) if err != nil { return nil, fmt.Errorf("could not create temp pem file %v: %v", pemFileName, err) } @@ -125,6 +127,56 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert, }, nil } +// AddOrUpdateCertAuth creates a .pem file with the specified CAs to be used in Cert Authentication +func AddOrUpdateCertAuth(name string, ca []byte) (*ingress.SSLCert, error) { + + caName := fmt.Sprintf("ca-%v.pem", name) + caFileName := fmt.Sprintf("%v/%v", ingress.DefaultSSLDirectory, caName) + + tempCAPemFile, err := ioutil.TempFile(ingress.DefaultSSLDirectory, caName) + glog.V(3).Infof("AddOrUpdateCertAuth: Creating temp file %v for CA: %v", tempCAPemFile.Name(), caName) + + if err != nil { + return nil, fmt.Errorf("could not create temp CA pem file %v: %v", tempCAPemFile.Name(), err) + } + + _, err = tempCAPemFile.Write(ca) + if err != nil { + return nil, fmt.Errorf("could not write to CA pem file %v: %v", tempCAPemFile.Name(), err) + } + + err = tempCAPemFile.Close() + if err != nil { + return nil, fmt.Errorf("could not close CA temp pem file %v: %v", tempCAPemFile.Name(), err) + } + + pemCACerts, err := ioutil.ReadFile(tempCAPemFile.Name()) + if err != nil { + return nil, err + } + + pemCABlock, _ := pem.Decode(pemCACerts) + if pemCABlock == nil { + return nil, fmt.Errorf("No valid PEM formatted block found") + } + + _, err = x509.ParseCertificate(pemCABlock.Bytes) + if err != nil { + return nil, err + } + + err = os.Rename(tempCAPemFile.Name(), caFileName) + if err != nil { + return nil, fmt.Errorf("could not move temp pem file %v to destination %v: %v", tempCAPemFile.Name(), caFileName, err) + } + + return &ingress.SSLCert{ + CAFileName: caFileName, + PemFileName: caFileName, + PemSHA: pemSHA1(caFileName), + }, nil +} + // SearchDHParamFile iterates all the secrets mounted inside the /etc/nginx-ssl directory // in order to find a file with the name dhparam.pem. If such file exists it will // returns the path. If not it just returns an empty string diff --git a/examples/tls-authentication/nginx/README.md b/examples/tls-authentication/nginx/README.md new file mode 100644 index 000000000..7b9160401 --- /dev/null +++ b/examples/tls-authentication/nginx/README.md @@ -0,0 +1,56 @@ +# TLS termination + +This example demonstrates how to enable the TLS Authentication through the nginx Ingress controller. + +## Prerequisites + +You need a valid CA File, composed of a group of valid enabled CAs. This MUST be in PEM Format. +Also the Ingress must terminate TLS, otherwise this makes no sense ;) + +## Deployment + +The following command instructs the controller to enable the TLS Authentication using +the secret containing the valid CA chains. + +```console +$ kubectl create -f nginx-tls-auth.yaml +``` + +## Validation + +You can confirm that the Ingress works. + +```console +$ kubectl describe ing nginx-test +Name: nginx-test +Namespace: default +Address: 104.198.183.6 +Default backend: default-http-backend:80 (10.180.0.4:8080,10.240.0.2:8080) +TLS: + tls-secret terminates ingress.test.com +Rules: + Host Path Backends + ---- ---- -------- + * + http-svc:80 () +Annotations: + auth-tls-secret: default/caingress + +Events: + FirstSeen LastSeen Count From SubObjectPath Type Reason Message + --------- -------- ----- ---- ------------- -------- ------ ------- + 7s 7s 1 {nginx-ingress-controller } Normal CREATE default/nginx-test + 7s 7s 1 {nginx-ingress-controller } Normal UPDATE default/nginx-test + 7s 7s 1 {nginx-ingress-controller } Normal CREATE ip: 104.198.183.6 + 7s 7s 1 {nginx-ingress-controller } Warning MAPPING Ingress rule 'default/nginx-test' contains no path definition. Assuming / + + +$ curl -k https://ingress.test.com +HTTP/1.1 400 Bad Request +Server: nginx/1.11.9 + +$ curl -I -k --key ~/user.key --cert ~/user.cer https://ingress.test.com +HTTP/1.1 200 OK +Server: nginx/1.11.9 + +``` diff --git a/examples/tls-authentication/nginx/nginx-tls-auth.yaml b/examples/tls-authentication/nginx/nginx-tls-auth.yaml new file mode 100644 index 000000000..ae313eaa4 --- /dev/null +++ b/examples/tls-authentication/nginx/nginx-tls-auth.yaml @@ -0,0 +1,23 @@ +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + annotations: + # Create this with kubectl create secret generic caingress --from-file=ca.crt --namespace=default + ingress.kubernetes.io/auth-tls-secret: default/caingress + name: nginx-test + namespace: default +spec: + rules: + - host: ingress.test.com + http: + paths: + - backend: + serviceName: http-svc:80 + servicePort: 80 + path: / + tls: + - hosts: + - ingress.test.com + # Create this cert as described in 'multi-tls' example + secretName: cert +