From d3c914d2f4d1d221ed1275408d593496d916526f Mon Sep 17 00:00:00 2001 From: Steve Sloka Date: Sat, 16 May 2020 22:37:51 -0400 Subject: [PATCH] update TLS fallbackCertificate logic to require tlscertificatedelegation to each namespace where a root HTTPProxy enables fallbackCertificate for a fqdn. Signed-off-by: Steve Sloka --- internal/dag/builder.go | 10 +- internal/dag/builder_test.go | 177 +++++++++++++++++++++ internal/dag/status_test.go | 2 +- internal/featuretests/fallbackcert_test.go | 63 ++++++++ 4 files changed, 249 insertions(+), 3 deletions(-) diff --git a/internal/dag/builder.go b/internal/dag/builder.go index a036f627a6d..7daf442b2be 100644 --- a/internal/dag/builder.go +++ b/internal/dag/builder.go @@ -534,11 +534,17 @@ func (b *Builder) computeHTTPProxy(proxy *projcontour.HTTPProxy) { return } - sec, err = b.lookupSecret(k8s.FullName{Name: b.FallbackCertificate.Name, Namespace: b.FallbackCertificate.Namespace}, validSecret) + sec, err = b.lookupSecret(*b.FallbackCertificate, validSecret) if err != nil { - sw.SetInvalid("Spec.Virtualhost.TLS fallback certificate Secret %q is invalid: %s", b.FallbackCertificate, err) + sw.SetInvalid("Spec.Virtualhost.TLS Secret %q fallback certificate is invalid: %s", b.FallbackCertificate, err) return } + + if !b.delegationPermitted(*b.FallbackCertificate, proxy.Namespace) { + sw.SetInvalid("Spec.VirtualHost.TLS fallback Secret %q is not configured for certificate delegation", b.FallbackCertificate) + return + } + svhost.FallbackCertificate = sec } diff --git a/internal/dag/builder_test.go b/internal/dag/builder_test.go index 4d60a5d321e..69dbcf36303 100644 --- a/internal/dag/builder_test.go +++ b/internal/dag/builder_test.go @@ -67,6 +67,15 @@ func TestDAGInsert(t *testing.T) { }, } + sec4 := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "secret", + Namespace: "root", + }, + Type: v1.SecretTypeTLS, + Data: secretdata(CERTIFICATE, RSA_PRIVATE_KEY), + } + fallbackCertificateSecret := &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "fallbacksecret", @@ -76,6 +85,15 @@ func TestDAGInsert(t *testing.T) { Data: secretdata(CERTIFICATE, RSA_PRIVATE_KEY), } + fallbackCertificateSecretRootNamespace := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fallbacksecret", + Namespace: "root", + }, + Type: v1.SecretTypeTLS, + Data: secretdata(CERTIFICATE, RSA_PRIVATE_KEY), + } + cert1 := &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "ca", @@ -6438,6 +6456,165 @@ func TestDAGInsert(t *testing.T) { }, ), }, + "httpproxy with fallback certificate enabled - cert delegation not configured": { + fallbackCertificateName: "fallbacksecret", + fallbackCertificateNamespace: "root", + objs: []interface{}{ + sec4, + s9, + fallbackCertificateSecret, + &projcontour.HTTPProxy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "nginx", + Namespace: "default", + }, + Spec: projcontour.HTTPProxySpec{ + VirtualHost: &projcontour.VirtualHost{ + Fqdn: "example.com", + TLS: &projcontour.TLS{ + SecretName: sec1.Name, + EnableFallbackCertificate: true, + }, + }, + Routes: []projcontour.Route{{ + Services: []projcontour.Service{{ + Name: "nginx", + Port: 80, + }}, + }}, + }, + }, + }, + want: listeners(), + }, + "httpproxy with fallback certificate enabled - cert delegation configured all namespaces": { + fallbackCertificateName: "fallbacksecret", + fallbackCertificateNamespace: "root", + objs: []interface{}{ + sec1, + s9, + fallbackCertificateSecretRootNamespace, + &projcontour.TLSCertificateDelegation{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fallbackcertdelegation", + Namespace: "root", + }, + Spec: projcontour.TLSCertificateDelegationSpec{ + Delegations: []projcontour.CertificateDelegation{{ + SecretName: "fallbacksecret", + TargetNamespaces: []string{"*"}, + }}, + }, + }, + &projcontour.HTTPProxy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "nginx", + Namespace: "default", + }, + Spec: projcontour.HTTPProxySpec{ + VirtualHost: &projcontour.VirtualHost{ + Fqdn: "example.com", + TLS: &projcontour.TLS{ + SecretName: sec1.Name, + EnableFallbackCertificate: true, + }, + }, + Routes: []projcontour.Route{{ + Services: []projcontour.Service{{ + Name: "nginx", + Port: 80, + }}, + }}, + }, + }, + }, + want: listeners( + &Listener{ + Port: 80, + VirtualHosts: virtualhosts( + virtualhost("example.com", routeUpgrade("/", service(s9))), + ), + }, + &Listener{ + Port: 443, + VirtualHosts: virtualhosts( + &SecureVirtualHost{ + VirtualHost: VirtualHost{ + Name: "example.com", + routes: routes(routeUpgrade("/", service(s9))), + }, + MinProtoVersion: envoy_api_v2_auth.TlsParameters_TLSv1_1, + Secret: secret(sec1), + FallbackCertificate: secret(fallbackCertificateSecretRootNamespace), + }, + ), + }, + ), + }, + "httpproxy with fallback certificate enabled - cert delegation configured single namespaces": { + fallbackCertificateName: "fallbacksecret", + fallbackCertificateNamespace: "root", + objs: []interface{}{ + sec1, + s9, + fallbackCertificateSecretRootNamespace, + &projcontour.TLSCertificateDelegation{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fallbackcertdelegation", + Namespace: "root", + }, + Spec: projcontour.TLSCertificateDelegationSpec{ + Delegations: []projcontour.CertificateDelegation{{ + SecretName: "fallbacksecret", + TargetNamespaces: []string{"default"}, + }}, + }, + }, + &projcontour.HTTPProxy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "nginx", + Namespace: "default", + }, + Spec: projcontour.HTTPProxySpec{ + VirtualHost: &projcontour.VirtualHost{ + Fqdn: "example.com", + TLS: &projcontour.TLS{ + SecretName: sec1.Name, + EnableFallbackCertificate: true, + }, + }, + Routes: []projcontour.Route{{ + Services: []projcontour.Service{{ + Name: "nginx", + Port: 80, + }}, + }}, + }, + }, + }, + want: listeners( + &Listener{ + Port: 80, + VirtualHosts: virtualhosts( + virtualhost("example.com", routeUpgrade("/", service(s9))), + ), + }, + &Listener{ + Port: 443, + VirtualHosts: virtualhosts( + &SecureVirtualHost{ + VirtualHost: VirtualHost{ + Name: "example.com", + routes: routes(routeUpgrade("/", service(s9))), + }, + MinProtoVersion: envoy_api_v2_auth.TlsParameters_TLSv1_1, + Secret: secret(sec1), + FallbackCertificate: secret(fallbackCertificateSecretRootNamespace), + }, + ), + }, + ), + }, "httpproxy with fallback certificate enabled - no tls secret": { fallbackCertificateName: "fallbacksecret", fallbackCertificateNamespace: "default", diff --git a/internal/dag/status_test.go b/internal/dag/status_test.go index cbff75cb481..c59b2e5871d 100644 --- a/internal/dag/status_test.go +++ b/internal/dag/status_test.go @@ -2653,7 +2653,7 @@ func TestDAGStatus(t *testing.T) { }, objs: []interface{}{fallbackCertificate, fallbackSecret, sec1, s4}, want: map[k8s.FullName]Status{ - {Name: fallbackCertificate.Name, Namespace: fallbackCertificate.Namespace}: {Object: fallbackCertificate, Status: "invalid", Description: "Spec.Virtualhost.TLS fallback certificate Secret \"invalid/invalid\" is invalid: Secret not found", Vhost: "example.com"}, + {Name: fallbackCertificate.Name, Namespace: fallbackCertificate.Namespace}: {Object: fallbackCertificate, Status: "invalid", Description: "Spec.Virtualhost.TLS Secret \"invalid/invalid\" fallback certificate is invalid: Secret not found", Vhost: "example.com"}, }, }, "fallback certificate requested but cert not configured in contour": { diff --git a/internal/featuretests/fallbackcert_test.go b/internal/featuretests/fallbackcert_test.go index 2692c867b4b..b203e5d9207 100644 --- a/internal/featuretests/fallbackcert_test.go +++ b/internal/featuretests/fallbackcert_test.go @@ -83,6 +83,7 @@ func TestFallbackCertificate(t *testing.T) { }}, }}, }) + rh.OnAdd(proxy1) // We should start with a single generic HTTPS service. @@ -124,6 +125,27 @@ func TestFallbackCertificate(t *testing.T) { rh.OnUpdate(proxy1, proxy2) + // Invalid since there's no TLSCertificateDelegation configured + c.Request(listenerType, "ingress_https").Equals(&v2.DiscoveryResponse{ + Resources: nil, + TypeUrl: listenerType, + }) + + certDelegationAll := &projcontour.TLSCertificateDelegation{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fallbackcertdelegation", + Namespace: "admin", + }, + Spec: projcontour.TLSCertificateDelegationSpec{ + Delegations: []projcontour.CertificateDelegation{{ + SecretName: "fallbacksecret", + TargetNamespaces: []string{"*"}, + }}, + }, + } + + rh.OnAdd(certDelegationAll) + // Now we should still have the generic HTTPS service filter, // but also the fallback certificate filter. c.Request(listenerType, "ingress_https").Equals(&v2.DiscoveryResponse{ @@ -145,6 +167,47 @@ func TestFallbackCertificate(t *testing.T) { ), }) + rh.OnDelete(certDelegationAll) + + c.Request(listenerType, "ingress_https").Equals(&v2.DiscoveryResponse{ + Resources: nil, + TypeUrl: listenerType, + }) + + certDelegationSingle := &projcontour.TLSCertificateDelegation{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fallbackcertdelegation", + Namespace: "admin", + }, + Spec: projcontour.TLSCertificateDelegationSpec{ + Delegations: []projcontour.CertificateDelegation{{ + SecretName: "fallbacksecret", + TargetNamespaces: []string{"default"}, + }}, + }, + } + + rh.OnAdd(certDelegationSingle) + + c.Request(listenerType, "ingress_https").Equals(&v2.DiscoveryResponse{ + TypeUrl: listenerType, + Resources: resources(t, + &v2.Listener{ + Name: "ingress_https", + Address: envoy.SocketAddress("0.0.0.0", 8443), + ListenerFilters: envoy.ListenerFilters( + envoy.TLSInspector(), + ), + FilterChains: appendFilterChains( + filterchaintls("fallback.example.com", sec1, + httpsFilterFor("fallback.example.com"), + nil, "h2", "http/1.1"), + filterchaintlsfallback(fallbackSecret, nil, "h2", "http/1.1"), + ), + }, + ), + }) + // Invalid HTTPProxy with FallbackCertificate enabled along with ClientValidation proxy3 := fixture.NewProxy("simple").WithSpec( projcontour.HTTPProxySpec{