diff --git a/controllers/nginx/configuration.md b/controllers/nginx/configuration.md index 69f27882a..2c534eedf 100644 --- a/controllers/nginx/configuration.md +++ b/controllers/nginx/configuration.md @@ -43,6 +43,8 @@ The following annotations are supported: |[ingress.kubernetes.io/auth-secret](#authentication)|string| |[ingress.kubernetes.io/auth-type](#authentication)|basic or digest| |[ingress.kubernetes.io/auth-url](#external-authentication)|string| +|[ingress.kubernetes.io/auth-tls-secret](#Certificate Authentication)|string| +|[ingress.kubernetes.io/auth-tls-verify-depth](#Certificate Authentication)|number| |[ingress.kubernetes.io/enable-cors](#enable-cors)|true or false| |[ingress.kubernetes.io/limit-connections](#rate-limiting)|number| |[ingress.kubernetes.io/limit-rps](#rate-limiting)|number| @@ -124,6 +126,27 @@ ingress.kubernetes.io/auth-realm: "realm string" Please check the [auth](examples/auth/README.md) example. +### Certificate Authentication + +It's possible to enable Certificate based authentication using additional annotations in Ingres Rule. + +The annotations are: + +``` +ingress.kubernetes.io/auth-tls-secret: secretName +``` + +The name of the secret that contains the full Certificate Authority chain that is enabled to authenticate against this ingress. It's composed of namespace/secretName + +``` +ingress.kubernetes.io/auth-tls-verify-depth +``` + +The validation depth between the provided client certificate and the Certification Authority chain. + +Please check the [tls-auth](examples/auth/client-certs/README.md) example. + + ### Enable CORS To enable Cross-Origin Resource Sharing (CORS) in an Ingress rule add the annotation `ingress.kubernetes.io/enable-cors: "true"`. This will add a section in the server location enabling this functionality. diff --git a/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl b/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl index 9f4f5b6b3..3dd0ecd2e 100644 --- a/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl +++ b/controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl @@ -225,11 +225,11 @@ http { {{ $path := buildLocation $location }} {{ $authPath := buildAuthLocation $location }} - {{ if not (empty $location.CertificateAuth.CAFileName) }} - # PEM sha: {{ $location.CertificateAuth.PemSHA }} - ssl_client_certificate {{ $location.CertificateAuth.CAFileName }}; + {{ if not (empty $location.CertificateAuth.AuthSSLCert.CAFileName) }} + # PEM sha: {{ $location.CertificateAuth.AuthSSLCert.PemSHA }} + ssl_client_certificate {{ $location.CertificateAuth.AuthSSLCert.CAFileName }}; ssl_verify_client on; - ssl_verify_depth 10; + ssl_verify_depth {{ $location.CertificateAuth.ValidationDepth }}; {{ end }} {{ if not (empty $authPath) }} @@ -297,7 +297,7 @@ http { proxy_set_header Host $host; # Pass the extracted client certificate to the backend - {{ if not (empty $location.CertificateAuth.CAFileName) }} + {{ if not (empty $location.CertificateAuth.AuthSSLCert.CAFileName) }} proxy_set_header ssl-client-cert $ssl_client_cert; {{ end }} diff --git a/core/pkg/ingress/annotations/authtls/main.go b/core/pkg/ingress/annotations/authtls/main.go index 143353249..c4172e51c 100644 --- a/core/pkg/ingress/annotations/authtls/main.go +++ b/core/pkg/ingress/annotations/authtls/main.go @@ -28,11 +28,16 @@ import ( const ( // name of the secret - authTLSSecret = "ingress.kubernetes.io/auth-tls-secret" + annotationAuthTLSSecret = "ingress.kubernetes.io/auth-tls-secret" + annotationAuthTLSDepth = "ingress.kubernetes.io/auth-tls-verify-depth" + defaultAuthTLSDepth = 1 ) -type authTLS struct { - certResolver resolver.AuthCertificate +// AuthSSLConfig contains the AuthSSLCert used for muthual autentication +// and the configured ValidationDepth +type AuthSSLConfig struct { + AuthSSLCert resolver.AuthSSLCert + ValidationDepth int `json:"validationDepth"` } // NewParser creates a new TLS authentication annotation parser @@ -40,29 +45,42 @@ func NewParser(resolver resolver.AuthCertificate) parser.IngressAnnotation { return authTLS{resolver} } -// ParseAnnotations parses the annotations contained in the ingress -// rule used to use an external URL as source for authentication +type authTLS struct { + certResolver resolver.AuthCertificate +} + +// Parse parses the annotations contained in the ingress +// rule used to use a Certificate as authentication method func (a authTLS) Parse(ing *extensions.Ingress) (interface{}, error) { - str, err := parser.GetStringAnnotation(authTLSSecret, ing) + + tlsauthsecret, err := parser.GetStringAnnotation(annotationAuthTLSSecret, ing) if err != nil { - return nil, err + return &AuthSSLConfig{}, err } - if str == "" { - return nil, ing_errors.NewLocationDenied("an empty string is not a valid secret name") + if tlsauthsecret == "" { + return &AuthSSLConfig{}, ing_errors.NewLocationDenied("an empty string is not a valid secret name") } - _, _, err = k8s.ParseNameNS(str) + _, _, err = k8s.ParseNameNS(tlsauthsecret) if err != nil { - return nil, ing_errors.NewLocationDenied("an empty string is not a valid secret name") + return &AuthSSLConfig{}, ing_errors.NewLocationDenied("an empty string is not a valid secret name") } - authCert, err := a.certResolver.GetAuthCertificate(str) + tlsdepth, err := parser.GetIntAnnotation(annotationAuthTLSDepth, ing) + if err != nil || tlsdepth == 0 { + tlsdepth = defaultAuthTLSDepth + } + + authCert, err := a.certResolver.GetAuthCertificate(tlsauthsecret) if err != nil { - return nil, ing_errors.LocationDenied{ + return &AuthSSLConfig{}, ing_errors.LocationDenied{ Reason: errors.Wrap(err, "error obtaining certificate"), } } - return authCert, nil + return &AuthSSLConfig{ + AuthSSLCert: *authCert, + ValidationDepth: tlsdepth, + }, nil } diff --git a/core/pkg/ingress/controller/backend_ssl.go b/core/pkg/ingress/controller/backend_ssl.go index 8fc15d3dd..fda361d1b 100644 --- a/core/pkg/ingress/controller/backend_ssl.go +++ b/core/pkg/ingress/controller/backend_ssl.go @@ -123,7 +123,7 @@ func (ic *GenericController) getPemCertificate(secretName string) (*ingress.SSLC 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) + s, err = ssl.AddCertAuth(nsSecName, ca) } else { return nil, fmt.Errorf("No keypair or CA cert could be found in %v", secretName) } diff --git a/core/pkg/ingress/controller/controller.go b/core/pkg/ingress/controller/controller.go index b0dbd334d..ff258e086 100644 --- a/core/pkg/ingress/controller/controller.go +++ b/core/pkg/ingress/controller/controller.go @@ -702,18 +702,16 @@ func (ic GenericController) GetAuthCertificate(secretName string) (*resolver.Aut if key != nil { ic.secretQueue.Enqueue(key) } - // Enough time to enqueue the new certificate - time.Sleep(5 * time.Second) + bc, exists := ic.sslCertTracker.Get(secretName) if !exists { return &resolver.AuthSSLCert{}, fmt.Errorf("secret %v does not exists", secretName) } cert := bc.(*ingress.SSLCert) return &resolver.AuthSSLCert{ - Secret: secretName, - CertFileName: cert.PemFileName, - CAFileName: cert.CAFileName, - PemSHA: cert.PemSHA, + Secret: secretName, + CAFileName: cert.CAFileName, + PemSHA: cert.PemSHA, }, nil } diff --git a/core/pkg/ingress/resolver/main.go b/core/pkg/ingress/resolver/main.go index 6017c8cb5..a11b35f58 100644 --- a/core/pkg/ingress/resolver/main.go +++ b/core/pkg/ingress/resolver/main.go @@ -37,8 +37,6 @@ type Secret interface { // AuthCertificate resolves a given secret name into an SSL certificate. // The secret must contain 3 keys named: // ca.crt: contains the certificate chain used for authentication -// tls.crt: (ignored) contains the tls certificate chain, or any other valid base64 data -// tls.key: (ignored) contains the tls secret key, or any other valid base64 data type AuthCertificate interface { GetAuthCertificate(string) (*AuthSSLCert, error) } @@ -48,10 +46,6 @@ type AuthCertificate interface { type AuthSSLCert struct { // Secret contains the name of the secret this was fetched from Secret string `json:"secret"` - // CertFileName contains the filename the secret's 'tls.crt' was saved to - CertFileName string `json:"certFilename"` - // KeyFileName contains the path the secret's 'tls.key' - KeyFileName string `json:"keyFilename"` // CAFileName contains the path to the secrets 'ca.crt' CAFileName string `json:"caFilename"` // PemSHA contains the SHA1 hash of the 'tls.crt' value diff --git a/core/pkg/ingress/types.go b/core/pkg/ingress/types.go index 488945574..4fb591e18 100644 --- a/core/pkg/ingress/types.go +++ b/core/pkg/ingress/types.go @@ -26,12 +26,12 @@ import ( cache_store "k8s.io/ingress/core/pkg/cache" "k8s.io/ingress/core/pkg/ingress/annotations/auth" "k8s.io/ingress/core/pkg/ingress/annotations/authreq" + "k8s.io/ingress/core/pkg/ingress/annotations/authtls" "k8s.io/ingress/core/pkg/ingress/annotations/ipwhitelist" "k8s.io/ingress/core/pkg/ingress/annotations/proxy" "k8s.io/ingress/core/pkg/ingress/annotations/ratelimit" "k8s.io/ingress/core/pkg/ingress/annotations/rewrite" "k8s.io/ingress/core/pkg/ingress/defaults" - "k8s.io/ingress/core/pkg/ingress/resolver" ) var ( @@ -273,7 +273,7 @@ type Location struct { // CertificateAuth indicates the access to this location requires // external authentication // +optional - CertificateAuth resolver.AuthSSLCert `json:"certificateAuth,omitempty"` + CertificateAuth authtls.AuthSSLConfig `json:"certificateAuth,omitempty"` // UsePortInRedirects indicates if redirects must specify the port // +optional UsePortInRedirects bool `json:"use-port-in-redirects"` diff --git a/core/pkg/net/ssl/ssl.go b/core/pkg/net/ssl/ssl.go index 460d63e16..62d2f6b7e 100644 --- a/core/pkg/net/ssl/ssl.go +++ b/core/pkg/net/ssl/ssl.go @@ -38,7 +38,7 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert, tempPemFile, err := ioutil.TempFile(ingress.DefaultSSLDirectory, pemName) - glog.V(3).Infof("AddOrUpdateCertAndKey: Creating temp file %v for Keypair: %v", tempPemFile.Name(), pemName) + glog.V(3).Infof("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) } @@ -66,12 +66,12 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert, return nil, err } - pembBock, _ := pem.Decode(pemCerts) - if pembBock == nil { + pemBlock, _ := pem.Decode(pemCerts) + if pemBlock == nil { return nil, fmt.Errorf("No valid PEM formatted block found") } - pemCert, err := x509.ParseCertificate(pembBock.Bytes) + pemCert, err := x509.ParseCertificate(pemBlock.Bytes) if err != nil { return nil, err } @@ -127,49 +127,29 @@ 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) { +// AddCertAuth creates a .pem file with the specified CAs to be used in Cert Authentication +// If it's already exists, it's clobbered. +func AddCertAuth(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) + pemCABlock, _ := pem.Decode(ca) if pemCABlock == nil { return nil, fmt.Errorf("No valid PEM formatted block found") } - _, err = x509.ParseCertificate(pemCABlock.Bytes) + _, err := x509.ParseCertificate(pemCABlock.Bytes) if err != nil { return nil, err } - err = os.Rename(tempCAPemFile.Name(), caFileName) + err = ioutil.WriteFile(caFileName, ca, 0644) if err != nil { - return nil, fmt.Errorf("could not move temp pem file %v to destination %v: %v", tempCAPemFile.Name(), caFileName, err) + return nil, fmt.Errorf("could not write CA file %v: %v", caFileName, err) } + glog.V(3).Infof("Created CA Certificate for authentication: %v", caFileName) return &ingress.SSLCert{ CAFileName: caFileName, PemFileName: caFileName, diff --git a/examples/auth/client-certs/nginx/README.md b/examples/auth/client-certs/nginx/README.md index 74436378c..fed88598a 100644 --- a/examples/auth/client-certs/nginx/README.md +++ b/examples/auth/client-certs/nginx/README.md @@ -26,7 +26,15 @@ Also your ingress must be configured as a HTTPs/TLS Ingress. ## Deployment -The following command instructs the controller to enalbe TLS authentication using the secret from the ``ingress.kubernetes.io/auth-tls-secret`` +Certificate Authentication is achieved through 2 annotations on the Ingress, as shown in the [example](nginx-tls-auth.yaml). + +|Name|Description|Values| +| --- | --- | --- | +|ingress.kubernetes.io/auth-tls-secret|Sets the secret that contains the authorized CA Chain|string| +|ingress.kubernetes.io/auth-tls-verify-depth|The verification depth Certificate Authentication will make|number (default to 1)| + + +The following command instructs the controller to enable TLS authentication using the secret from the ``ingress.kubernetes.io/auth-tls-secret`` annotation on the Ingress. Clients must present this cert to the loadbalancer, or they will receive a HTTP 400 response ```console @@ -52,6 +60,7 @@ Rules: http-svc:80 () Annotations: auth-tls-secret: default/caingress + auth-tls-verify-depth: 3 Events: FirstSeen LastSeen Count From SubObjectPath Type Reason Message diff --git a/examples/auth/client-certs/nginx/nginx-tls-auth.yaml b/examples/auth/client-certs/nginx/nginx-tls-auth.yaml index c10461549..d4f18bd0c 100644 --- a/examples/auth/client-certs/nginx/nginx-tls-auth.yaml +++ b/examples/auth/client-certs/nginx/nginx-tls-auth.yaml @@ -3,7 +3,8 @@ 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 + ingress.kubernetes.io/auth-tls-secret: "default/caingress" + ingress.kubernetes.io/auth-tls-verify-depth: "3" kubernetes.io/ingress.class: "nginx" name: nginx-test namespace: default