From 807619afcb4c0792a3dcfca4746554387f4019ff Mon Sep 17 00:00:00 2001 From: Steve Kriss Date: Wed, 15 Jun 2022 15:34:46 +0000 Subject: [PATCH] Gateway API: add support for ReferenceGrant Adds support for the renamed ReferenceGrant resource while retaining support for ReferencePolicy, to allow for a seamless migration. ReferencePolicy support will be removed in a subsequent release. Updates #4555. Signed-off-by: Steve Kriss --- cmd/contour/serve.go | 5 + examples/contour/02-role-contour.yaml | 1 + examples/gateway-provisioner/01-roles.yaml | 1 + examples/render/contour-deployment.yaml | 1 + .../render/contour-gateway-provisioner.yaml | 1 + examples/render/contour-gateway.yaml | 1 + examples/render/contour.yaml | 1 + internal/dag/builder_test.go | 100 +++++++++--------- internal/dag/cache.go | 10 ++ internal/dag/cache_test.go | 16 +-- internal/dag/gatewayapi_processor.go | 46 +++++++- internal/dag/status_test.go | 50 ++++----- internal/k8s/rbac.go | 2 +- .../objects/rbac/clusterrole/cluster_role.go | 4 +- .../resources/security-threat-model.md | 4 +- 15 files changed, 150 insertions(+), 93 deletions(-) diff --git a/cmd/contour/serve.go b/cmd/contour/serve.go index 75b6c78ccc0..39d1f85ccfd 100644 --- a/cmd/contour/serve.go +++ b/cmd/contour/serve.go @@ -809,6 +809,11 @@ func (s *Server) setupGatewayAPI(contourConfiguration contour_api_v1alpha1.Conto s.log.WithError(err).WithField("resource", "referencepolicies").Fatal("failed to create informer") } + // Inform on ReferenceGrants. + if err := informOnResource(&gatewayapi_v1alpha2.ReferenceGrant{}, eventHandler, mgr.GetCache()); err != nil { + s.log.WithError(err).WithField("resource", "referencegrants").Fatal("failed to create informer") + } + // Inform on Namespaces. if err := informOnResource(&corev1.Namespace{}, eventHandler, mgr.GetCache()); err != nil { s.log.WithError(err).WithField("resource", "namespaces").Fatal("failed to create informer") diff --git a/examples/contour/02-role-contour.yaml b/examples/contour/02-role-contour.yaml index db4351a36b5..86bc94e5afa 100644 --- a/examples/contour/02-role-contour.yaml +++ b/examples/contour/02-role-contour.yaml @@ -26,6 +26,7 @@ rules: - gatewayclasses - gateways - httproutes + - referencegrants - referencepolicies - tlsroutes verbs: diff --git a/examples/gateway-provisioner/01-roles.yaml b/examples/gateway-provisioner/01-roles.yaml index c31fdef51db..eeb478e64de 100644 --- a/examples/gateway-provisioner/01-roles.yaml +++ b/examples/gateway-provisioner/01-roles.yaml @@ -76,6 +76,7 @@ rules: - gatewayclasses - gateways - httproutes + - referencegrants - referencepolicies - tlsroutes verbs: diff --git a/examples/render/contour-deployment.yaml b/examples/render/contour-deployment.yaml index 6b46a3e5dda..ff5aa9685e5 100644 --- a/examples/render/contour-deployment.yaml +++ b/examples/render/contour-deployment.yaml @@ -4852,6 +4852,7 @@ rules: - gatewayclasses - gateways - httproutes + - referencegrants - referencepolicies - tlsroutes verbs: diff --git a/examples/render/contour-gateway-provisioner.yaml b/examples/render/contour-gateway-provisioner.yaml index 15d10c03529..487eb1de733 100644 --- a/examples/render/contour-gateway-provisioner.yaml +++ b/examples/render/contour-gateway-provisioner.yaml @@ -11626,6 +11626,7 @@ rules: - gatewayclasses - gateways - httproutes + - referencegrants - referencepolicies - tlsroutes verbs: diff --git a/examples/render/contour-gateway.yaml b/examples/render/contour-gateway.yaml index 540ff3c5ac6..c9185f3c53a 100644 --- a/examples/render/contour-gateway.yaml +++ b/examples/render/contour-gateway.yaml @@ -4857,6 +4857,7 @@ rules: - gatewayclasses - gateways - httproutes + - referencegrants - referencepolicies - tlsroutes verbs: diff --git a/examples/render/contour.yaml b/examples/render/contour.yaml index 536bec80866..c13fd2c6337 100644 --- a/examples/render/contour.yaml +++ b/examples/render/contour.yaml @@ -4852,6 +4852,7 @@ rules: - gatewayclasses - gateways - httproutes + - referencegrants - referencepolicies - tlsroutes verbs: diff --git a/internal/dag/builder_test.go b/internal/dag/builder_test.go index 5a73d60db91..a9b72f01bb3 100644 --- a/internal/dag/builder_test.go +++ b/internal/dag/builder_test.go @@ -1424,7 +1424,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, ), }, - "HTTPRoute references a backend in a different namespace, no ReferencePolicy": { + "HTTPRoute references a backend in a different namespace, no ReferenceGrant": { gatewayclass: validClass, gateway: gatewayHTTPAllNamespaces, objs: []interface{}{ @@ -1463,7 +1463,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { ), }), }, - "HTTPRoute references a backend in a different namespace, with valid ReferencePolicy": { + "HTTPRoute references a backend in a different namespace, with valid ReferenceGrant": { gatewayclass: validClass, gateway: gatewayHTTPAllNamespaces, objs: []interface{}{ @@ -1493,7 +1493,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }}, }, }, - &gatewayapi_v1alpha2.ReferencePolicy{ + &gatewayapi_v1alpha2.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: kuardService.Namespace, @@ -1516,7 +1516,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { VirtualHosts: virtualhosts(virtualhost("*", prefixrouteHTTPRoute("/", service(kuardService)))), }), }, - "HTTPRoute references a backend in a different namespace, with valid ReferencePolicy (service-specific)": { + "HTTPRoute references a backend in a different namespace, with valid ReferenceGrant (service-specific)": { gatewayclass: validClass, gateway: gatewayHTTPAllNamespaces, objs: []interface{}{ @@ -1546,7 +1546,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }}, }, }, - &gatewayapi_v1alpha2.ReferencePolicy{ + &gatewayapi_v1alpha2.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: kuardService.Namespace, @@ -1570,7 +1570,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { VirtualHosts: virtualhosts(virtualhost("*", prefixrouteHTTPRoute("/", service(kuardService)))), }), }, - "HTTPRoute references a backend in a different namespace, with invalid ReferencePolicy (wrong Kind)": { + "HTTPRoute references a backend in a different namespace, with invalid ReferenceGrant (wrong Kind)": { gatewayclass: validClass, gateway: gatewayHTTPAllNamespaces, objs: []interface{}{ @@ -1600,7 +1600,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }}, }, }, - &gatewayapi_v1alpha2.ReferencePolicy{ + &gatewayapi_v1alpha2.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: kuardService.Namespace, @@ -1625,7 +1625,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { ), }), }, - "HTTPRoute references a backend in a different namespace, with invalid ReferencePolicy (policy in wrong namespace)": { + "HTTPRoute references a backend in a different namespace, with invalid ReferenceGrant (grant in wrong namespace)": { gatewayclass: validClass, gateway: gatewayHTTPAllNamespaces, objs: []interface{}{ @@ -1655,7 +1655,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }}, }, }, - &gatewayapi_v1alpha2.ReferencePolicy{ + &gatewayapi_v1alpha2.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: "some-other-namespace", // would need to be "projectcontour" to be valid @@ -1680,7 +1680,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { ), }), }, - "HTTPRoute references a backend in a different namespace, with invalid ReferencePolicy (wrong from namespace)": { + "HTTPRoute references a backend in a different namespace, with invalid ReferenceGrant (wrong from namespace)": { gatewayclass: validClass, gateway: gatewayHTTPAllNamespaces, objs: []interface{}{ @@ -1710,7 +1710,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }}, }, }, - &gatewayapi_v1alpha2.ReferencePolicy{ + &gatewayapi_v1alpha2.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: kuardService.Namespace, @@ -1735,7 +1735,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { ), }), }, - "HTTPRoute references a backend in a different namespace, with invalid ReferencePolicy (wrong service name)": { + "HTTPRoute references a backend in a different namespace, with invalid ReferenceGrant (wrong service name)": { gatewayclass: validClass, gateway: gatewayHTTPAllNamespaces, objs: []interface{}{ @@ -1765,7 +1765,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }}, }, }, - &gatewayapi_v1alpha2.ReferencePolicy{ + &gatewayapi_v1alpha2.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: kuardService.Namespace, @@ -2089,15 +2089,15 @@ func TestDAGInsertGatewayAPI(t *testing.T) { want: listeners(), }, - // BEGIN TLS CertificateRef + ReferencePolicy tests - "Gateway references TLS cert in different namespace, with valid ReferencePolicy": { + // BEGIN TLS CertificateRef + ReferenceGrant tests + "Gateway references TLS cert in different namespace, with valid ReferenceGrant": { gatewayclass: validClass, gateway: gatewayTLSTerminateCertInDifferentNamespace, objs: []interface{}{ sec2, - &gatewayapi_v1alpha2.ReferencePolicy{ + &gatewayapi_v1alpha2.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ - Name: "tls-cert-reference-policy", + Name: "tls-cert-reference-grant", Namespace: sec2.Namespace, }, Spec: gatewayapi_v1alpha2.ReferenceGrantSpec{ @@ -2132,7 +2132,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, ), }, - "Gateway references TLS cert in different namespace, with no ReferencePolicy": { + "Gateway references TLS cert in different namespace, with no ReferenceGrant": { gatewayclass: validClass, gateway: gatewayTLSTerminateCertInDifferentNamespace, objs: []interface{}{ @@ -2142,14 +2142,14 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, want: listeners(), }, - "Gateway references TLS cert in different namespace, with valid ReferencePolicy (secret-specific)": { + "Gateway references TLS cert in different namespace, with valid ReferenceGrant (secret-specific)": { gatewayclass: validClass, gateway: gatewayTLSTerminateCertInDifferentNamespace, objs: []interface{}{ sec2, - &gatewayapi_v1alpha2.ReferencePolicy{ + &gatewayapi_v1alpha2.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ - Name: "tls-cert-reference-policy", + Name: "tls-cert-reference-grant", Namespace: sec2.Namespace, }, Spec: gatewayapi_v1alpha2.ReferenceGrantSpec{ @@ -2185,14 +2185,14 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, ), }, - "Gateway references TLS cert in different namespace, with invalid ReferencePolicy (policy in wrong namespace)": { + "Gateway references TLS cert in different namespace, with invalid ReferenceGrant (grant in wrong namespace)": { gatewayclass: validClass, gateway: gatewayTLSTerminateCertInDifferentNamespace, objs: []interface{}{ sec2, - &gatewayapi_v1alpha2.ReferencePolicy{ + &gatewayapi_v1alpha2.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ - Name: "tls-cert-reference-policy", + Name: "tls-cert-reference-grant", Namespace: "wrong-namespace", }, Spec: gatewayapi_v1alpha2.ReferenceGrantSpec{ @@ -2211,14 +2211,14 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, want: listeners(), }, - "Gateway references TLS cert in different namespace, with invalid ReferencePolicy (wrong From namespace)": { + "Gateway references TLS cert in different namespace, with invalid ReferenceGrant (wrong From namespace)": { gatewayclass: validClass, gateway: gatewayTLSTerminateCertInDifferentNamespace, objs: []interface{}{ sec2, - &gatewayapi_v1alpha2.ReferencePolicy{ + &gatewayapi_v1alpha2.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ - Name: "tls-cert-reference-policy", + Name: "tls-cert-reference-grant", Namespace: sec2.Namespace, }, Spec: gatewayapi_v1alpha2.ReferenceGrantSpec{ @@ -2237,14 +2237,14 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, want: listeners(), }, - "Gateway references TLS cert in different namespace, with invalid ReferencePolicy (wrong From kind)": { + "Gateway references TLS cert in different namespace, with invalid ReferenceGrant (wrong From kind)": { gatewayclass: validClass, gateway: gatewayTLSTerminateCertInDifferentNamespace, objs: []interface{}{ sec2, - &gatewayapi_v1alpha2.ReferencePolicy{ + &gatewayapi_v1alpha2.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ - Name: "tls-cert-reference-policy", + Name: "tls-cert-reference-grant", Namespace: sec2.Namespace, }, Spec: gatewayapi_v1alpha2.ReferenceGrantSpec{ @@ -2263,14 +2263,14 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, want: listeners(), }, - "Gateway references TLS cert in different namespace, with invalid ReferencePolicy (wrong To kind)": { + "Gateway references TLS cert in different namespace, with invalid ReferenceGrant (wrong To kind)": { gatewayclass: validClass, gateway: gatewayTLSTerminateCertInDifferentNamespace, objs: []interface{}{ sec2, - &gatewayapi_v1alpha2.ReferencePolicy{ + &gatewayapi_v1alpha2.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ - Name: "tls-cert-reference-policy", + Name: "tls-cert-reference-grant", Namespace: sec2.Namespace, }, Spec: gatewayapi_v1alpha2.ReferenceGrantSpec{ @@ -2289,14 +2289,14 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, want: listeners(), }, - "Gateway references TLS cert in different namespace, with invalid ReferencePolicy (wrong secret name)": { + "Gateway references TLS cert in different namespace, with invalid ReferenceGrant (wrong secret name)": { gatewayclass: validClass, gateway: gatewayTLSTerminateCertInDifferentNamespace, objs: []interface{}{ sec2, - &gatewayapi_v1alpha2.ReferencePolicy{ + &gatewayapi_v1alpha2.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ - Name: "tls-cert-reference-policy", + Name: "tls-cert-reference-grant", Namespace: sec2.Namespace, }, Spec: gatewayapi_v1alpha2.ReferenceGrantSpec{ @@ -2317,7 +2317,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { want: listeners(), }, - // END CertificateRef ReferencePolicy tests + // END CertificateRef ReferenceGrant tests "No valid hostnames defined": { gatewayclass: validClass, @@ -3311,7 +3311,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, ), }, - "TLSRoute references a backend in a different namespace, no ReferencePolicy": { + "TLSRoute references a backend in a different namespace, no ReferenceGrant": { gatewayclass: validClass, gateway: gatewayTLSPassthroughAllNamespaces, objs: []interface{}{ @@ -3334,7 +3334,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, want: listeners(), }, - "TLSRoute references a backend in a different namespace, with valid ReferencePolicy": { + "TLSRoute references a backend in a different namespace, with valid ReferenceGrant": { gatewayclass: validClass, gateway: gatewayTLSPassthroughAllNamespaces, objs: []interface{}{ @@ -3364,7 +3364,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }}, }, }, - &gatewayapi_v1alpha2.ReferencePolicy{ + &gatewayapi_v1alpha2.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: kuardService.Namespace, @@ -3398,7 +3398,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, ), }, - "TLSRoute references a backend in a different namespace, with valid ReferencePolicy (service-specific)": { + "TLSRoute references a backend in a different namespace, with valid ReferenceGrant (service-specific)": { gatewayclass: validClass, gateway: gatewayTLSPassthroughAllNamespaces, objs: []interface{}{ @@ -3428,7 +3428,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }}, }, }, - &gatewayapi_v1alpha2.ReferencePolicy{ + &gatewayapi_v1alpha2.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: kuardService.Namespace, @@ -3463,7 +3463,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, ), }, - "TLSRoute references a backend in a different namespace, with invalid ReferencePolicy (wrong Kind)": { + "TLSRoute references a backend in a different namespace, with invalid ReferenceGrant (wrong Kind)": { gatewayclass: validClass, gateway: gatewayTLSPassthroughAllNamespaces, objs: []interface{}{ @@ -3493,7 +3493,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }}, }, }, - &gatewayapi_v1alpha2.ReferencePolicy{ + &gatewayapi_v1alpha2.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: kuardService.Namespace, @@ -3512,7 +3512,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, want: listeners(), }, - "TLSRoute references a backend in a different namespace, with invalid ReferencePolicy (policy in wrong namespace)": { + "TLSRoute references a backend in a different namespace, with invalid ReferenceGrant (grant in wrong namespace)": { gatewayclass: validClass, gateway: gatewayTLSPassthroughAllNamespaces, objs: []interface{}{ @@ -3542,7 +3542,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }}, }, }, - &gatewayapi_v1alpha2.ReferencePolicy{ + &gatewayapi_v1alpha2.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: "some-other-namespace", // would have to be "projectcontour" to be valid @@ -3561,7 +3561,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, want: listeners(), }, - "TLSRoute references a backend in a different namespace, with invalid ReferencePolicy (wrong from namespace)": { + "TLSRoute references a backend in a different namespace, with invalid ReferenceGrant (wrong from namespace)": { gatewayclass: validClass, gateway: gatewayTLSPassthroughAllNamespaces, objs: []interface{}{ @@ -3591,7 +3591,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }}, }, }, - &gatewayapi_v1alpha2.ReferencePolicy{ + &gatewayapi_v1alpha2.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: kuardService.Namespace, @@ -3610,7 +3610,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }, want: listeners(), }, - "TLSRoute references a backend in a different namespace, with invalid ReferencePolicy (wrong service name)": { + "TLSRoute references a backend in a different namespace, with invalid ReferenceGrant (wrong service name)": { gatewayclass: validClass, gateway: gatewayTLSPassthroughAllNamespaces, objs: []interface{}{ @@ -3640,7 +3640,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) { }}, }, }, - &gatewayapi_v1alpha2.ReferencePolicy{ + &gatewayapi_v1alpha2.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ Name: "foo", Namespace: kuardService.Namespace, diff --git a/internal/dag/cache.go b/internal/dag/cache.go index 13fd8146c5d..1735ee8a177 100644 --- a/internal/dag/cache.go +++ b/internal/dag/cache.go @@ -67,6 +67,7 @@ type KubernetesCache struct { httproutes map[types.NamespacedName]*gatewayapi_v1alpha2.HTTPRoute tlsroutes map[types.NamespacedName]*gatewayapi_v1alpha2.TLSRoute referencepolicies map[types.NamespacedName]*gatewayapi_v1alpha2.ReferencePolicy + referencegrants map[types.NamespacedName]*gatewayapi_v1alpha2.ReferenceGrant extensions map[types.NamespacedName]*contour_api_v1alpha1.ExtensionService Client client.Reader @@ -86,6 +87,7 @@ func (kc *KubernetesCache) init() { kc.namespaces = make(map[string]*v1.Namespace) kc.httproutes = make(map[types.NamespacedName]*gatewayapi_v1alpha2.HTTPRoute) kc.referencepolicies = make(map[types.NamespacedName]*gatewayapi_v1alpha2.ReferencePolicy) + kc.referencegrants = make(map[types.NamespacedName]*gatewayapi_v1alpha2.ReferenceGrant) kc.tlsroutes = make(map[types.NamespacedName]*gatewayapi_v1alpha2.TLSRoute) kc.extensions = make(map[types.NamespacedName]*contour_api_v1alpha1.ExtensionService) } @@ -202,6 +204,9 @@ func (kc *KubernetesCache) Insert(obj interface{}) bool { case *gatewayapi_v1alpha2.ReferencePolicy: kc.referencepolicies[k8s.NamespacedNameOf(obj)] = obj return true + case *gatewayapi_v1alpha2.ReferenceGrant: + kc.referencegrants[k8s.NamespacedNameOf(obj)] = obj + return true case *contour_api_v1alpha1.ExtensionService: kc.extensions[k8s.NamespacedNameOf(obj)] = obj return true @@ -328,6 +333,11 @@ func (kc *KubernetesCache) remove(obj interface{}) bool { _, ok := kc.referencepolicies[m] delete(kc.referencepolicies, m) return ok + case *gatewayapi_v1alpha2.ReferenceGrant: + m := k8s.NamespacedNameOf(obj) + _, ok := kc.referencegrants[m] + delete(kc.referencegrants, m) + return ok case *contour_api_v1alpha1.ExtensionService: m := k8s.NamespacedNameOf(obj) _, ok := kc.extensions[m] diff --git a/internal/dag/cache_test.go b/internal/dag/cache_test.go index 4dabbd20448..35ea717610a 100644 --- a/internal/dag/cache_test.go +++ b/internal/dag/cache_test.go @@ -873,10 +873,10 @@ func TestKubernetesCacheInsert(t *testing.T) { }, want: true, }, - "insert gateway-api ReferencePolicy": { - obj: &gatewayapi_v1alpha2.ReferencePolicy{ + "insert gateway-api ReferenceGrant": { + obj: &gatewayapi_v1alpha2.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ - Name: "referencepolicy-1", + Name: "referencegrant-1", Namespace: "default", }, }, @@ -1233,16 +1233,16 @@ func TestKubernetesCacheRemove(t *testing.T) { }, want: true, }, - "remove gateway-api ReferencePolicy": { - cache: cache(&gatewayapi_v1alpha2.ReferencePolicy{ + "remove gateway-api ReferenceGrant": { + cache: cache(&gatewayapi_v1alpha2.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ - Name: "referencepolicy", + Name: "referencegrant", Namespace: "default", }, }), - obj: &gatewayapi_v1alpha2.ReferencePolicy{ + obj: &gatewayapi_v1alpha2.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ - Name: "referencepolicy", + Name: "referencegrant", Namespace: "default", }, }, diff --git a/internal/dag/gatewayapi_processor.go b/internal/dag/gatewayapi_processor.go index 147f3423abc..076f088a43d 100644 --- a/internal/dag/gatewayapi_processor.go +++ b/internal/dag/gatewayapi_processor.go @@ -535,7 +535,7 @@ func (p *GatewayAPIProcessor) validGatewayTLS(listenerTLS gatewayapi_v1alpha2.Ga } // If the secret is in a different namespace than the gateway, then we need to - // check for a ReferencePolicy that allows the reference. + // check for a ReferencePolicy or ReferenceGrant that allows the reference. if certificateRef.Namespace != nil && string(*certificateRef.Namespace) != p.source.gateway.Namespace { if !p.validCrossNamespaceRef( crossNamespaceFrom{ @@ -555,7 +555,7 @@ func (p *GatewayAPIProcessor) validGatewayTLS(listenerTLS gatewayapi_v1alpha2.Ga gatewayapi_v1alpha2.ListenerConditionResolvedRefs, metav1.ConditionFalse, gatewayapi_v1alpha2.ListenerReasonInvalidCertificateRef, - fmt.Sprintf("Spec.VirtualHost.TLS.CertificateRefs %q namespace must match the Gateway's namespace or be covered by a ReferencePolicy", certificateRef.Name), + fmt.Sprintf("Spec.VirtualHost.TLS.CertificateRefs %q namespace must match the Gateway's namespace or be covered by a ReferencePolicy/ReferenceGrant", certificateRef.Name), ) return nil } @@ -632,7 +632,43 @@ func (p *GatewayAPIProcessor) validCrossNamespaceRef(from crossNamespaceFrom, to return true } - // If we got here, no reference policy allowed both the "from" and "to". + for _, referenceGrant := range p.source.referencegrants { + // The ReferenceGrant must be defined in the namespace of + // the "to" (the referent). + if referenceGrant.Namespace != to.namespace { + continue + } + + // Check if the ReferenceGrant has a matching "from". + var fromAllowed bool + for _, refGrantFrom := range referenceGrant.Spec.From { + if string(refGrantFrom.Namespace) == from.namespace && string(refGrantFrom.Group) == from.group && string(refGrantFrom.Kind) == from.kind { + fromAllowed = true + break + } + } + if !fromAllowed { + continue + } + + // Check if the ReferenceGrant has a matching "to". + var toAllowed bool + for _, refGrantTo := range referenceGrant.Spec.To { + if string(refGrantTo.Group) == to.group && string(refGrantTo.Kind) == to.kind && (refGrantTo.Name == nil || *refGrantTo.Name == "" || string(*refGrantTo.Name) == to.name) { + toAllowed = true + break + } + } + if !toAllowed { + continue + } + + // If we got here, both the "from" and the "to" were allowed by this + // reference grant. + return true + } + + // If we got here, no reference policy or reference grant allowed both the "from" and "to". return false } @@ -1107,7 +1143,7 @@ func (p *GatewayAPIProcessor) validateBackendObjectRef(backendObjectRef gatewaya } // If the backend is in a different namespace than the route, then we need to - // check for a ReferencePolicy that allows the reference. + // check for a ReferencePolicy or ReferenceGrant that allows the reference. if backendObjectRef.Namespace != nil && string(*backendObjectRef.Namespace) != routeNamespace { if !p.validCrossNamespaceRef( crossNamespaceFrom{ @@ -1126,7 +1162,7 @@ func (p *GatewayAPIProcessor) validateBackendObjectRef(backendObjectRef gatewaya Type: string(gatewayapi_v1alpha2.RouteConditionResolvedRefs), Status: metav1.ConditionFalse, Reason: string(gatewayapi_v1alpha2.ListenerReasonRefNotPermitted), - Message: fmt.Sprintf("%s.Namespace must match the route's namespace or be covered by a ReferencePolicy", field), + Message: fmt.Sprintf("%s.Namespace must match the route's namespace or be covered by a ReferencePolicy/ReferenceGrant", field), } } } diff --git a/internal/dag/status_test.go b/internal/dag/status_test.go index 131c768d3e8..fce6d0177ad 100644 --- a/internal/dag/status_test.go +++ b/internal/dag/status_test.go @@ -3600,7 +3600,7 @@ func TestGatewayAPIHTTPRouteDAGStatus(t *testing.T) { Type: string(status.ConditionResolvedRefs), Status: contour_api_v1.ConditionFalse, Reason: string(gatewayapi_v1alpha2.ListenerReasonRefNotPermitted), - Message: "Spec.Rules.BackendRef.Namespace must match the route's namespace or be covered by a ReferencePolicy", + Message: "Spec.Rules.BackendRef.Namespace must match the route's namespace or be covered by a ReferencePolicy/ReferenceGrant", }, gatewayapi_v1alpha2.RouteConditionAccepted: { Type: string(gatewayapi_v1alpha2.RouteConditionAccepted), @@ -3614,8 +3614,8 @@ func TestGatewayAPIHTTPRouteDAGStatus(t *testing.T) { wantGatewayStatusUpdate: validGatewayStatusUpdate("http", "HTTPRoute", 1), }) - // BEGIN TLS CertificateRef + ReferencePolicy tests - run(t, "Gateway references TLS cert in different namespace, with valid ReferencePolicy", testcase{ + // BEGIN TLS CertificateRef + ReferenceGrant tests + run(t, "Gateway references TLS cert in different namespace, with valid ReferenceGrant", testcase{ gateway: &gatewayapi_v1alpha2.Gateway{ ObjectMeta: metav1.ObjectMeta{ Name: "contour", @@ -3650,7 +3650,7 @@ func TestGatewayAPIHTTPRouteDAGStatus(t *testing.T) { Type: v1.SecretTypeTLS, Data: secretdata(fixture.CERTIFICATE, fixture.RSA_PRIVATE_KEY), }, - &gatewayapi_v1alpha2.ReferencePolicy{ + &gatewayapi_v1alpha2.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ Name: "tls-cert-reference-policy", Namespace: "tls-cert-namespace", @@ -3670,7 +3670,7 @@ func TestGatewayAPIHTTPRouteDAGStatus(t *testing.T) { wantGatewayStatusUpdate: validGatewayStatusUpdate("tls", "TLSRoute", 0), }) - run(t, "Gateway references TLS cert in different namespace, with no ReferencePolicy", testcase{ + run(t, "Gateway references TLS cert in different namespace, with no ReferenceGrant", testcase{ gateway: &gatewayapi_v1alpha2.Gateway{ ObjectMeta: metav1.ObjectMeta{ Name: "contour", @@ -3734,7 +3734,7 @@ func TestGatewayAPIHTTPRouteDAGStatus(t *testing.T) { Type: string(gatewayapi_v1alpha2.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, Reason: string(gatewayapi_v1alpha2.ListenerReasonInvalidCertificateRef), - Message: "Spec.VirtualHost.TLS.CertificateRefs \"secret\" namespace must match the Gateway's namespace or be covered by a ReferencePolicy", + Message: "Spec.VirtualHost.TLS.CertificateRefs \"secret\" namespace must match the Gateway's namespace or be covered by a ReferencePolicy/ReferenceGrant", }, }, }, @@ -3742,7 +3742,7 @@ func TestGatewayAPIHTTPRouteDAGStatus(t *testing.T) { }}, }) - run(t, "Gateway references TLS cert in different namespace, with valid ReferencePolicy (secret-specific)", testcase{ + run(t, "Gateway references TLS cert in different namespace, with valid ReferenceGrant (secret-specific)", testcase{ gateway: &gatewayapi_v1alpha2.Gateway{ ObjectMeta: metav1.ObjectMeta{ Name: "contour", @@ -3777,7 +3777,7 @@ func TestGatewayAPIHTTPRouteDAGStatus(t *testing.T) { Type: v1.SecretTypeTLS, Data: secretdata(fixture.CERTIFICATE, fixture.RSA_PRIVATE_KEY), }, - &gatewayapi_v1alpha2.ReferencePolicy{ + &gatewayapi_v1alpha2.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ Name: "tls-cert-reference-policy", Namespace: "tls-cert-namespace", @@ -3798,7 +3798,7 @@ func TestGatewayAPIHTTPRouteDAGStatus(t *testing.T) { wantGatewayStatusUpdate: validGatewayStatusUpdate("tls", "TLSRoute", 0), }) - run(t, "Gateway references TLS cert in different namespace, with invalid ReferencePolicy (policy in wrong namespace)", testcase{ + run(t, "Gateway references TLS cert in different namespace, with invalid ReferenceGrant (policy in wrong namespace)", testcase{ gateway: &gatewayapi_v1alpha2.Gateway{ ObjectMeta: metav1.ObjectMeta{ Name: "contour", @@ -3833,7 +3833,7 @@ func TestGatewayAPIHTTPRouteDAGStatus(t *testing.T) { Type: v1.SecretTypeTLS, Data: secretdata(fixture.CERTIFICATE, fixture.RSA_PRIVATE_KEY), }, - &gatewayapi_v1alpha2.ReferencePolicy{ + &gatewayapi_v1alpha2.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ Name: "tls-cert-reference-policy", Namespace: "wrong-namespace", @@ -3878,7 +3878,7 @@ func TestGatewayAPIHTTPRouteDAGStatus(t *testing.T) { Type: string(gatewayapi_v1alpha2.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, Reason: string(gatewayapi_v1alpha2.ListenerReasonInvalidCertificateRef), - Message: "Spec.VirtualHost.TLS.CertificateRefs \"secret\" namespace must match the Gateway's namespace or be covered by a ReferencePolicy", + Message: "Spec.VirtualHost.TLS.CertificateRefs \"secret\" namespace must match the Gateway's namespace or be covered by a ReferencePolicy/ReferenceGrant", }, }, }, @@ -3886,7 +3886,7 @@ func TestGatewayAPIHTTPRouteDAGStatus(t *testing.T) { }}, }) - run(t, "Gateway references TLS cert in different namespace, with invalid ReferencePolicy (wrong From namespace)", testcase{ + run(t, "Gateway references TLS cert in different namespace, with invalid ReferenceGrant (wrong From namespace)", testcase{ gateway: &gatewayapi_v1alpha2.Gateway{ ObjectMeta: metav1.ObjectMeta{ Name: "contour", @@ -3921,7 +3921,7 @@ func TestGatewayAPIHTTPRouteDAGStatus(t *testing.T) { Type: v1.SecretTypeTLS, Data: secretdata(fixture.CERTIFICATE, fixture.RSA_PRIVATE_KEY), }, - &gatewayapi_v1alpha2.ReferencePolicy{ + &gatewayapi_v1alpha2.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ Name: "tls-cert-reference-policy", Namespace: "tls-cert-namespace", @@ -3966,7 +3966,7 @@ func TestGatewayAPIHTTPRouteDAGStatus(t *testing.T) { Type: string(gatewayapi_v1alpha2.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, Reason: string(gatewayapi_v1alpha2.ListenerReasonInvalidCertificateRef), - Message: "Spec.VirtualHost.TLS.CertificateRefs \"secret\" namespace must match the Gateway's namespace or be covered by a ReferencePolicy", + Message: "Spec.VirtualHost.TLS.CertificateRefs \"secret\" namespace must match the Gateway's namespace or be covered by a ReferencePolicy/ReferenceGrant", }, }, }, @@ -3974,7 +3974,7 @@ func TestGatewayAPIHTTPRouteDAGStatus(t *testing.T) { }}, }) - run(t, "Gateway references TLS cert in different namespace, with invalid ReferencePolicy (wrong From kind)", testcase{ + run(t, "Gateway references TLS cert in different namespace, with invalid ReferenceGrant (wrong From kind)", testcase{ gateway: &gatewayapi_v1alpha2.Gateway{ ObjectMeta: metav1.ObjectMeta{ Name: "contour", @@ -4009,7 +4009,7 @@ func TestGatewayAPIHTTPRouteDAGStatus(t *testing.T) { Type: v1.SecretTypeTLS, Data: secretdata(fixture.CERTIFICATE, fixture.RSA_PRIVATE_KEY), }, - &gatewayapi_v1alpha2.ReferencePolicy{ + &gatewayapi_v1alpha2.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ Name: "tls-cert-reference-policy", Namespace: "tls-cert-namespace", @@ -4054,7 +4054,7 @@ func TestGatewayAPIHTTPRouteDAGStatus(t *testing.T) { Type: string(gatewayapi_v1alpha2.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, Reason: string(gatewayapi_v1alpha2.ListenerReasonInvalidCertificateRef), - Message: "Spec.VirtualHost.TLS.CertificateRefs \"secret\" namespace must match the Gateway's namespace or be covered by a ReferencePolicy", + Message: "Spec.VirtualHost.TLS.CertificateRefs \"secret\" namespace must match the Gateway's namespace or be covered by a ReferencePolicy/ReferenceGrant", }, }, }, @@ -4062,7 +4062,7 @@ func TestGatewayAPIHTTPRouteDAGStatus(t *testing.T) { }}, }) - run(t, "Gateway references TLS cert in different namespace, with invalid ReferencePolicy (wrong To kind)", testcase{ + run(t, "Gateway references TLS cert in different namespace, with invalid ReferenceGrant (wrong To kind)", testcase{ gateway: &gatewayapi_v1alpha2.Gateway{ ObjectMeta: metav1.ObjectMeta{ Name: "contour", @@ -4097,7 +4097,7 @@ func TestGatewayAPIHTTPRouteDAGStatus(t *testing.T) { Type: v1.SecretTypeTLS, Data: secretdata(fixture.CERTIFICATE, fixture.RSA_PRIVATE_KEY), }, - &gatewayapi_v1alpha2.ReferencePolicy{ + &gatewayapi_v1alpha2.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ Name: "tls-cert-reference-policy", Namespace: "tls-cert-namespace", @@ -4142,7 +4142,7 @@ func TestGatewayAPIHTTPRouteDAGStatus(t *testing.T) { Type: string(gatewayapi_v1alpha2.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, Reason: string(gatewayapi_v1alpha2.ListenerReasonInvalidCertificateRef), - Message: "Spec.VirtualHost.TLS.CertificateRefs \"secret\" namespace must match the Gateway's namespace or be covered by a ReferencePolicy", + Message: "Spec.VirtualHost.TLS.CertificateRefs \"secret\" namespace must match the Gateway's namespace or be covered by a ReferencePolicy/ReferenceGrant", }, }, }, @@ -4150,7 +4150,7 @@ func TestGatewayAPIHTTPRouteDAGStatus(t *testing.T) { }}, }) - run(t, "Gateway references TLS cert in different namespace, with invalid ReferencePolicy (wrong secret name)", testcase{ + run(t, "Gateway references TLS cert in different namespace, with invalid ReferenceGrant (wrong secret name)", testcase{ gateway: &gatewayapi_v1alpha2.Gateway{ ObjectMeta: metav1.ObjectMeta{ Name: "contour", @@ -4185,7 +4185,7 @@ func TestGatewayAPIHTTPRouteDAGStatus(t *testing.T) { Type: v1.SecretTypeTLS, Data: secretdata(fixture.CERTIFICATE, fixture.RSA_PRIVATE_KEY), }, - &gatewayapi_v1alpha2.ReferencePolicy{ + &gatewayapi_v1alpha2.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ Name: "tls-cert-reference-policy", Namespace: "tls-cert-namespace", @@ -4231,7 +4231,7 @@ func TestGatewayAPIHTTPRouteDAGStatus(t *testing.T) { Type: string(gatewayapi_v1alpha2.ListenerConditionResolvedRefs), Status: metav1.ConditionFalse, Reason: string(gatewayapi_v1alpha2.ListenerReasonInvalidCertificateRef), - Message: "Spec.VirtualHost.TLS.CertificateRefs \"secret\" namespace must match the Gateway's namespace or be covered by a ReferencePolicy", + Message: "Spec.VirtualHost.TLS.CertificateRefs \"secret\" namespace must match the Gateway's namespace or be covered by a ReferencePolicy/ReferenceGrant", }, }, }, @@ -4239,7 +4239,7 @@ func TestGatewayAPIHTTPRouteDAGStatus(t *testing.T) { }}, }) - // END TLS CertificateRef + ReferencePolicy tests + // END TLS CertificateRef + ReferenceGrant tests run(t, "spec.rules.hostname: invalid wildcard", testcase{ objs: []interface{}{ @@ -4887,7 +4887,7 @@ func TestGatewayAPIHTTPRouteDAGStatus(t *testing.T) { Type: string(status.ConditionResolvedRefs), Status: contour_api_v1.ConditionFalse, Reason: string(gatewayapi_v1alpha2.ListenerReasonRefNotPermitted), - Message: "Spec.Rules.Filters.RequestMirror.BackendRef.Namespace must match the route's namespace or be covered by a ReferencePolicy", + Message: "Spec.Rules.Filters.RequestMirror.BackendRef.Namespace must match the route's namespace or be covered by a ReferencePolicy/ReferenceGrant", }, gatewayapi_v1alpha2.RouteConditionAccepted: { Type: string(gatewayapi_v1alpha2.RouteConditionAccepted), diff --git a/internal/k8s/rbac.go b/internal/k8s/rbac.go index addbbb0a44d..f375a46688d 100644 --- a/internal/k8s/rbac.go +++ b/internal/k8s/rbac.go @@ -19,7 +19,7 @@ package k8s // +kubebuilder:rbac:groups="projectcontour.io",resources=httpproxies;tlscertificatedelegations;extensionservices;contourconfigurations,verbs=get;list;watch // +kubebuilder:rbac:groups="projectcontour.io",resources=httpproxies/status;extensionservices/status;contourconfigurations/status,verbs=create;get;update -// +kubebuilder:rbac:groups="gateway.networking.k8s.io",resources=gatewayclasses;gateways;httproutes;tlsroutes;referencepolicies,verbs=get;list;watch +// +kubebuilder:rbac:groups="gateway.networking.k8s.io",resources=gatewayclasses;gateways;httproutes;tlsroutes;referencepolicies;referencegrants,verbs=get;list;watch // +kubebuilder:rbac:groups="gateway.networking.k8s.io",resources=gatewayclasses/status;gateways/status;httproutes/status;tlsroutes/status,verbs=update // +kubebuilder:rbac:groups="",resources=secrets;endpoints;services;namespaces,verbs=get;list;watch diff --git a/internal/provisioner/objects/rbac/clusterrole/cluster_role.go b/internal/provisioner/objects/rbac/clusterrole/cluster_role.go index a9bd59660ff..5e17d594457 100644 --- a/internal/provisioner/objects/rbac/clusterrole/cluster_role.go +++ b/internal/provisioner/objects/rbac/clusterrole/cluster_role.go @@ -86,8 +86,8 @@ func desiredClusterRole(name string, contour *model.Contour) *rbacv1.ClusterRole policyRuleFor(corev1.GroupName, getListWatch, "secrets", "endpoints", "services", "namespaces"), // Gateway API resources. - // Note, ReferencePolicy does not currently have a .status field so it's omitted from the status rule. - policyRuleFor(gatewayv1alpha2.GroupName, getListWatch, "gatewayclasses", "gateways", "httproutes", "tlsroutes", "referencepolicies"), + // Note, ReferencePolicy/ReferenceGrant does not currently have a .status field so it's omitted from the status rule. + policyRuleFor(gatewayv1alpha2.GroupName, getListWatch, "gatewayclasses", "gateways", "httproutes", "tlsroutes", "referencepolicies", "referencegrants"), policyRuleFor(gatewayv1alpha2.GroupName, update, "gatewayclasses/status", "gateways/status", "httproutes/status", "tlsroutes/status"), // Ingress resources. diff --git a/site/content/resources/security-threat-model.md b/site/content/resources/security-threat-model.md index 7a5d2d71a16..f8446691022 100644 --- a/site/content/resources/security-threat-model.md +++ b/site/content/resources/security-threat-model.md @@ -54,7 +54,7 @@ Contour is unable to do much about this, and we expect administrators the use th #### Multitenancy Contour is designed to be used in a multitenant fashion - it's an expected use case that a Contour install would service teams that have completely different security contexts, and should not be able to access each others config. Contour mitigates this as far as we can, using our HTTPProxy and TSLCertificateDelegation CRDs to enable more-secure cross-namespace references. -The ReferencePolicy object in the Gateway API is also based on this idea, that cross-namespace references are only valid when the *owner of the namespace* accepts them. +The ReferenceGrant object in the Gateway API is also based on this idea, that cross-namespace references are only valid when the *owner of the namespace* accepts them. #### Insider access In general, Contour adheres to the Kubernetes security model, that makes the minimum size security boundary the namespace (or at least, the RBAC around objects in that namespace). For Contour's primary use cases to work, application developers and other ingress configuration owners *must* have access to create or modify ingress config objects (whether they are Ingress, HTTPProxy, or Gateway API) inside their own namespace. @@ -98,4 +98,4 @@ The Contour team works hard to understand the project's security context and kee The team hopes that this examination of our security model provides some insight into both how we develop Contour and what administrators should be thinking about to run Contour in as secure way as possible. We aim for secure-by-default as far as possible, and where we do have to allow risks, will document them here. -[1]: /resources/security-process \ No newline at end of file +[1]: /resources/security-process