From def13fc06cd6b2732277ca2ee90993cf34054170 Mon Sep 17 00:00:00 2001 From: Gabor Lekeny Date: Wed, 17 Jul 2019 02:23:32 +0200 Subject: [PATCH 1/3] Add proxy_ssl_* directives Add support for backends which require client certificate (eg. NiFi) authentication. The `proxy-ssl-secret` k8s annotation references a secret which is used to authenticate to the backend server. All other directives fine tune the backend communication. The following annotations are supported: * proxy-ssl-secret * proxy-ssl-ciphers * proxy-ssl-protocol * proxy-ssl-verify * proxy-ssl-verify-depth --- .../nginx-configuration/annotations.md | 20 ++ internal/ingress/annotations/annotations.go | 3 + internal/ingress/annotations/proxyssl/main.go | 157 +++++++++++ .../ingress/annotations/proxyssl/main_test.go | 265 ++++++++++++++++++ internal/ingress/controller/controller.go | 11 + internal/ingress/controller/store/store.go | 1 + internal/ingress/types.go | 8 + rootfs/etc/nginx/template/nginx.tmpl | 11 + 8 files changed, 476 insertions(+) create mode 100644 internal/ingress/annotations/proxyssl/main.go create mode 100644 internal/ingress/annotations/proxyssl/main_test.go diff --git a/docs/user-guide/nginx-configuration/annotations.md b/docs/user-guide/nginx-configuration/annotations.md index 574117abee..8abef6b992 100755 --- a/docs/user-guide/nginx-configuration/annotations.md +++ b/docs/user-guide/nginx-configuration/annotations.md @@ -67,6 +67,11 @@ You can add these Kubernetes annotations to specific Ingress objects to customiz |[nginx.ingress.kubernetes.io/proxy-redirect-from](#proxy-redirect)|string| |[nginx.ingress.kubernetes.io/proxy-redirect-to](#proxy-redirect)|string| |[nginx.ingress.kubernetes.io/proxy-http-version](#proxy-http-version)|"1.0" or "1.1"| +|[nginx.ingress.kubernetes.io/proxy-ssl-secret](#backend-certificate-authentication)|string| +|[nginx.ingress.kubernetes.io/proxy-ssl-ciphers](#backend-certificate-authentication)|string| +|[nginx.ingress.kubernetes.io/proxy-ssl-protocols](#backend-certificate-authentication)|string| +|[nginx.ingress.kubernetes.io/proxy-ssl-verify](#backend-certificate-authentication)|string| +|[nginx.ingress.kubernetes.io/proxy-ssl-verify-depth](#backend-certificate-authentication)|number| |[nginx.ingress.kubernetes.io/enable-rewrite-log](#enable-rewrite-log)|"true" or "false"| |[nginx.ingress.kubernetes.io/rewrite-target](#rewrite)|URI| |[nginx.ingress.kubernetes.io/satisfy](#satisfy)|string| @@ -233,6 +238,21 @@ The annotations are: Only Authenticated Origin Pulls are allowed and can be configured by following their tutorial: [https://support.cloudflare.com/hc/en-us/articles/204494148-Setting-up-NGINX-to-use-TLS-Authenticated-Origin-Pulls](https://support.cloudflare.com/hc/en-us/articles/204494148-Setting-up-NGINX-to-use-TLS-Authenticated-Origin-Pulls) +### Backend Certificate Authentication + +It is possible to authenticate to a proxied HTTPS backend with certificate using additional annotations in Ingress Rule. + +* `nginx.ingress.kubernetes.io/proxy-ssl-secret: secretName`: + Specifies a Secret with the certificate `tls.crt`, key `tls.key` in PEM format used for authentication to a proxied HTTPS server. It should also contain trusted CA certificates `ca.crt` in PEM format used to verify the certificate of the proxied HTTPS server. + This annotation also accepts the alternative form "namespace/secretName", in which case the Secret lookup is performed in the referenced namespace instead of the Ingress namespace. +* `nginx.ingress.kubernetes.io/proxy-ssl-verify`: + Enables or disables verification of the proxied HTTPS server certificate. (default: off) +* `nginx.ingress.kubernetes.io/proxy-ssl-verify-depth`: + Sets the verification depth in the proxied HTTPS server certificates chain. (default: 1) +* `nginx.ingress.kubernetes.io/proxy-ssl-ciphers`: + Specifies the enabled [ciphers](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_ssl_ciphers) for requests to a proxied HTTPS server. The ciphers are specified in the format understood by the OpenSSL library. +* `nginx.ingress.kubernetes.io/proxy-ssl-protocols`: + Enables the specified [protocols](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_ssl_protocols) for requests to a proxied HTTPS server. ### Configuration snippet diff --git a/internal/ingress/annotations/annotations.go b/internal/ingress/annotations/annotations.go index 00080c8400..c11ebc2b44 100644 --- a/internal/ingress/annotations/annotations.go +++ b/internal/ingress/annotations/annotations.go @@ -20,6 +20,7 @@ import ( "github.com/imdario/mergo" "k8s.io/ingress-nginx/internal/ingress/annotations/canary" "k8s.io/ingress-nginx/internal/ingress/annotations/modsecurity" + "k8s.io/ingress-nginx/internal/ingress/annotations/proxyssl" "k8s.io/ingress-nginx/internal/ingress/annotations/sslcipher" "k8s.io/klog" @@ -87,6 +88,7 @@ type Ingress struct { EnableGlobalAuth bool HTTP2PushPreload bool Proxy proxy.Config + ProxySSL proxyssl.Config RateLimit ratelimit.Config Redirect redirect.Config Rewrite rewrite.Config @@ -132,6 +134,7 @@ func NewAnnotationExtractor(cfg resolver.Resolver) Extractor { "EnableGlobalAuth": authreqglobal.NewParser(cfg), "HTTP2PushPreload": http2pushpreload.NewParser(cfg), "Proxy": proxy.NewParser(cfg), + "ProxySSL": proxyssl.NewParser(cfg), "RateLimit": ratelimit.NewParser(cfg), "Redirect": redirect.NewParser(cfg), "Rewrite": rewrite.NewParser(cfg), diff --git a/internal/ingress/annotations/proxyssl/main.go b/internal/ingress/annotations/proxyssl/main.go new file mode 100644 index 0000000000..4617458939 --- /dev/null +++ b/internal/ingress/annotations/proxyssl/main.go @@ -0,0 +1,157 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package proxyssl + +import ( + "regexp" + "sort" + "strings" + + "github.com/pkg/errors" + networking "k8s.io/api/networking/v1beta1" + "k8s.io/ingress-nginx/internal/ingress/annotations/parser" + ing_errors "k8s.io/ingress-nginx/internal/ingress/errors" + "k8s.io/ingress-nginx/internal/ingress/resolver" + "k8s.io/ingress-nginx/internal/k8s" +) + +const ( + defaultProxySSLCiphers = "DEFAULT" + defaultProxySSLProtocols = "TLSv1 TLSv1.1 TLSv1.2" + defaultProxySSLVerify = "off" + defaultProxySSLVerifyDepth = 1 +) + +var ( + proxySSLOnOffRegex = regexp.MustCompile(`^(on|off)$`) + proxySSLProtocolRegex = regexp.MustCompile(`^(SSLv2|SSLv3|TLSv1|TLSv1\.1|TLSv1\.2|TLSv1\.3)$`) +) + +// Config contains the AuthSSLCert used for mutual authentication +// and the configured VerifyDepth +type Config struct { + resolver.AuthSSLCert + Ciphers string `json:"ciphers"` + Protocols string `json:"protocols"` + Verify string `json:"verify"` + VerifyDepth int `json:"verifyDepth"` +} + +// Equal tests for equality between two Config types +func (pssl1 *Config) Equal(pssl2 *Config) bool { + if pssl1 == pssl2 { + return true + } + if pssl1 == nil || pssl2 == nil { + return false + } + if !(&pssl1.AuthSSLCert).Equal(&pssl2.AuthSSLCert) { + return false + } + if pssl1.Ciphers != pssl2.Ciphers { + return false + } + if pssl1.Protocols != pssl2.Protocols { + return false + } + if pssl1.Verify != pssl2.Verify { + return false + } + if pssl1.VerifyDepth != pssl2.VerifyDepth { + return false + } + return true +} + +// NewParser creates a new TLS authentication annotation parser +func NewParser(resolver resolver.Resolver) parser.IngressAnnotation { + return proxySSL{resolver} +} + +type proxySSL struct { + r resolver.Resolver +} + +func sortProtocols(protocols string) string { + protolist := strings.Split(protocols, " ") + + n := 0 + for _, proto := range protolist { + proto = strings.TrimSpace(proto) + if proto == "" || !proxySSLProtocolRegex.MatchString(proto) { + continue + } + protolist[n] = proto + n++ + } + + if n == 0 { + return defaultProxySSLProtocols + } + + protolist = protolist[:n] + sort.Strings(protolist) + return strings.Join(protolist, " ") +} + +// Parse parses the annotations contained in the ingress +// rule used to use a Certificate as authentication method +func (p proxySSL) Parse(ing *networking.Ingress) (interface{}, error) { + var err error + config := &Config{} + + proxysslsecret, err := parser.GetStringAnnotation("proxy-ssl-secret", ing) + if err != nil { + return &Config{}, err + } + + _, _, err = k8s.ParseNameNS(proxysslsecret) + if err != nil { + return &Config{}, ing_errors.NewLocationDenied(err.Error()) + } + + proxyCert, err := p.r.GetAuthCertificate(proxysslsecret) + if err != nil { + e := errors.Wrap(err, "error obtaining certificate") + return &Config{}, ing_errors.LocationDenied{Reason: e} + } + config.AuthSSLCert = *proxyCert + + config.Ciphers, err = parser.GetStringAnnotation("proxy-ssl-ciphers", ing) + if err != nil { + config.Ciphers = defaultProxySSLCiphers + } + + config.Protocols, err = parser.GetStringAnnotation("proxy-ssl-protocols", ing) + if err != nil { + config.Protocols = defaultProxySSLProtocols + } else { + config.Protocols = sortProtocols(config.Protocols) + } + + config.Verify, err = parser.GetStringAnnotation("proxy-ssl-verify", ing) + if err != nil || !proxySSLOnOffRegex.MatchString(config.Verify) { + config.Verify = defaultProxySSLVerify + } + + config.VerifyDepth, err = parser.GetIntAnnotation("proxy-ssl-verify-depth", ing) + if err != nil || config.VerifyDepth == 0 { + config.VerifyDepth = defaultProxySSLVerifyDepth + } + + return config, nil +} diff --git a/internal/ingress/annotations/proxyssl/main_test.go b/internal/ingress/annotations/proxyssl/main_test.go new file mode 100644 index 0000000000..922fe0f89c --- /dev/null +++ b/internal/ingress/annotations/proxyssl/main_test.go @@ -0,0 +1,265 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package proxyssl + +import ( + "testing" + + api "k8s.io/api/core/v1" + networking "k8s.io/api/networking/v1beta1" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/ingress-nginx/internal/ingress/annotations/parser" + "k8s.io/ingress-nginx/internal/ingress/errors" + "k8s.io/ingress-nginx/internal/ingress/resolver" +) + +func buildIngress() *networking.Ingress { + defaultBackend := networking.IngressBackend{ + ServiceName: "default-backend", + ServicePort: intstr.FromInt(80), + } + + return &networking.Ingress{ + ObjectMeta: meta_v1.ObjectMeta{ + Name: "foo", + Namespace: api.NamespaceDefault, + }, + Spec: networking.IngressSpec{ + Backend: &networking.IngressBackend{ + ServiceName: "default-backend", + ServicePort: intstr.FromInt(80), + }, + Rules: []networking.IngressRule{ + { + Host: "foo.bar.com", + IngressRuleValue: networking.IngressRuleValue{ + HTTP: &networking.HTTPIngressRuleValue{ + Paths: []networking.HTTPIngressPath{ + { + Path: "/foo", + Backend: defaultBackend, + }, + }, + }, + }, + }, + }, + }, + } +} + +// mocks the resolver for proxySSL +type mockSecret struct { + resolver.Mock +} + +// GetAuthCertificate from mockSecret mocks the GetAuthCertificate for backend certificate authentication +func (m mockSecret) GetAuthCertificate(name string) (*resolver.AuthSSLCert, error) { + if name != "default/demo-secret" { + return nil, errors.Errorf("there is no secret with name %v", name) + } + + return &resolver.AuthSSLCert{ + Secret: "default/demo-secret", + CAFileName: "/ssl/ca.crt", + PemSHA: "abc", + }, nil + +} + +func TestAnnotations(t *testing.T) { + ing := buildIngress() + data := map[string]string{} + + data[parser.GetAnnotationWithPrefix("proxy-ssl-secret")] = "default/demo-secret" + data[parser.GetAnnotationWithPrefix("proxy-ssl-ciphers")] = "HIGH:-SHA" + data[parser.GetAnnotationWithPrefix("proxy-ssl-name")] = "$host" + data[parser.GetAnnotationWithPrefix("proxy-ssl-protocols")] = "TLSv1.3 SSLv2 TLSv1 TLSv1.2" + data[parser.GetAnnotationWithPrefix("proxy-ssl-server-name")] = "off" + data[parser.GetAnnotationWithPrefix("proxy-ssl-session-reuse")] = "off" + data[parser.GetAnnotationWithPrefix("proxy-ssl-verify")] = "on" + data[parser.GetAnnotationWithPrefix("proxy-ssl-verify-depth")] = "3" + + ing.SetAnnotations(data) + + fakeSecret := &mockSecret{} + i, err := NewParser(fakeSecret).Parse(ing) + if err != nil { + t.Errorf("Uxpected error with ingress: %v", err) + } + + u, ok := i.(*Config) + if !ok { + t.Errorf("expected *Config but got %v", u) + } + + secret, err := fakeSecret.GetAuthCertificate("default/demo-secret") + if err != nil { + t.Errorf("unexpected error getting secret %v", err) + } + + if u.AuthSSLCert.Secret != secret.Secret { + t.Errorf("expected %v but got %v", secret.Secret, u.AuthSSLCert.Secret) + } + if u.Ciphers != "HIGH:-SHA" { + t.Errorf("expected %v but got %v", "HIGH:-SHA", u.Ciphers) + } + if u.Protocols != "SSLv2 TLSv1 TLSv1.2 TLSv1.3" { + t.Errorf("expected %v but got %v", "SSLv2 TLSv1 TLSv1.2 TLSv1.3", u.Protocols) + } + if u.Verify != "on" { + t.Errorf("expected %v but got %v", "on", u.Verify) + } + if u.VerifyDepth != 3 { + t.Errorf("expected %v but got %v", 3, u.VerifyDepth) + } +} + +func TestInvalidAnnotations(t *testing.T) { + ing := buildIngress() + fakeSecret := &mockSecret{} + data := map[string]string{} + + // No annotation + _, err := NewParser(fakeSecret).Parse(ing) + if err == nil { + t.Errorf("Expected error with ingress but got nil") + } + + // Invalid NameSpace + data[parser.GetAnnotationWithPrefix("proxy-ssl-secret")] = "demo-secret" + ing.SetAnnotations(data) + _, err = NewParser(fakeSecret).Parse(ing) + if err == nil { + t.Errorf("Expected error with ingress but got nil") + } + + // Invalid Proxy Certificate + data[parser.GetAnnotationWithPrefix("proxy-ssl-secret")] = "default/invalid-demo-secret" + ing.SetAnnotations(data) + _, err = NewParser(fakeSecret).Parse(ing) + if err == nil { + t.Errorf("Expected error with ingress but got nil") + } + + // Invalid optional Annotations + data[parser.GetAnnotationWithPrefix("proxy-ssl-secret")] = "default/demo-secret" + data[parser.GetAnnotationWithPrefix("proxy-ssl-protocols")] = "TLSv111 SSLv1" + data[parser.GetAnnotationWithPrefix("proxy-ssl-server-name")] = "w00t" + data[parser.GetAnnotationWithPrefix("proxy-ssl-session-reuse")] = "w00t" + data[parser.GetAnnotationWithPrefix("proxy-ssl-verify")] = "w00t" + data[parser.GetAnnotationWithPrefix("proxy-ssl-verify-depth")] = "abcd" + ing.SetAnnotations(data) + + i, err := NewParser(fakeSecret).Parse(ing) + if err != nil { + t.Errorf("Uxpected error with ingress: %v", err) + } + u, ok := i.(*Config) + if !ok { + t.Errorf("expected *Config but got %v", u) + } + + if u.Protocols != defaultProxySSLProtocols { + t.Errorf("expected %v but got %v", defaultProxySSLProtocols, u.Protocols) + } + if u.Verify != defaultProxySSLVerify { + t.Errorf("expected %v but got %v", defaultProxySSLVerify, u.Verify) + } + if u.VerifyDepth != defaultProxySSLVerifyDepth { + t.Errorf("expected %v but got %v", defaultProxySSLVerifyDepth, u.VerifyDepth) + } +} + +func TestEquals(t *testing.T) { + cfg1 := &Config{} + cfg2 := &Config{} + + // Same config + result := cfg1.Equal(cfg1) + if result != true { + t.Errorf("Expected true") + } + + // compare nil + result = cfg1.Equal(nil) + if result != false { + t.Errorf("Expected false") + } + + // Different Certs + sslCert1 := resolver.AuthSSLCert{ + Secret: "default/demo-secret", + CAFileName: "/ssl/ca.crt", + PemSHA: "abc", + } + sslCert2 := resolver.AuthSSLCert{ + Secret: "default/other-demo-secret", + CAFileName: "/ssl/ca.crt", + PemSHA: "abc", + } + cfg1.AuthSSLCert = sslCert1 + cfg2.AuthSSLCert = sslCert2 + result = cfg1.Equal(cfg2) + if result != false { + t.Errorf("Expected false") + } + cfg2.AuthSSLCert = sslCert1 + + // Different Ciphers + cfg1.Ciphers = "DEFAULT" + cfg2.Ciphers = "HIGH:-SHA" + result = cfg1.Equal(cfg2) + if result != false { + t.Errorf("Expected false") + } + cfg2.Ciphers = "DEFAULT" + + // Different Protocols + cfg1.Protocols = "SSLv2 TLSv1 TLSv1.2 TLSv1.3" + cfg2.Protocols = "SSLv3 TLSv1 TLSv1.2 TLSv1.3" + result = cfg1.Equal(cfg2) + if result != false { + t.Errorf("Expected false") + } + cfg2.Protocols = "SSLv2 TLSv1 TLSv1.2 TLSv1.3" + + // Different Verify + cfg1.Verify = "off" + cfg2.Verify = "on" + result = cfg1.Equal(cfg2) + if result != false { + t.Errorf("Expected false") + } + cfg2.Verify = "off" + + // Different VerifyDepth + cfg1.VerifyDepth = 1 + cfg2.VerifyDepth = 2 + result = cfg1.Equal(cfg2) + if result != false { + t.Errorf("Expected false") + } + cfg2.VerifyDepth = 1 + + // Equal Configs + result = cfg1.Equal(cfg2) + if result != true { + t.Errorf("Expected true") + } +} diff --git a/internal/ingress/controller/controller.go b/internal/ingress/controller/controller.go index e37e085532..3830fc5a8c 100644 --- a/internal/ingress/controller/controller.go +++ b/internal/ingress/controller/controller.go @@ -481,6 +481,17 @@ func (n *NGINXController) getBackendServers(ingresses []*ingress.Ingress) ([]*in server.Hostname, ingKey) } + if server.ProxySSL.CAFileName == "" { + server.ProxySSL = anns.ProxySSL + if server.ProxySSL.Secret != "" && server.ProxySSL.CAFileName == "" { + klog.V(3).Infof("Secret %q has no 'ca.crt' key, client cert authentication disabled for Ingress %q", + server.ProxySSL.Secret, ingKey) + } + } else { + klog.V(3).Infof("Server %q is already configured for client cert authentication (Ingress %q)", + server.Hostname, ingKey) + } + if rule.HTTP == nil { klog.V(3).Infof("Ingress %q does not contain any HTTP rule, using default backend", ingKey) continue diff --git a/internal/ingress/controller/store/store.go b/internal/ingress/controller/store/store.go index 114bfe18e3..f1a5ff4f8d 100644 --- a/internal/ingress/controller/store/store.go +++ b/internal/ingress/controller/store/store.go @@ -684,6 +684,7 @@ func (s *k8sStore) updateSecretIngressMap(ing *networkingv1beta1.Ingress) { secretAnnotations := []string{ "auth-secret", "auth-tls-secret", + "proxy-ssl-secret", } for _, ann := range secretAnnotations { secrKey, err := objectRefAnnotationNsKey(ann, ing) diff --git a/internal/ingress/types.go b/internal/ingress/types.go index accc89c599..f8c98a4325 100644 --- a/internal/ingress/types.go +++ b/internal/ingress/types.go @@ -33,6 +33,7 @@ import ( "k8s.io/ingress-nginx/internal/ingress/annotations/luarestywaf" "k8s.io/ingress-nginx/internal/ingress/annotations/modsecurity" "k8s.io/ingress-nginx/internal/ingress/annotations/proxy" + "k8s.io/ingress-nginx/internal/ingress/annotations/proxyssl" "k8s.io/ingress-nginx/internal/ingress/annotations/ratelimit" "k8s.io/ingress-nginx/internal/ingress/annotations/redirect" "k8s.io/ingress-nginx/internal/ingress/annotations/rewrite" @@ -188,6 +189,9 @@ type Server struct { // CertificateAuth indicates the this server requires mutual authentication // +optional CertificateAuth authtls.Config `json:"certificateAuth"` + // ProxySSL indicates the this server uses client certificate to access backends + // +optional + ProxySSL proxyssl.Config `json:"proxySSL"` // ServerSnippet returns the snippet of server // +optional ServerSnippet string `json:"serverSnippet"` @@ -273,6 +277,10 @@ type Location struct { // to be used in connections against endpoints // +optional Proxy proxy.Config `json:"proxy,omitempty"` + // ProxySSL contains information about SSL configuration parameters + // to be used in connections against endpoints + // +optional + ProxySSL proxyssl.Config `json:"proxySSL,omitempty"` // UsePortInRedirects indicates if redirects must specify the port // +optional UsePortInRedirects bool `json:"usePortInRedirects"` diff --git a/rootfs/etc/nginx/template/nginx.tmpl b/rootfs/etc/nginx/template/nginx.tmpl index 619ca4a7fb..9eae43fd08 100755 --- a/rootfs/etc/nginx/template/nginx.tmpl +++ b/rootfs/etc/nginx/template/nginx.tmpl @@ -864,6 +864,17 @@ stream { {{ end }} {{ end }} + {{ if not (empty $server.ProxySSL.CAFileName) }} + # PEM sha: {{ $server.ProxySSL.PemSHA }} + proxy_ssl_certificate {{ $server.ProxySSL.CAFileName }}; + proxy_ssl_certificate_key {{ $server.ProxySSL.CAFileName }}; + proxy_ssl_trusted_certificate {{ $server.ProxySSL.CAFileName }}; + proxy_ssl_ciphers {{ $server.ProxySSL.Ciphers }}; + proxy_ssl_protocols {{ $server.ProxySSL.Protocols }}; + proxy_ssl_verify {{ $server.ProxySSL.Verify }}; + proxy_ssl_verify_depth {{ $server.ProxySSL.VerifyDepth }}; + {{ end }} + {{ if not (empty $server.SSLCiphers) }} ssl_ciphers {{ $server.SSLCiphers }}; {{ end }} From 8a2a0e915a4e92c925f6ad92fe388578c50c99dc Mon Sep 17 00:00:00 2001 From: Gabor Lekeny Date: Wed, 14 Aug 2019 08:08:40 +0200 Subject: [PATCH 2/3] Add e2e tests for proxyssl --- test/e2e/annotations/proxyssl.go | 115 +++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 test/e2e/annotations/proxyssl.go diff --git a/test/e2e/annotations/proxyssl.go b/test/e2e/annotations/proxyssl.go new file mode 100644 index 0000000000..d83c110715 --- /dev/null +++ b/test/e2e/annotations/proxyssl.go @@ -0,0 +1,115 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package annotations + +import ( + "fmt" + "strings" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "k8s.io/ingress-nginx/test/e2e/framework" +) + +var _ = framework.IngressNginxDescribe("Annotations - ProxySSL", func() { + f := framework.NewDefaultFramework("proxyssl") + + BeforeEach(func() { + f.NewEchoDeploymentWithReplicas(2) + }) + + AfterEach(func() { + }) + + It("should set valid proxy-ssl-secret", func() { + host := "proxyssl.foo.com" + annotations := map[string]string{ + "nginx.ingress.kubernetes.io/proxy-ssl-secret": f.Namespace + "/" + host, + } + + _, err := framework.CreateIngressMASecret(f.KubeClientSet, host, host, f.Namespace) + Expect(err).ToNot(HaveOccurred()) + + ing := framework.NewSingleIngressWithTLS(host, "/", host, []string{host}, f.Namespace, "http-svc", 80, &annotations) + f.EnsureIngress(ing) + + assertProxySSL(f, host, "DEFAULT", "TLSv1 TLSv1.1 TLSv1.2", "off", 1) + }) + + It("should set valid proxy-ssl-secret, proxy-ssl-verify to on, and proxy-ssl-verify-depth to 2", func() { + host := "proxyssl.foo.com" + annotations := map[string]string{ + "nginx.ingress.kubernetes.io/proxy-ssl-secret": f.Namespace + "/" + host, + "nginx.ingress.kubernetes.io/proxy-ssl-verify": "on", + "nginx.ingress.kubernetes.io/proxy-ssl-verify-depth": "2", + } + + _, err := framework.CreateIngressMASecret(f.KubeClientSet, host, host, f.Namespace) + Expect(err).ToNot(HaveOccurred()) + + ing := framework.NewSingleIngressWithTLS(host, "/", host, []string{host}, f.Namespace, "http-svc", 80, &annotations) + f.EnsureIngress(ing) + + assertProxySSL(f, host, "DEFAULT", "TLSv1 TLSv1.1 TLSv1.2", "on", 2) + }) + + It("should set valid proxy-ssl-secret, proxy-ssl-ciphers to HIGH:!AES", func() { + host := "proxyssl.foo.com" + annotations := map[string]string{ + "nginx.ingress.kubernetes.io/proxy-ssl-secret": f.Namespace + "/" + host, + "nginx.ingress.kubernetes.io/proxy-ssl-ciphers": "HIGH:!AES", + } + + _, err := framework.CreateIngressMASecret(f.KubeClientSet, host, host, f.Namespace) + Expect(err).ToNot(HaveOccurred()) + + ing := framework.NewSingleIngressWithTLS(host, "/", host, []string{host}, f.Namespace, "http-svc", 80, &annotations) + f.EnsureIngress(ing) + + assertProxySSL(f, host, "HIGH:!AES", "TLSv1 TLSv1.1 TLSv1.2", "off", 1) + }) + + It("should set valid proxy-ssl-secret, proxy-ssl-protocols", func() { + host := "proxyssl.foo.com" + annotations := map[string]string{ + "nginx.ingress.kubernetes.io/proxy-ssl-secret": f.Namespace + "/" + host, + "nginx.ingress.kubernetes.io/proxy-ssl-protocols": "TLSv1.2 TLSv1.3", + } + + _, err := framework.CreateIngressMASecret(f.KubeClientSet, host, host, f.Namespace) + Expect(err).ToNot(HaveOccurred()) + + ing := framework.NewSingleIngressWithTLS(host, "/", host, []string{host}, f.Namespace, "http-svc", 80, &annotations) + f.EnsureIngress(ing) + + assertProxySSL(f, host, "DEFAULT", "TLSv1.2 TLSv1.3", "off", 1) + }) +}) + +func assertProxySSL(f *framework.Framework, host, ciphers, protocols, verify string, depth int) { + certFile := fmt.Sprintf("/etc/ingress-controller/ssl/%s-%s.pem", f.Namespace, host) + f.WaitForNginxServer(host, + func(server string) bool { + return strings.Contains(server, fmt.Sprintf("proxy_ssl_certificate %s;", certFile)) && + strings.Contains(server, fmt.Sprintf("proxy_ssl_certificate_key %s;", certFile)) && + strings.Contains(server, fmt.Sprintf("proxy_ssl_trusted_certificate %s;", certFile)) && + strings.Contains(server, fmt.Sprintf("proxy_ssl_ciphers %s;", ciphers)) && + strings.Contains(server, fmt.Sprintf("proxy_ssl_protocols %s;", protocols)) && + strings.Contains(server, fmt.Sprintf("proxy_ssl_verify %s;", verify)) && + strings.Contains(server, fmt.Sprintf("proxy_ssl_verify_depth %d;", depth)) + }) +} From 4624b5bc771b72ca5a1d5f6bd87f0df54d2f50f5 Mon Sep 17 00:00:00 2001 From: Gabor Lekeny Date: Fri, 16 Aug 2019 06:31:15 +0200 Subject: [PATCH 3/3] Change PemSHA to CASHA --- internal/ingress/annotations/proxyssl/main_test.go | 6 +++--- rootfs/etc/nginx/template/nginx.tmpl | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/ingress/annotations/proxyssl/main_test.go b/internal/ingress/annotations/proxyssl/main_test.go index 922fe0f89c..37279a5507 100644 --- a/internal/ingress/annotations/proxyssl/main_test.go +++ b/internal/ingress/annotations/proxyssl/main_test.go @@ -77,7 +77,7 @@ func (m mockSecret) GetAuthCertificate(name string) (*resolver.AuthSSLCert, erro return &resolver.AuthSSLCert{ Secret: "default/demo-secret", CAFileName: "/ssl/ca.crt", - PemSHA: "abc", + CASHA: "abc", }, nil } @@ -206,12 +206,12 @@ func TestEquals(t *testing.T) { sslCert1 := resolver.AuthSSLCert{ Secret: "default/demo-secret", CAFileName: "/ssl/ca.crt", - PemSHA: "abc", + CASHA: "abc", } sslCert2 := resolver.AuthSSLCert{ Secret: "default/other-demo-secret", CAFileName: "/ssl/ca.crt", - PemSHA: "abc", + CASHA: "abc", } cfg1.AuthSSLCert = sslCert1 cfg2.AuthSSLCert = sslCert2 diff --git a/rootfs/etc/nginx/template/nginx.tmpl b/rootfs/etc/nginx/template/nginx.tmpl index 127d69aed9..009608a5a7 100755 --- a/rootfs/etc/nginx/template/nginx.tmpl +++ b/rootfs/etc/nginx/template/nginx.tmpl @@ -806,7 +806,7 @@ stream { {{ end }} {{ if not (empty $server.ProxySSL.CAFileName) }} - # PEM sha: {{ $server.ProxySSL.PemSHA }} + # PEM sha: {{ $server.ProxySSL.CASHA }} proxy_ssl_certificate {{ $server.ProxySSL.CAFileName }}; proxy_ssl_certificate_key {{ $server.ProxySSL.CAFileName }}; proxy_ssl_trusted_certificate {{ $server.ProxySSL.CAFileName }};