From 245cb2bb4812e58cfe3c514472d42787ea14e842 Mon Sep 17 00:00:00 2001 From: Kate Osborn Date: Thu, 22 Jun 2023 12:28:25 -0600 Subject: [PATCH 01/10] Add support for ReferenceGrants for Gateway/Secret --- conformance/Makefile | 3 +- deploy/manifests/rbac.yaml | 1 + docs/gateway-api-compatibility.md | 15 +- examples/https-termination/README.md | 84 +++++++- examples/https-termination/cafe-secret.yaml | 6 + examples/https-termination/gateway.yaml | 2 +- .../https-termination/reference-grant.yaml | 14 ++ internal/events/handler.go | 4 + internal/manager/manager.go | 4 + internal/manager/manager_test.go | 2 + internal/state/change_processor.go | 20 +- internal/state/change_processor_test.go | 111 ++++++++-- internal/state/conditions/conditions.go | 20 ++ internal/state/graph/gateway.go | 9 +- internal/state/graph/gateway_listener.go | 50 +++-- internal/state/graph/gateway_listener_test.go | 36 +--- internal/state/graph/gateway_test.go | 114 ++++++++-- internal/state/graph/graph.go | 13 +- internal/state/graph/reference_grant.go | 64 ++++++ internal/state/graph/reference_grant_test.go | 201 ++++++++++++++++++ 20 files changed, 674 insertions(+), 99 deletions(-) create mode 100644 examples/https-termination/reference-grant.yaml create mode 100644 internal/state/graph/reference_grant.go create mode 100644 internal/state/graph/reference_grant_test.go diff --git a/conformance/Makefile b/conformance/Makefile index 8a2963b5a3..ea3a0e98a4 100644 --- a/conformance/Makefile +++ b/conformance/Makefile @@ -2,7 +2,6 @@ NKG_TAG = edge NKG_PREFIX = nginx-kubernetes-gateway GATEWAY_CLASS = nginx SUPPORTED_FEATURES = HTTPRoute,HTTPRouteQueryParamMatching,HTTPRouteMethodMatching,HTTPRoutePortRedirect,HTTPRouteSchemeRedirect -EXEMPT_FEATURES = ReferenceGrant KIND_KUBE_CONFIG_FOLDER = $${HOME}/.kube/kind TAG = latest PREFIX = conformance-test-runner @@ -62,7 +61,7 @@ run-conformance-tests: ## Run conformance tests --image=$(PREFIX):$(TAG) --image-pull-policy=Never \ --overrides='{ "spec": { "serviceAccountName": "conformance" } }' \ --restart=Never -- go test -v . -tags conformance -args --gateway-class=$(GATEWAY_CLASS) --debug \ - --supported-features=$(SUPPORTED_FEATURES) --exempt-features=$(EXEMPT_FEATURES) + --supported-features=$(SUPPORTED_FEATURES) .PHONY: cleanup-conformance-tests cleanup-conformance-tests: ## Clean up conformance tests fixtures diff --git a/deploy/manifests/rbac.yaml b/deploy/manifests/rbac.yaml index f014f5fa2f..c60f0145b4 100644 --- a/deploy/manifests/rbac.yaml +++ b/deploy/manifests/rbac.yaml @@ -38,6 +38,7 @@ rules: - gatewayclasses - gateways - httproutes + - referencegrants verbs: - list - watch diff --git a/docs/gateway-api-compatibility.md b/docs/gateway-api-compatibility.md index 72a580910f..f01b091fd4 100644 --- a/docs/gateway-api-compatibility.md +++ b/docs/gateway-api-compatibility.md @@ -148,7 +148,20 @@ Fields: ### ReferenceGrant -> Status: Not supported. +> Status: Partially supported. + +NKG only supports ReferenceGrants that permit Gateways to reference Secrets. + +Fields: +* `spec` + * `to` + * `group` - supported. + * `kind` - partially supported. Only `Secret`. + * `name`- supported. + * `from` + * `group` - supported. + * `kind` - partially supported. Only `Gateway`. + * `namespace`- supported. ### Custom Policies diff --git a/examples/https-termination/README.md b/examples/https-termination/README.md index 085192176d..b2da0a19d1 100644 --- a/examples/https-termination/README.md +++ b/examples/https-termination/README.md @@ -1,6 +1,8 @@ # HTTPS Termination Example -In this example, we expand on the simple [cafe-example](../cafe-example) by adding HTTPS termination to our routes and an HTTPS redirect from port 80 to 443. +In this example, we expand on the simple [cafe-example](../cafe-example) by adding HTTPS termination to our routes and +an HTTPS redirect from port 80 to 443. We will also show how you can use a ReferenceGrant to permit your Gateway to +reference a Secret in a different Namespace. ## Running the Example @@ -40,13 +42,21 @@ In this example, we expand on the simple [cafe-example](../cafe-example) by addi ## 3. Configure HTTPS Termination and Routing -1. Create a Secret with a TLS certificate and key: +1. Create the Namespace `certificate` and a Secret with a TLS certificate and key: ``` kubectl apply -f cafe-secret.yaml ``` The TLS certificate and key in this Secret are used to terminate the TLS connections for the cafe application. - **Important**: This certificate and key are for demo purposes only. + > **Important**: This certificate and key are for demo purposes only. + +1. Create the `ReferenceGrant`: + ``` + kubectl apply -f reference-grant.yaml + ``` + + This ReferenceGrant allows all Gateways in the `default` namespace to references the `cafe-secret` Secret in + the `certificate` namespace. 1. Create the `Gateway` resource: ``` @@ -54,15 +64,18 @@ In this example, we expand on the simple [cafe-example](../cafe-example) by addi ``` This [Gateway](./gateway.yaml) configures: - * `http` listener for HTTP traffic - * `https` listener for HTTPS traffic. It terminates TLS connections using the `cafe-secret` we created in step 1. + * `http` listener for HTTP traffic + * `https` listener for HTTPS traffic. It terminates TLS connections using the `cafe-secret` we created in step 1. 1. Create the `HTTPRoute` resources: ``` kubectl apply -f cafe-routes.yaml ``` - To configure HTTPS termination for our cafe application, we will bind our `coffee` and `tea` HTTPRoutes to the `https` listener in [cafe-routes.yaml](./cafe-routes.yaml) using the [`parentReference`](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.ParentReference) field: + To configure HTTPS termination for our cafe application, we will bind our `coffee` and `tea` HTTPRoutes to + the `https` listener in [cafe-routes.yaml](./cafe-routes.yaml) using + the [`parentReference`](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.ParentReference) + field: ```yaml parentRefs: @@ -70,7 +83,9 @@ In this example, we expand on the simple [cafe-example](../cafe-example) by addi sectionName: https ``` - To configure an HTTPS redirect from port 80 to 443, we will bind the special `cafe-tls-redirect` HTTPRoute with a [`HTTPRequestRedirectFilter`](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRequestRedirectFilter) to the `http` listener: + To configure an HTTPS redirect from port 80 to 443, we will bind the special `cafe-tls-redirect` HTTPRoute with + a [`HTTPRequestRedirectFilter`](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRequestRedirectFilter) + to the `http` listener: ```yaml parentRefs: @@ -80,13 +95,16 @@ In this example, we expand on the simple [cafe-example](../cafe-example) by addi ## 4. Test the Application -To access the application, we will use `curl` to send requests to the `coffee` and `tea` Services. First, we will access the application over HTTP to test that the HTTPS redirect works. Then we will use HTTPS. +To access the application, we will use `curl` to send requests to the `coffee` and `tea` Services. First, we will access +the application over HTTP to test that the HTTPS redirect works. Then we will use HTTPS. ### 4.1 Test HTTPS Redirect -To test that NGINX sends an HTTPS redirect, we will send requests to the `coffee` and `tea` Services on HTTP port. We will use curl's `--include` option to print the response headers (we are interested in the `Location` header). +To test that NGINX sends an HTTPS redirect, we will send requests to the `coffee` and `tea` Services on HTTP port. We +will use curl's `--include` option to print the response headers (we are interested in the `Location` header). To get a redirect for coffee: + ``` curl --resolve cafe.example.com:$GW_HTTP_PORT:$GW_IP http://cafe.example.com:$GW_HTTP_PORT/coffee --include HTTP/1.1 302 Moved Temporarily @@ -96,6 +114,7 @@ Location: https://cafe.example.com:443/coffee ``` To get a redirect for tea: + ``` curl --resolve cafe.example.com:$GW_HTTP_PORT:$GW_IP http://cafe.example.com:$GW_HTTP_PORT/tea --include HTTP/1.1 302 Moved Temporarily @@ -104,9 +123,10 @@ Location: https://cafe.example.com:443/tea ... ``` -### 4.2 Access Coffee and Tea +### 4.2 Access Coffee and Tea -Now we will access the application over HTTPS. Since our certificate is self-signed, we will use curl's `--insecure` option to turn off certificate verification. +Now we will access the application over HTTPS. Since our certificate is self-signed, we will use curl's `--insecure` +option to turn off certificate verification. To get coffee: @@ -123,3 +143,45 @@ curl --resolve cafe.example.com:$GW_HTTPS_PORT:$GW_IP https://cafe.example.com:$ Server address: 10.12.0.19:80 Server name: tea-7cd44fcb4d-xfw2x ``` + +### 4.3 Remove the ReferenceGrant + +To restrict access to the `cafe-secret` in the `certificate` Namespace, we can delete the ReferenceGrant we created in +Step 3: + +``` +kubectl delete -f reference-grant.yaml +``` + +Now, if we try to access the application over HTTPS, we will get a connection refused error: +``` +curl --resolve cafe.example.com:$GW_HTTPS_PORT:$GW_IP https://cafe.example.com:$GW_HTTPS_PORT/coffee --insecure -vvv +... +curl: (7) Failed to connect to cafe.example.com port 443 after 0 ms: Connection refused +``` + + +You can also check the conditions of the Gateway `https` Listener to verify the that the reference is not permitted: + +``` + Name: https + Conditions: + Last Transition Time: 2023-06-26T20:23:56Z + Message: Certificate ref to secret certificate/cafe-secret not permitted by any ReferenceGrant + Observed Generation: 2 + Reason: RefNotPermitted + Status: False + Type: Accepted + Last Transition Time: 2023-06-26T20:23:56Z + Message: Certificate ref to secret certificate/cafe-secret not permitted by any ReferenceGrant + Observed Generation: 2 + Reason: RefNotPermitted + Status: False + Type: ResolvedRefs + Last Transition Time: 2023-06-26T20:23:56Z + Message: Certificate ref to secret certificate/cafe-secret not permitted by any ReferenceGrant + Observed Generation: 2 + Reason: Invalid + Status: False + Type: Programmed +``` diff --git a/examples/https-termination/cafe-secret.yaml b/examples/https-termination/cafe-secret.yaml index 4510460bba..d4037e2d67 100644 --- a/examples/https-termination/cafe-secret.yaml +++ b/examples/https-termination/cafe-secret.yaml @@ -1,7 +1,13 @@ apiVersion: v1 +kind: Namespace +metadata: + name: certificate +--- +apiVersion: v1 kind: Secret metadata: name: cafe-secret + namespace: certificate type: kubernetes.io/tls data: tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNzakNDQVpvQ0NRQzdCdVdXdWRtRkNEQU5CZ2txaGtpRzl3MEJBUXNGQURBYk1Sa3dGd1lEVlFRRERCQmoKWVdabExtVjRZVzF3YkdVdVkyOXRNQjRYRFRJeU1EY3hOREl4TlRJek9Wb1hEVEl6TURjeE5ESXhOVEl6T1ZvdwpHekVaTUJjR0ExVUVBd3dRWTJGbVpTNWxlR0Z0Y0d4bExtTnZiVENDQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFECmdnRVBBRENDQVFvQ2dnRUJBTHFZMnRHNFc5aStFYzJhdnV4Q2prb2tnUUx1ek10U1Rnc1RNaEhuK3ZRUmxIam8KVzFLRnMvQVdlS25UUStyTWVKVWNseis4M3QwRGtyRThwUisxR2NKSE50WlNMb0NEYUlRN0Nhck5nY1daS0o4Qgo1WDNnVS9YeVJHZjI2c1REd2xzU3NkSEQ1U2U3K2Vab3NPcTdHTVF3K25HR2NVZ0VtL1Q1UEMvY05PWE0zZWxGClRPL051MStoMzROVG9BbDNQdTF2QlpMcDNQVERtQ0thaEROV0NWbUJQUWpNNFI4VERsbFhhMHQ5Z1o1MTRSRzUKWHlZWTNtdzZpUzIrR1dYVXllMjFuWVV4UEhZbDV4RHY0c0FXaGRXbElweHlZQlNCRURjczN6QlI2bFF1OWkxZAp0R1k4dGJ3blVmcUVUR3NZdWxzc05qcU95V1VEcFdJelhibHhJZVVDQXdFQUFUQU5CZ2txaGtpRzl3MEJBUXNGCkFBT0NBUUVBcjkrZWJ0U1dzSnhLTGtLZlRkek1ISFhOd2Y5ZXFVbHNtTXZmMGdBdWVKTUpUR215dG1iWjlpbXQKL2RnWlpYVE9hTElHUG9oZ3BpS0l5eVVRZVdGQ2F0NHRxWkNPVWRhbUloOGk0Q1h6QVJYVHNvcUNOenNNLzZMRQphM25XbFZyS2lmZHYrWkxyRi8vblc0VVNvOEoxaCtQeDljY0tpRDZZU0RVUERDRGh1RUtFWXcvbHpoUDJVOXNmCnl6cEJKVGQ4enFyM3paTjNGWWlITmgzYlRhQS82di9jU2lyamNTK1EwQXg4RWpzQzYxRjRVMTc4QzdWNWRCKzQKcmtPTy9QNlA0UFlWNTRZZHMvRjE2WkZJTHFBNENCYnExRExuYWRxamxyN3NPbzl2ZzNnWFNMYXBVVkdtZ2todAp6VlZPWG1mU0Z4OS90MDBHUi95bUdPbERJbWlXMGc9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== diff --git a/examples/https-termination/gateway.yaml b/examples/https-termination/gateway.yaml index 75861a43ae..51b0758a6e 100644 --- a/examples/https-termination/gateway.yaml +++ b/examples/https-termination/gateway.yaml @@ -18,4 +18,4 @@ spec: certificateRefs: - kind: Secret name: cafe-secret - namespace: default + namespace: certificate diff --git a/examples/https-termination/reference-grant.yaml b/examples/https-termination/reference-grant.yaml new file mode 100644 index 0000000000..0299cbd6db --- /dev/null +++ b/examples/https-termination/reference-grant.yaml @@ -0,0 +1,14 @@ +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: ReferenceGrant +metadata: + name: allow-default-to-cafe-secret + namespace: certificate +spec: + to: + - group: core + kind: Secret + name: cafe-secret # if you omit this name, then Gateways in default ns can access all Secrets in the certificate ns + from: + - group: gateway.networking.k8s.io + kind: Gateway + namespace: default diff --git a/internal/events/handler.go b/internal/events/handler.go index d7e5e26d34..f324d09658 100644 --- a/internal/events/handler.go +++ b/internal/events/handler.go @@ -126,6 +126,8 @@ func (h *EventHandlerImpl) propagateUpsert(e *UpsertEvent) { h.cfg.Processor.CaptureUpsertChange(r) case *v1beta1.HTTPRoute: h.cfg.Processor.CaptureUpsertChange(r) + case *v1beta1.ReferenceGrant: + h.cfg.Processor.CaptureUpsertChange(r) case *apiv1.Service: h.cfg.Processor.CaptureUpsertChange(r) case *apiv1.Namespace: @@ -149,6 +151,8 @@ func (h *EventHandlerImpl) propagateDelete(e *DeleteEvent) { h.cfg.Processor.CaptureDeleteChange(e.Type, e.NamespacedName) case *v1beta1.HTTPRoute: h.cfg.Processor.CaptureDeleteChange(e.Type, e.NamespacedName) + case *v1beta1.ReferenceGrant: + h.cfg.Processor.CaptureDeleteChange(e.Type, e.NamespacedName) case *apiv1.Service: h.cfg.Processor.CaptureDeleteChange(e.Type, e.NamespacedName) case *apiv1.Namespace: diff --git a/internal/manager/manager.go b/internal/manager/manager.go index e777fea353..864dcfe36a 100644 --- a/internal/manager/manager.go +++ b/internal/manager/manager.go @@ -121,6 +121,9 @@ func Start(cfg config.Config) error { controller.WithK8sPredicate(k8spredicate.LabelChangedPredicate{}), }, }, + { + objectType: &gatewayv1beta1.ReferenceGrant{}, + }, } ctx := ctlr.SetupSignalHandler() @@ -207,6 +210,7 @@ func prepareFirstEventBatchPreparerArgs( &apiv1.NamespaceList{}, &discoveryV1.EndpointSliceList{}, &gatewayv1beta1.HTTPRouteList{}, + &gatewayv1beta1.ReferenceGrantList{}, } if gwNsName == nil { diff --git a/internal/manager/manager_test.go b/internal/manager/manager_test.go index 9166962ae2..71554bc5fe 100644 --- a/internal/manager/manager_test.go +++ b/internal/manager/manager_test.go @@ -34,6 +34,7 @@ func TestPrepareFirstEventBatchPreparerArgs(t *testing.T) { &discoveryV1.EndpointSliceList{}, &gatewayv1beta1.HTTPRouteList{}, &gatewayv1beta1.GatewayList{}, + &gatewayv1beta1.ReferenceGrantList{}, }, }, { @@ -52,6 +53,7 @@ func TestPrepareFirstEventBatchPreparerArgs(t *testing.T) { &apiv1.NamespaceList{}, &discoveryV1.EndpointSliceList{}, &gatewayv1beta1.HTTPRouteList{}, + &gatewayv1beta1.ReferenceGrantList{}, }, }, } diff --git a/internal/state/change_processor.go b/internal/state/change_processor.go index 72f6ef9ec2..72715d1320 100644 --- a/internal/state/change_processor.go +++ b/internal/state/change_processor.go @@ -85,11 +85,12 @@ type ChangeProcessorImpl struct { // NewChangeProcessorImpl creates a new ChangeProcessorImpl for the Gateway resource with the configured namespace name. func NewChangeProcessorImpl(cfg ChangeProcessorConfig) *ChangeProcessorImpl { clusterStore := graph.ClusterState{ - GatewayClasses: make(map[types.NamespacedName]*v1beta1.GatewayClass), - Gateways: make(map[types.NamespacedName]*v1beta1.Gateway), - HTTPRoutes: make(map[types.NamespacedName]*v1beta1.HTTPRoute), - Services: make(map[types.NamespacedName]*apiv1.Service), - Namespaces: make(map[types.NamespacedName]*apiv1.Namespace), + GatewayClasses: make(map[types.NamespacedName]*v1beta1.GatewayClass), + Gateways: make(map[types.NamespacedName]*v1beta1.Gateway), + HTTPRoutes: make(map[types.NamespacedName]*v1beta1.HTTPRoute), + Services: make(map[types.NamespacedName]*apiv1.Service), + Namespaces: make(map[types.NamespacedName]*apiv1.Namespace), + ReferenceGrants: make(map[types.NamespacedName]*v1beta1.ReferenceGrant), } extractGVK := func(obj client.Object) schema.GroupVersionKind { @@ -119,6 +120,11 @@ func NewChangeProcessorImpl(cfg ChangeProcessorConfig) *ChangeProcessorImpl { store: newObjectStoreMapAdapter(clusterStore.HTTPRoutes), trackUpsertDelete: true, }, + { + gvk: extractGVK(&v1beta1.ReferenceGrant{}), + store: newObjectStoreMapAdapter(clusterStore.ReferenceGrants), + trackUpsertDelete: true, + }, { gvk: extractGVK(&apiv1.Namespace{}), store: newObjectStoreMapAdapter(clusterStore.Namespaces), @@ -145,8 +151,8 @@ func NewChangeProcessorImpl(cfg ChangeProcessorConfig) *ChangeProcessorImpl { var err error switch o := obj.(type) { - // We don't validate GatewayClass, because as of 0.7.1, the webhook doesn't validate it (it only - // validates an update that requires the previous version of the resource, + // We don't validate GatewayClass or ReferenceGrant, because as of 0.7.1, the webhook doesn't validate them. + // It only validates a GatewayClass update that requires the previous version of the resource, // which NKG cannot reliably provide - for example, after NKG restarts). // https://github.com/kubernetes-sigs/gateway-api/blob/v0.7.1/apis/v1beta1/validation/gatewayclass.go#L28 case *v1beta1.Gateway: diff --git a/internal/state/change_processor_test.go b/internal/state/change_processor_test.go index 9b8c83bd3e..c8beaf3342 100644 --- a/internal/state/change_processor_test.go +++ b/internal/state/change_processor_test.go @@ -106,7 +106,7 @@ func createGateway(name string) *v1beta1.Gateway { } } -func createGatewayWithTLSListener(name string) *v1beta1.Gateway { +func createGatewayWithTLSListener(name, certNs string) *v1beta1.Gateway { gw := createGateway(name) l := v1beta1.Listener{ @@ -120,7 +120,7 @@ func createGatewayWithTLSListener(name string) *v1beta1.Gateway { { Kind: (*v1beta1.Kind)(helpers.GetPointer("Secret")), Name: "secret", - Namespace: (*v1beta1.Namespace)(helpers.GetPointer("test")), + Namespace: (*v1beta1.Namespace)(helpers.GetPointer(certNs)), }, }, }, @@ -228,6 +228,7 @@ var _ = Describe("ChangeProcessor", func() { gcUpdated *v1beta1.GatewayClass hr1, hr1Updated, hr2 *v1beta1.HTTPRoute gw1, gw1Updated, gw2 *v1beta1.Gateway + refGrant *v1beta1.ReferenceGrant expGraph *graph.Graph expRouteHR1, expRouteHR2 *graph.Route ) @@ -242,12 +243,33 @@ var _ = Describe("ChangeProcessor", func() { hr2 = createRoute("hr-2", "gateway-2", "bar.example.com") - gw1 = createGatewayWithTLSListener("gateway-1") + gw1 = createGatewayWithTLSListener("gateway-1", "cert-ns") // cert in diff namespace than gw + refGrant = &v1beta1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "cert-ns", + Name: "ref-grant", + }, + Spec: v1beta1.ReferenceGrantSpec{ + From: []v1beta1.ReferenceGrantFrom{ + { + Group: v1beta1.GroupName, + Kind: "Gateway", + Namespace: "test", + }, + }, + To: []v1beta1.ReferenceGrantTo{ + { + Group: "core", + Kind: "Secret", + }, + }, + }, + } gw1Updated = gw1.DeepCopy() gw1Updated.Generation++ - gw2 = createGatewayWithTLSListener("gateway-2") + gw2 = createGatewayWithTLSListener("gateway-2", "test") }) BeforeEach(func() { expRouteHR1 = &graph.Route{ @@ -380,6 +402,36 @@ var _ = Describe("ChangeProcessor", func() { It("returns updated graph", func() { processor.CaptureUpsertChange(gc) + // no ref grant exists yet for gw1 + expGraph.Gateway.Listeners["listener-443-1"] = &graph.Listener{ + Source: gw1.Spec.Listeners[1], + Valid: false, + Routes: map[types.NamespacedName]*graph.Route{}, + Conditions: conditions.NewListenerRefNotPermitted( + "Certificate ref to secret cert-ns/secret not permitted by any ReferenceGrant", + ), + } + + hr1Name := types.NamespacedName{Namespace: hr1.Namespace, Name: hr1.Name} + + expAttachment := &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{}, + FailedCondition: conditions.NewRouteInvalidListener(), + Attached: false, + } + + expGraph.Gateway.Listeners["listener-80-1"].Routes[hr1Name].ParentRefs[1].Attachment = expAttachment + expGraph.Routes[hr1Name].ParentRefs[1].Attachment = expAttachment + + changed, graphCfg := processor.Process() + Expect(changed).To(BeTrue()) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + }) + }) + When("the ReferenceGrant allowing the Gateway to reference its Secret is upserted", func() { + It("returns updated graph", func() { + processor.CaptureUpsertChange(refGrant) + changed, graphCfg := processor.Process() Expect(changed).To(BeTrue()) Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) @@ -511,6 +563,8 @@ var _ = Describe("ChangeProcessor", func() { hr1Name := types.NamespacedName{Namespace: "test", Name: "hr-1"} hr2Name := types.NamespacedName{Namespace: "test", Name: "hr-2"} expGraph.Gateway.Source = gw2 + expGraph.Gateway.Listeners["listener-80-1"].Source = gw2.Spec.Listeners[0] + expGraph.Gateway.Listeners["listener-443-1"].Source = gw2.Spec.Listeners[1] delete(expGraph.Gateway.Listeners["listener-80-1"].Routes, hr1Name) delete(expGraph.Gateway.Listeners["listener-443-1"].Routes, hr1Name) expGraph.Gateway.Listeners["listener-80-1"].Routes[hr2Name] = expRouteHR2 @@ -535,6 +589,8 @@ var _ = Describe("ChangeProcessor", func() { // no routes remain hr1Name := types.NamespacedName{Namespace: "test", Name: "hr-1"} expGraph.Gateway.Source = gw2 + expGraph.Gateway.Listeners["listener-80-1"].Source = gw2.Spec.Listeners[0] + expGraph.Gateway.Listeners["listener-443-1"].Source = gw2.Spec.Listeners[1] delete(expGraph.Gateway.Listeners["listener-80-1"].Routes, hr1Name) delete(expGraph.Gateway.Listeners["listener-443-1"].Routes, hr1Name) expGraph.Routes = map[types.NamespacedName]*graph.Route{} @@ -979,15 +1035,16 @@ var _ = Describe("ChangeProcessor", func() { // -- this is done in 'Normal cases of processing changes' var ( - processor *state.ChangeProcessorImpl - fakeRelationshipCapturer *relationshipfakes.FakeCapturer - gcNsName, gwNsName, hrNsName, hr2NsName types.NamespacedName - svcNsName, sliceNsName types.NamespacedName - gc, gcUpdated *v1beta1.GatewayClass - gw1, gw1Updated, gw2 *v1beta1.Gateway - hr1, hr1Updated, hr2 *v1beta1.HTTPRoute - svc *apiv1.Service - slice *discoveryV1.EndpointSlice + processor *state.ChangeProcessorImpl + fakeRelationshipCapturer *relationshipfakes.FakeCapturer + gcNsName, gwNsName, hrNsName, hr2NsName, rgNsName types.NamespacedName + svcNsName, sliceNsName types.NamespacedName + gc, gcUpdated *v1beta1.GatewayClass + gw1, gw1Updated, gw2 *v1beta1.Gateway + hr1, hr1Updated, hr2 *v1beta1.HTTPRoute + rg1, rg1Updated, rg2 *v1beta1.ReferenceGrant + svc *apiv1.Service + slice *discoveryV1.EndpointSlice ) BeforeEach(OncePerOrdered, func() { @@ -1066,6 +1123,21 @@ var _ = Describe("ChangeProcessor", func() { Name: sliceNsName.Name, }, } + + rgNsName = types.NamespacedName{Namespace: "test", Name: "rg-1"} + + rg1 = &v1beta1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Name: rgNsName.Name, + Namespace: rgNsName.Namespace, + }, + } + + rg1Updated = rg1.DeepCopy() + rg1Updated.Generation++ + + rg2 = rg1.DeepCopy() + rg2.Name = "rg-2" }) // Changing change - a change that makes processor.Process() report changed // Non-changing change - a change that doesn't do that @@ -1079,6 +1151,7 @@ var _ = Describe("ChangeProcessor", func() { processor.CaptureUpsertChange(gc) processor.CaptureUpsertChange(gw1) processor.CaptureUpsertChange(hr1) + processor.CaptureUpsertChange(rg1) changed, _ := processor.Process() Expect(changed).To(BeTrue()) @@ -1087,6 +1160,7 @@ var _ = Describe("ChangeProcessor", func() { processor.CaptureUpsertChange(gc) processor.CaptureUpsertChange(gw1) processor.CaptureUpsertChange(hr1) + processor.CaptureUpsertChange(rg1) changed, _ := processor.Process() Expect(changed).To(BeFalse()) @@ -1097,11 +1171,13 @@ var _ = Describe("ChangeProcessor", func() { processor.CaptureUpsertChange(gcUpdated) processor.CaptureUpsertChange(gw1Updated) processor.CaptureUpsertChange(hr1Updated) + processor.CaptureUpsertChange(rg1Updated) // there are non-changing changes processor.CaptureUpsertChange(gcUpdated) processor.CaptureUpsertChange(gw1Updated) processor.CaptureUpsertChange(hr1Updated) + processor.CaptureUpsertChange(rg1Updated) changed, _ := processor.Process() Expect(changed).To(BeTrue()) @@ -1111,6 +1187,7 @@ var _ = Describe("ChangeProcessor", func() { // we can't have a second GatewayClass, so we don't add it processor.CaptureUpsertChange(gw2) processor.CaptureUpsertChange(hr2) + processor.CaptureUpsertChange(rg2) changed, _ := processor.Process() Expect(changed).To(BeTrue()) @@ -1121,10 +1198,12 @@ var _ = Describe("ChangeProcessor", func() { processor.CaptureDeleteChange(&v1beta1.GatewayClass{}, gcNsName) processor.CaptureDeleteChange(&v1beta1.Gateway{}, gwNsName) processor.CaptureDeleteChange(&v1beta1.HTTPRoute{}, hrNsName) + processor.CaptureDeleteChange(&v1beta1.ReferenceGrant{}, rgNsName) // these are non-changing changes processor.CaptureUpsertChange(gw2) processor.CaptureUpsertChange(hr2) + processor.CaptureUpsertChange(rg2) changed, _ := processor.Process() Expect(changed).To(BeTrue()) @@ -1143,6 +1222,7 @@ var _ = Describe("ChangeProcessor", func() { processor.CaptureDeleteChange(&v1beta1.Gateway{}, gwNsName) processor.CaptureDeleteChange(&v1beta1.HTTPRoute{}, hrNsName) processor.CaptureDeleteChange(&v1beta1.HTTPRoute{}, hr2NsName) + processor.CaptureDeleteChange(&v1beta1.ReferenceGrant{}, rgNsName) changed, _ := processor.Process() Expect(changed).To(BeFalse()) @@ -1206,6 +1286,7 @@ var _ = Describe("ChangeProcessor", func() { processor.CaptureUpsertChange(gc) processor.CaptureUpsertChange(gw1) processor.CaptureUpsertChange(hr1) + processor.CaptureUpsertChange(rg1) // related Kubernetes API resources fakeRelationshipCapturer.ExistsReturns(true) @@ -1222,6 +1303,7 @@ var _ = Describe("ChangeProcessor", func() { processor.CaptureUpsertChange(gc) processor.CaptureUpsertChange(gw1) processor.CaptureUpsertChange(hr1) + processor.CaptureUpsertChange(rg1) // unrelated Kubernetes API resources fakeRelationshipCapturer.ExistsReturns(false) @@ -1244,6 +1326,7 @@ var _ = Describe("ChangeProcessor", func() { processor.CaptureUpsertChange(gc) processor.CaptureUpsertChange(gw1) processor.CaptureUpsertChange(hr1) + processor.CaptureUpsertChange(rg1) changed, _ := processor.Process() Expect(changed).To(BeTrue()) @@ -1257,6 +1340,7 @@ var _ = Describe("ChangeProcessor", func() { processor.CaptureUpsertChange(gcUpdated) processor.CaptureUpsertChange(gw1Updated) processor.CaptureUpsertChange(hr1Updated) + processor.CaptureUpsertChange(rg1Updated) // these are non-changing changes processor.CaptureUpsertChange(svc) @@ -1279,6 +1363,7 @@ var _ = Describe("ChangeProcessor", func() { processor.CaptureUpsertChange(gcUpdated) processor.CaptureUpsertChange(gw1Updated) processor.CaptureUpsertChange(hr1Updated) + processor.CaptureUpsertChange(rg1Updated) changed, _ := processor.Process() Expect(changed).To(BeTrue()) diff --git a/internal/state/conditions/conditions.go b/internal/state/conditions/conditions.go index c5ce697f88..1dc04c95de 100644 --- a/internal/state/conditions/conditions.go +++ b/internal/state/conditions/conditions.go @@ -381,6 +381,26 @@ func NewListenerUnsupportedProtocol(msg string) []Condition { } } +// NewListenerRefNotPermitted returns Conditions that indicates that the Listener references a TLS secret that is not +// permitted by a ReferenceGrant. +func NewListenerRefNotPermitted(msg string) []Condition { + return []Condition{ + { + Type: string(v1beta1.ListenerConditionAccepted), + Status: metav1.ConditionFalse, + Reason: string(v1beta1.ListenerReasonRefNotPermitted), + Message: msg, + }, + { + Type: string(v1beta1.ListenerReasonResolvedRefs), + Status: metav1.ConditionFalse, + Reason: string(v1beta1.ListenerReasonRefNotPermitted), + Message: msg, + }, + NewListenerNotProgrammedInvalid(msg), + } +} + // NewDefaultGatewayClassConditions returns the default Conditions that must be present in the status of a GatewayClass. func NewDefaultGatewayClassConditions() []Condition { return []Condition{ diff --git a/internal/state/graph/gateway.go b/internal/state/graph/gateway.go index 07675b1605..26e60077f0 100644 --- a/internal/state/graph/gateway.go +++ b/internal/state/graph/gateway.go @@ -90,7 +90,12 @@ func processGateways( } } -func buildGateway(gw *v1beta1.Gateway, secretMemoryMgr secrets.SecretDiskMemoryManager, gc *GatewayClass) *Gateway { +func buildGateway( + gw *v1beta1.Gateway, + secretMemoryMgr secrets.SecretDiskMemoryManager, + gc *GatewayClass, + refGrants map[types.NamespacedName]*v1beta1.ReferenceGrant, +) *Gateway { if gw == nil { return nil } @@ -107,7 +112,7 @@ func buildGateway(gw *v1beta1.Gateway, secretMemoryMgr secrets.SecretDiskMemoryM return &Gateway{ Source: gw, - Listeners: buildListeners(gw, secretMemoryMgr), + Listeners: buildListeners(gw, secretMemoryMgr, refGrants), Valid: true, } } diff --git a/internal/state/graph/gateway_listener.go b/internal/state/graph/gateway_listener.go index c2a6a400b6..f743625e97 100644 --- a/internal/state/graph/gateway_listener.go +++ b/internal/state/graph/gateway_listener.go @@ -36,10 +36,11 @@ type Listener struct { func buildListeners( gw *v1beta1.Gateway, secretMemoryMgr secrets.SecretDiskMemoryManager, + refGrants map[types.NamespacedName]*v1beta1.ReferenceGrant, ) map[string]*Listener { listeners := make(map[string]*Listener) - listenerFactory := newListenerConfiguratorFactory(gw, secretMemoryMgr) + listenerFactory := newListenerConfiguratorFactory(gw, secretMemoryMgr, refGrants) for _, gl := range gw.Spec.Listeners { configurator := listenerFactory.getConfiguratorForListener(gl) @@ -67,6 +68,7 @@ func (f *listenerConfiguratorFactory) getConfiguratorForListener(l v1beta1.Liste func newListenerConfiguratorFactory( gw *v1beta1.Gateway, secretMemoryMgr secrets.SecretDiskMemoryManager, + refGrants map[types.NamespacedName]*v1beta1.ReferenceGrant, ) *listenerConfiguratorFactory { sharedPortConflictResolver := createPortConflictResolver() @@ -99,13 +101,13 @@ func newListenerConfiguratorFactory( validateListenerAllowedRouteKind, validateListenerLabelSelector, validateListenerHostname, - createHTTPSListenerValidator(gw.Namespace), + createHTTPSListenerValidator(), }, conflictResolvers: []listenerConflictResolver{ sharedPortConflictResolver, }, externalReferenceResolvers: []listenerExternalReferenceResolver{ - createExternalReferencesForTLSSecretsResolver(gw.Namespace, secretMemoryMgr), + createExternalReferencesForTLSSecretsResolver(gw.Namespace, secretMemoryMgr, refGrants), }, }, } @@ -265,7 +267,7 @@ func validateListenerPort(port v1beta1.PortNumber) error { return nil } -func createHTTPSListenerValidator(gwNsName string) listenerValidator { +func createHTTPSListenerValidator() listenerValidator { return func(listener v1beta1.Listener) []conditions.Condition { var conds []conditions.Condition @@ -317,14 +319,6 @@ func createHTTPSListenerValidator(gwNsName string) listenerValidator { conds = append(conds, conditions.NewListenerInvalidCertificateRef(valErr.Error())...) } - // secret must be in the same namespace as the gateway - if certRef.Namespace != nil && string(*certRef.Namespace) != gwNsName { - const detail = "Referenced Secret must belong to the same namespace as the Gateway" - path := certRefPath.Child("namespace") - valErr := field.Invalid(path, certRef.Namespace, detail) - conds = append(conds, conditions.NewListenerInvalidCertificateRef(valErr.Error())...) - } - if l := len(listener.TLS.CertificateRefs); l > 1 { path := tlsPath.Child("certificateRefs") valErr := field.TooMany(path, l, 1) @@ -382,20 +376,42 @@ func createPortConflictResolver() listenerConflictResolver { func createExternalReferencesForTLSSecretsResolver( gwNs string, secretMemoryMgr secrets.SecretDiskMemoryManager, + refGrants map[types.NamespacedName]*v1beta1.ReferenceGrant, ) listenerExternalReferenceResolver { return func(l *Listener) { - nsname := types.NamespacedName{ - Namespace: gwNs, - Name: string(l.Source.TLS.CertificateRefs[0].Name), + certRef := l.Source.TLS.CertificateRefs[0] + + certRefNs := gwNs + if certRef.Namespace != nil { + certRefNs = string(*certRef.Namespace) + } + + certRefNsName := types.NamespacedName{ + Namespace: certRefNs, + Name: string(certRef.Name), + } + + if certRefNs != gwNs { + if !refGrantAllowsGatewayToSecret(refGrants, gwNs, certRefNsName) { + msg := fmt.Sprintf( + "Certificate ref to secret %s/%s not permitted by any ReferenceGrant", + certRefNs, + certRef.Name, + ) + + l.Conditions = append(l.Conditions, conditions.NewListenerRefNotPermitted(msg)...) + l.Valid = false + return + } } var err error - l.SecretPath, err = secretMemoryMgr.Request(nsname) + l.SecretPath, err = secretMemoryMgr.Request(certRefNsName) if err != nil { path := field.NewPath("tls", "certificateRefs").Index(0) // field.NotFound could be better, but it doesn't allow us to set the error message. - valErr := field.Invalid(path, nsname, err.Error()) + valErr := field.Invalid(path, certRefNsName, err.Error()) l.Conditions = append(l.Conditions, conditions.NewListenerInvalidCertificateRef(valErr.Error())...) l.Valid = false diff --git a/internal/state/graph/gateway_listener_test.go b/internal/state/graph/gateway_listener_test.go index ac79452f83..0472238a3c 100644 --- a/internal/state/graph/gateway_listener_test.go +++ b/internal/state/graph/gateway_listener_test.go @@ -45,31 +45,25 @@ func TestValidateHTTPListener(t *testing.T) { } func TestValidateHTTPSListener(t *testing.T) { - gwNs := "gateway-ns" + secretNs := "secret-ns" validSecretRef := v1beta1.SecretObjectReference{ Kind: (*v1beta1.Kind)(helpers.GetStringPointer("Secret")), Name: "secret", - Namespace: (*v1beta1.Namespace)(helpers.GetStringPointer(gwNs)), + Namespace: (*v1beta1.Namespace)(helpers.GetStringPointer(secretNs)), } invalidSecretRefGroup := v1beta1.SecretObjectReference{ Group: (*v1beta1.Group)(helpers.GetStringPointer("some-group")), Kind: (*v1beta1.Kind)(helpers.GetStringPointer("Secret")), Name: "secret", - Namespace: (*v1beta1.Namespace)(helpers.GetStringPointer(gwNs)), + Namespace: (*v1beta1.Namespace)(helpers.GetStringPointer(secretNs)), } invalidSecretRefKind := v1beta1.SecretObjectReference{ Kind: (*v1beta1.Kind)(helpers.GetStringPointer("ConfigMap")), Name: "secret", - Namespace: (*v1beta1.Namespace)(helpers.GetStringPointer(gwNs)), - } - - invalidSecretRefTNamespace := v1beta1.SecretObjectReference{ - Kind: (*v1beta1.Kind)(helpers.GetStringPointer("Secret")), - Name: "secret", - Namespace: (*v1beta1.Namespace)(helpers.GetStringPointer("diff-ns")), + Namespace: (*v1beta1.Namespace)(helpers.GetStringPointer(secretNs)), } tests := []struct { @@ -150,20 +144,6 @@ func TestValidateHTTPSListener(t *testing.T) { ), name: "invalid cert ref kind", }, - { - l: v1beta1.Listener{ - Port: 443, - TLS: &v1beta1.GatewayTLSConfig{ - Mode: helpers.GetTLSModePointer(v1beta1.TLSModeTerminate), - CertificateRefs: []v1beta1.SecretObjectReference{invalidSecretRefTNamespace}, - }, - }, - expected: conditions.NewListenerInvalidCertificateRef( - `tls.certificateRefs[0].namespace: Invalid value: "diff-ns": Referenced Secret must belong to ` + - `the same namespace as the Gateway`, - ), - name: "invalid cert ref namespace", - }, { l: v1beta1.Listener{ Port: 443, @@ -172,8 +152,10 @@ func TestValidateHTTPSListener(t *testing.T) { CertificateRefs: []v1beta1.SecretObjectReference{validSecretRef, validSecretRef}, }, }, - expected: conditions.NewListenerUnsupportedValue("tls.certificateRefs: Too many: 2: must have at most 1 items"), - name: "too many cert refs", + expected: conditions.NewListenerUnsupportedValue( + "tls.certificateRefs: Too many: 2: must have at most 1 items", + ), + name: "too many cert refs", }, } @@ -181,7 +163,7 @@ func TestValidateHTTPSListener(t *testing.T) { t.Run(test.name, func(t *testing.T) { g := NewGomegaWithT(t) - v := createHTTPSListenerValidator(gwNs) + v := createHTTPSListenerValidator() result := v(test.l) g.Expect(result).To(Equal(test.expected)) diff --git a/internal/state/graph/gateway_test.go b/internal/state/graph/gateway_test.go index e7e7d68503..2a0ee8b2e9 100644 --- a/internal/state/graph/gateway_test.go +++ b/internal/state/graph/gateway_test.go @@ -169,7 +169,7 @@ func TestBuildGateway(t *testing.T) { }, } - gatewayTLSConfig := &v1beta1.GatewayTLSConfig{ + gatewayTLSConfigSameNs := &v1beta1.GatewayTLSConfig{ Mode: helpers.GetTLSModePointer(v1beta1.TLSModeTerminate), CertificateRefs: []v1beta1.SecretObjectReference{ { @@ -191,6 +191,17 @@ func TestBuildGateway(t *testing.T) { }, } + gatewayTLSConfigDiffNs := &v1beta1.GatewayTLSConfig{ + Mode: helpers.GetTLSModePointer(v1beta1.TLSModeTerminate), + CertificateRefs: []v1beta1.SecretObjectReference{ + { + Kind: (*v1beta1.Kind)(helpers.GetStringPointer("Secret")), + Name: "secret", + Namespace: (*v1beta1.Namespace)(helpers.GetStringPointer("diff-ns")), + }, + }, + } + createListener := func( name string, hostname string, @@ -223,29 +234,42 @@ func TestBuildGateway(t *testing.T) { foo443Listener := createHTTPListener("foo-443", "foo.example.com", 443) // foo https listeners - foo80HTTPSListener := createHTTPSListener("foo-80-https", "foo.example.com", 80, gatewayTLSConfig) - foo443HTTPSListener1 := createHTTPSListener("foo-443-https-1", "foo.example.com", 443, gatewayTLSConfig) - foo8443HTTPSListener := createHTTPSListener("foo-8443-https", "foo.example.com", 8443, gatewayTLSConfig) + foo80HTTPSListener := createHTTPSListener("foo-80-https", "foo.example.com", 80, gatewayTLSConfigSameNs) + foo443HTTPSListener1 := createHTTPSListener("foo-443-https-1", "foo.example.com", 443, gatewayTLSConfigSameNs) + foo8443HTTPSListener := createHTTPSListener("foo-8443-https", "foo.example.com", 8443, gatewayTLSConfigSameNs) // bar http listener bar80Listener := createHTTPListener("bar-80", "bar.example.com", 80) // bar https listeners - bar443HTTPSListener := createHTTPSListener("bar-443-https", "bar.example.com", 443, gatewayTLSConfig) - bar8443HTTPSListener := createHTTPSListener("bar-8443-https", "bar.example.com", 8443, gatewayTLSConfig) + bar443HTTPSListener := createHTTPSListener("bar-443-https", "bar.example.com", 443, gatewayTLSConfigSameNs) + bar8443HTTPSListener := createHTTPSListener("bar-8443-https", "bar.example.com", 8443, gatewayTLSConfigSameNs) + + // https listener that references secret in different namespace + crossNamespaceSecretListener := createHTTPSListener( + "listener-cross-ns-secret", + "foo.example.com", + 443, + gatewayTLSConfigDiffNs, + ) // invalid listeners invalidProtocolListener := createTCPListener("invalid-protocol", "bar.example.com", 80) invalidPortListener := createHTTPListener("invalid-port", "invalid-port", 0) invalidHostnameListener := createHTTPListener("invalid-hostname", "$example.com", 80) - invalidHTTPSHostnameListener := createHTTPSListener("invalid-https-hostname", "$example.com", 443, gatewayTLSConfig) + invalidHTTPSHostnameListener := createHTTPSListener( + "invalid-https-hostname", + "$example.com", + 443, + gatewayTLSConfigSameNs, + ) invalidTLSConfigListener := createHTTPSListener( "invalid-tls-config", "foo.example.com", 443, tlsConfigInvalidSecret, ) - invalidHTTPSPortListener := createHTTPSListener("invalid-https-port", "foo.example.com", 0, gatewayTLSConfig) + invalidHTTPSPortListener := createHTTPSListener("invalid-https-port", "foo.example.com", 0, gatewayTLSConfigSameNs) const ( invalidHostnameMsg = `hostname: Invalid value: "$example.com": a lowercase RFC 1123 subdomain ` + @@ -259,7 +283,8 @@ func TestBuildGateway(t *testing.T) { conflict443PortMsg = "Multiple listeners for the same port 443 specify incompatible protocols; " + "ensure only one protocol per port" - secretPath = "/etc/nginx/secrets/test_secret" + secretPath = "/etc/nginx/secrets/test_secret" + diffNsSecretPath = "/etc/nginx/secrets/diff_ns_secret" ) type gatewayCfg struct { @@ -295,6 +320,7 @@ func TestBuildGateway(t *testing.T) { tests := []struct { gateway *v1beta1.Gateway gatewayClass *GatewayClass + refGrants map[types.NamespacedName]*v1beta1.ReferenceGrant expected *Gateway name string }{ @@ -361,6 +387,66 @@ func TestBuildGateway(t *testing.T) { }, name: "valid http listener with allowed routes label selector", }, + { + gateway: createGateway(gatewayCfg{listeners: []v1beta1.Listener{crossNamespaceSecretListener}}), + gatewayClass: validGC, + refGrants: map[types.NamespacedName]*v1beta1.ReferenceGrant{ + {Name: "ref-grant", Namespace: "diff-ns"}: { + ObjectMeta: metav1.ObjectMeta{ + Name: "ref-grant", + Namespace: "diff-ns", + }, + Spec: v1beta1.ReferenceGrantSpec{ + From: []v1beta1.ReferenceGrantFrom{ + { + Group: v1beta1.GroupName, + Kind: "Gateway", + Namespace: "test", + }, + }, + To: []v1beta1.ReferenceGrantTo{ + { + Group: "core", + Kind: "Secret", + Name: helpers.GetPointer(v1beta1.ObjectName("secret")), + }, + }, + }, + }, + }, + expected: &Gateway{ + Source: getLastCreatedGetaway(), + Listeners: map[string]*Listener{ + "listener-cross-ns-secret": { + Source: crossNamespaceSecretListener, + Valid: true, + Routes: map[types.NamespacedName]*Route{}, + SecretPath: diffNsSecretPath, + }, + }, + Valid: true, + }, + name: "valid https listener with cross-namespace secret; allowed by reference grant", + }, + { + gateway: createGateway(gatewayCfg{listeners: []v1beta1.Listener{crossNamespaceSecretListener}}), + gatewayClass: validGC, + expected: &Gateway{ + Source: getLastCreatedGetaway(), + Listeners: map[string]*Listener{ + "listener-cross-ns-secret": { + Source: crossNamespaceSecretListener, + Valid: false, + Conditions: conditions.NewListenerRefNotPermitted( + `Certificate ref to secret diff-ns/secret not permitted by any ReferenceGrant`, + ), + Routes: map[types.NamespacedName]*Route{}, + }, + }, + Valid: true, + }, + name: "invalid https listener with cross-namespace secret; no reference grant", + }, { gateway: createGateway(gatewayCfg{listeners: []v1beta1.Listener{listenerInvalidSelector}}), gatewayClass: validGC, @@ -645,16 +731,20 @@ func TestBuildGateway(t *testing.T) { secretMemoryMgr := &secretsfakes.FakeSecretDiskMemoryManager{} secretMemoryMgr.RequestCalls(func(nsname types.NamespacedName) (string, error) { - if (nsname == types.NamespacedName{Namespace: "test", Name: "secret"}) { + switch nsname { + case types.NamespacedName{Namespace: "test", Name: "secret"}: return secretPath, nil + case types.NamespacedName{Namespace: "diff-ns", Name: "secret"}: + return diffNsSecretPath, nil + default: + return "", errors.New("secret not found") } - return "", errors.New("secret not found") }) for _, test := range tests { t.Run(test.name, func(t *testing.T) { g := NewGomegaWithT(t) - result := buildGateway(test.gateway, secretMemoryMgr, test.gatewayClass) + result := buildGateway(test.gateway, secretMemoryMgr, test.gatewayClass, test.refGrants) g.Expect(helpers.Diff(test.expected, result)).To(BeEmpty()) }) } diff --git a/internal/state/graph/graph.go b/internal/state/graph/graph.go index 6fb7557f58..031752526f 100644 --- a/internal/state/graph/graph.go +++ b/internal/state/graph/graph.go @@ -11,11 +11,12 @@ import ( // ClusterState includes cluster resources necessary to build the Graph. type ClusterState struct { - GatewayClasses map[types.NamespacedName]*v1beta1.GatewayClass - Gateways map[types.NamespacedName]*v1beta1.Gateway - HTTPRoutes map[types.NamespacedName]*v1beta1.HTTPRoute - Services map[types.NamespacedName]*v1.Service - Namespaces map[types.NamespacedName]*v1.Namespace + GatewayClasses map[types.NamespacedName]*v1beta1.GatewayClass + Gateways map[types.NamespacedName]*v1beta1.Gateway + HTTPRoutes map[types.NamespacedName]*v1beta1.HTTPRoute + Services map[types.NamespacedName]*v1.Service + Namespaces map[types.NamespacedName]*v1.Namespace + ReferenceGrants map[types.NamespacedName]*v1beta1.ReferenceGrant } // Graph is a Graph-like representation of Gateway API resources. @@ -50,7 +51,7 @@ func BuildGraph( processedGws := processGateways(state.Gateways, gcName) - gw := buildGateway(processedGws.Winner, secretMemoryMgr, gc) + gw := buildGateway(processedGws.Winner, secretMemoryMgr, gc, state.ReferenceGrants) routes := buildRoutesForGateways(validators.HTTPFieldsValidator, state.HTTPRoutes, processedGws.GetAllNsNames()) bindRoutesToListeners(routes, gw, state.Namespaces) diff --git a/internal/state/graph/reference_grant.go b/internal/state/graph/reference_grant.go new file mode 100644 index 0000000000..549d6720f0 --- /dev/null +++ b/internal/state/graph/reference_grant.go @@ -0,0 +1,64 @@ +package graph + +import ( + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/gateway-api/apis/v1beta1" +) + +func refGrantAllowsGatewayToSecret( + refGrants map[types.NamespacedName]*v1beta1.ReferenceGrant, + gwNs string, + secretNsName types.NamespacedName, +) bool { + for nsname, grant := range refGrants { + if nsname.Namespace != secretNsName.Namespace { + continue + } + + if fromIncludesGatewayNs(grant.Spec.From, gwNs) && toIncludesSecret(grant.Spec.To, secretNsName.Name) { + return true + } + } + + return false +} + +func fromIncludesGatewayNs(fromList []v1beta1.ReferenceGrantFrom, gwNs string) bool { + for _, from := range fromList { + if from.Group != v1beta1.GroupName { + continue + } + + if from.Kind != "Gateway" { + continue + } + + if string(from.Namespace) != gwNs { + continue + } + + return true + } + + return false +} + +func toIncludesSecret(toList []v1beta1.ReferenceGrantTo, secretName string) bool { + for _, to := range toList { + if to.Group != "" && to.Group != "core" { + continue + } + + if to.Kind != "Secret" { + continue + } + + if to.Name != nil && string(*to.Name) != secretName { + continue + } + + return true + } + + return false +} diff --git a/internal/state/graph/reference_grant_test.go b/internal/state/graph/reference_grant_test.go new file mode 100644 index 0000000000..04ce051a1d --- /dev/null +++ b/internal/state/graph/reference_grant_test.go @@ -0,0 +1,201 @@ +package graph + +import ( + "testing" + + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/gateway-api/apis/v1beta1" + + "github.com/nginxinc/nginx-kubernetes-gateway/internal/helpers" +) + +func TestRefGrantAllowsGatewayToSecret(t *testing.T) { + gwNs := "gw-ns" + secretNsName := types.NamespacedName{Namespace: "test", Name: "certificate"} + + getNormalRefGrant := func() *v1beta1.ReferenceGrant { + return &v1beta1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rg", + Namespace: "test", + }, + Spec: v1beta1.ReferenceGrantSpec{ + From: []v1beta1.ReferenceGrantFrom{ + { + Group: v1beta1.GroupName, + Kind: "Gateway", + Namespace: v1beta1.Namespace(gwNs), + }, + }, + To: []v1beta1.ReferenceGrantTo{ + { + Group: "core", + Kind: "Secret", + Name: helpers.GetPointer(v1beta1.ObjectName(secretNsName.Name)), + }, + }, + }, + } + } + + createModifiedRefGrant := func(mod func(rg *v1beta1.ReferenceGrant)) *v1beta1.ReferenceGrant { + rg := getNormalRefGrant() + mod(rg) + return rg + } + + tests := []struct { + refGrants map[types.NamespacedName]*v1beta1.ReferenceGrant + msg string + allowed bool + }{ + { + msg: "allowed; specific ref grant exists", + refGrants: map[types.NamespacedName]*v1beta1.ReferenceGrant{ + {Namespace: "wrong-ns", Name: "rg"}: createModifiedRefGrant(func(rg *v1beta1.ReferenceGrant) { + rg.Namespace = "wrong-ns" + }), + {Namespace: "test", Name: "wrong-to-kind"}: createModifiedRefGrant(func(rg *v1beta1.ReferenceGrant) { + rg.Spec.To[0].Kind = "WrongKind" + rg.Name = "wrong-to-kind" + }), + {Namespace: "test", Name: "rg"}: getNormalRefGrant(), + }, + allowed: true, + }, + { + msg: "allowed; all-namespace ref grant exists", + refGrants: map[types.NamespacedName]*v1beta1.ReferenceGrant{ + {Namespace: "test", Name: "rg"}: createModifiedRefGrant(func(rg *v1beta1.ReferenceGrant) { + rg.Spec.To[0].Name = nil + }), + }, + allowed: true, + }, + { + msg: "allowed; implicit to Group", + refGrants: map[types.NamespacedName]*v1beta1.ReferenceGrant{ + {Namespace: "test", Name: "rg"}: createModifiedRefGrant(func(rg *v1beta1.ReferenceGrant) { + rg.Spec.To[0].Group = "" + }), + }, + allowed: true, + }, + { + msg: "allowed; one matching 'to' ref", + refGrants: map[types.NamespacedName]*v1beta1.ReferenceGrant{ + {Namespace: "test", Name: "rg"}: createModifiedRefGrant(func(rg *v1beta1.ReferenceGrant) { + rg.Spec.To = []v1beta1.ReferenceGrantTo{ + { + Group: "wrong.group", + }, + { + Kind: "WrongKind", + }, + { + Group: "core", + Kind: "Secret", + Name: helpers.GetPointer(v1beta1.ObjectName(secretNsName.Name)), + }, + } + }), + }, + allowed: true, + }, + { + msg: "allowed; one matching 'from' ref", + refGrants: map[types.NamespacedName]*v1beta1.ReferenceGrant{ + {Namespace: "test", Name: "rg"}: createModifiedRefGrant(func(rg *v1beta1.ReferenceGrant) { + rg.Spec.From = []v1beta1.ReferenceGrantFrom{ + { + Group: "wrong.group", + }, + { + Kind: "WrongKind", + }, + { + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Namespace: v1beta1.Namespace(gwNs), + }, + } + }), + }, + allowed: true, + }, + { + msg: "not allowed; no ref group in secret namespace", + refGrants: map[types.NamespacedName]*v1beta1.ReferenceGrant{ + {Namespace: "wrong-ns", Name: "rg"}: createModifiedRefGrant(func(rg *v1beta1.ReferenceGrant) { + rg.Namespace = "wrong-ns" + }), + }, + allowed: false, + }, + { + msg: "not allowed; no ref group with the right from Group", + refGrants: map[types.NamespacedName]*v1beta1.ReferenceGrant{ + {Namespace: "test", Name: "rg"}: createModifiedRefGrant(func(rg *v1beta1.ReferenceGrant) { + rg.Spec.From[0].Group = "wrong.group" + }), + }, + allowed: false, + }, + { + msg: "not allowed; no ref group with the right from Kind", + refGrants: map[types.NamespacedName]*v1beta1.ReferenceGrant{ + {Namespace: "test", Name: "rg"}: createModifiedRefGrant(func(rg *v1beta1.ReferenceGrant) { + rg.Spec.From[0].Kind = "WrongKind" + }), + }, + allowed: false, + }, + { + msg: "not allowed; no ref group with the right from Namespace", + refGrants: map[types.NamespacedName]*v1beta1.ReferenceGrant{ + {Namespace: "test", Name: "rg"}: createModifiedRefGrant(func(rg *v1beta1.ReferenceGrant) { + rg.Spec.From[0].Namespace = "wrong-ns" + }), + }, + allowed: false, + }, + { + msg: "not allowed; no ref group with the right to Group", + refGrants: map[types.NamespacedName]*v1beta1.ReferenceGrant{ + {Namespace: "test", Name: "rg"}: createModifiedRefGrant(func(rg *v1beta1.ReferenceGrant) { + rg.Spec.To[0].Group = "wrong.group" + }), + }, + allowed: false, + }, + { + msg: "not allowed; no ref group with the right to Kind", + refGrants: map[types.NamespacedName]*v1beta1.ReferenceGrant{ + {Namespace: "test", Name: "rg"}: createModifiedRefGrant(func(rg *v1beta1.ReferenceGrant) { + rg.Spec.To[0].Kind = "WrongKind" + }), + }, + allowed: false, + }, + { + msg: "not allowed; no ref group with the right to Name", + refGrants: map[types.NamespacedName]*v1beta1.ReferenceGrant{ + {Namespace: "test", Name: "rg"}: createModifiedRefGrant(func(rg *v1beta1.ReferenceGrant) { + rg.Spec.To[0].Name = helpers.GetPointer(v1beta1.ObjectName("wrong-name")) + }), + }, + allowed: false, + }, + } + + for _, test := range tests { + t.Run(test.msg, func(t *testing.T) { + g := NewGomegaWithT(t) + + allowed := refGrantAllowsGatewayToSecret(test.refGrants, gwNs, secretNsName) + g.Expect(allowed).To(Equal(test.allowed)) + }) + } +} From 76e0bba67f165e5c239c076e1c1b9e46aac7d66e Mon Sep 17 00:00:00 2001 From: Kate Osborn Date: Mon, 26 Jun 2023 15:08:39 -0600 Subject: [PATCH 02/10] Update doc summary --- docs/gateway-api-compatibility.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/gateway-api-compatibility.md b/docs/gateway-api-compatibility.md index f01b091fd4..9e64aa7a18 100644 --- a/docs/gateway-api-compatibility.md +++ b/docs/gateway-api-compatibility.md @@ -12,7 +12,7 @@ This document describes which Gateway API resources NGINX Kubernetes Gateway sup | [TLSRoute](#tlsroute) | Not supported | | [TCPRoute](#tcproute) | Not supported | | [UDPRoute](#udproute) | Not supported | -| [ReferenceGrant](#referencegrant) | Not supported | +| [ReferenceGrant](#referencegrant) | Partially supported | | [Custom policies](#custom-policies) | Not supported | ## Terminology From 29023b9e1730ec27b7f3ea096138d2ddb0facd90 Mon Sep 17 00:00:00 2001 From: Kate Osborn <50597707+kate-osborn@users.noreply.github.com> Date: Tue, 27 Jun 2023 11:21:53 -0600 Subject: [PATCH 03/10] references -> reference Co-authored-by: Saylor Berman --- examples/https-termination/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/https-termination/README.md b/examples/https-termination/README.md index b2da0a19d1..c7abe87433 100644 --- a/examples/https-termination/README.md +++ b/examples/https-termination/README.md @@ -55,7 +55,7 @@ reference a Secret in a different Namespace. kubectl apply -f reference-grant.yaml ``` - This ReferenceGrant allows all Gateways in the `default` namespace to references the `cafe-secret` Secret in + This ReferenceGrant allows all Gateways in the `default` namespace to reference the `cafe-secret` Secret in the `certificate` namespace. 1. Create the `Gateway` resource: From b15f8a88827ca2c60cd675c7f13fca27be2696f1 Mon Sep 17 00:00:00 2001 From: Kate Osborn Date: Tue, 27 Jun 2023 11:30:23 -0600 Subject: [PATCH 04/10] Use empty string for group name --- examples/https-termination/reference-grant.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/https-termination/reference-grant.yaml b/examples/https-termination/reference-grant.yaml index 0299cbd6db..ac0e212f1b 100644 --- a/examples/https-termination/reference-grant.yaml +++ b/examples/https-termination/reference-grant.yaml @@ -5,7 +5,7 @@ metadata: namespace: certificate spec: to: - - group: core + - group: "" kind: Secret name: cafe-secret # if you omit this name, then Gateways in default ns can access all Secrets in the certificate ns from: From 03d60a2bf22249467521114e88eb5d7c79f7f21e Mon Sep 17 00:00:00 2001 From: Kate Osborn Date: Tue, 27 Jun 2023 11:31:31 -0600 Subject: [PATCH 05/10] Add quotes around to and from in ref grant test desc --- internal/state/graph/reference_grant_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/state/graph/reference_grant_test.go b/internal/state/graph/reference_grant_test.go index 04ce051a1d..320c4206ed 100644 --- a/internal/state/graph/reference_grant_test.go +++ b/internal/state/graph/reference_grant_test.go @@ -75,7 +75,7 @@ func TestRefGrantAllowsGatewayToSecret(t *testing.T) { allowed: true, }, { - msg: "allowed; implicit to Group", + msg: "allowed; implicit 'to' Group", refGrants: map[types.NamespacedName]*v1beta1.ReferenceGrant{ {Namespace: "test", Name: "rg"}: createModifiedRefGrant(func(rg *v1beta1.ReferenceGrant) { rg.Spec.To[0].Group = "" @@ -135,7 +135,7 @@ func TestRefGrantAllowsGatewayToSecret(t *testing.T) { allowed: false, }, { - msg: "not allowed; no ref group with the right from Group", + msg: "not allowed; no ref group with the right 'from' Group", refGrants: map[types.NamespacedName]*v1beta1.ReferenceGrant{ {Namespace: "test", Name: "rg"}: createModifiedRefGrant(func(rg *v1beta1.ReferenceGrant) { rg.Spec.From[0].Group = "wrong.group" @@ -144,7 +144,7 @@ func TestRefGrantAllowsGatewayToSecret(t *testing.T) { allowed: false, }, { - msg: "not allowed; no ref group with the right from Kind", + msg: "not allowed; no ref group with the right 'from' Kind", refGrants: map[types.NamespacedName]*v1beta1.ReferenceGrant{ {Namespace: "test", Name: "rg"}: createModifiedRefGrant(func(rg *v1beta1.ReferenceGrant) { rg.Spec.From[0].Kind = "WrongKind" @@ -153,7 +153,7 @@ func TestRefGrantAllowsGatewayToSecret(t *testing.T) { allowed: false, }, { - msg: "not allowed; no ref group with the right from Namespace", + msg: "not allowed; no ref group with the right 'from' Namespace", refGrants: map[types.NamespacedName]*v1beta1.ReferenceGrant{ {Namespace: "test", Name: "rg"}: createModifiedRefGrant(func(rg *v1beta1.ReferenceGrant) { rg.Spec.From[0].Namespace = "wrong-ns" @@ -162,7 +162,7 @@ func TestRefGrantAllowsGatewayToSecret(t *testing.T) { allowed: false, }, { - msg: "not allowed; no ref group with the right to Group", + msg: "not allowed; no ref group with the right 'to' Group", refGrants: map[types.NamespacedName]*v1beta1.ReferenceGrant{ {Namespace: "test", Name: "rg"}: createModifiedRefGrant(func(rg *v1beta1.ReferenceGrant) { rg.Spec.To[0].Group = "wrong.group" @@ -171,7 +171,7 @@ func TestRefGrantAllowsGatewayToSecret(t *testing.T) { allowed: false, }, { - msg: "not allowed; no ref group with the right to Kind", + msg: "not allowed; no ref group with the right 'to' Kind", refGrants: map[types.NamespacedName]*v1beta1.ReferenceGrant{ {Namespace: "test", Name: "rg"}: createModifiedRefGrant(func(rg *v1beta1.ReferenceGrant) { rg.Spec.To[0].Kind = "WrongKind" @@ -180,7 +180,7 @@ func TestRefGrantAllowsGatewayToSecret(t *testing.T) { allowed: false, }, { - msg: "not allowed; no ref group with the right to Name", + msg: "not allowed; no ref group with the right 'to' Name", refGrants: map[types.NamespacedName]*v1beta1.ReferenceGrant{ {Namespace: "test", Name: "rg"}: createModifiedRefGrant(func(rg *v1beta1.ReferenceGrant) { rg.Spec.To[0].Name = helpers.GetPointer(v1beta1.ObjectName("wrong-name")) From 7e14069dc55f2be1770bf8d874b9f19dde2ab8b1 Mon Sep 17 00:00:00 2001 From: Kate Osborn Date: Tue, 27 Jun 2023 11:33:20 -0600 Subject: [PATCH 06/10] Use GetPointer --- internal/state/graph/gateway_test.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/internal/state/graph/gateway_test.go b/internal/state/graph/gateway_test.go index 2a0ee8b2e9..c4f44625c5 100644 --- a/internal/state/graph/gateway_test.go +++ b/internal/state/graph/gateway_test.go @@ -148,7 +148,7 @@ func TestBuildGateway(t *testing.T) { } listenerAllowedRoutes := v1beta1.Listener{ Name: "listener-with-allowed-routes", - Hostname: (*v1beta1.Hostname)(helpers.GetStringPointer("foo.example.com")), + Hostname: (*v1beta1.Hostname)(helpers.GetPointer("foo.example.com")), Port: 80, Protocol: v1beta1.HTTPProtocolType, AllowedRoutes: &v1beta1.AllowedRoutes{ @@ -170,34 +170,34 @@ func TestBuildGateway(t *testing.T) { } gatewayTLSConfigSameNs := &v1beta1.GatewayTLSConfig{ - Mode: helpers.GetTLSModePointer(v1beta1.TLSModeTerminate), + Mode: helpers.GetPointer(v1beta1.TLSModeTerminate), CertificateRefs: []v1beta1.SecretObjectReference{ { - Kind: (*v1beta1.Kind)(helpers.GetStringPointer("Secret")), + Kind: (*v1beta1.Kind)(helpers.GetPointer("Secret")), Name: "secret", - Namespace: (*v1beta1.Namespace)(helpers.GetStringPointer("test")), + Namespace: (*v1beta1.Namespace)(helpers.GetPointer("test")), }, }, } tlsConfigInvalidSecret := &v1beta1.GatewayTLSConfig{ - Mode: helpers.GetTLSModePointer(v1beta1.TLSModeTerminate), + Mode: helpers.GetPointer(v1beta1.TLSModeTerminate), CertificateRefs: []v1beta1.SecretObjectReference{ { - Kind: (*v1beta1.Kind)(helpers.GetStringPointer("Secret")), + Kind: (*v1beta1.Kind)(helpers.GetPointer("Secret")), Name: "does-not-exist", - Namespace: (*v1beta1.Namespace)(helpers.GetStringPointer("test")), + Namespace: (*v1beta1.Namespace)(helpers.GetPointer("test")), }, }, } gatewayTLSConfigDiffNs := &v1beta1.GatewayTLSConfig{ - Mode: helpers.GetTLSModePointer(v1beta1.TLSModeTerminate), + Mode: helpers.GetPointer(v1beta1.TLSModeTerminate), CertificateRefs: []v1beta1.SecretObjectReference{ { - Kind: (*v1beta1.Kind)(helpers.GetStringPointer("Secret")), + Kind: (*v1beta1.Kind)(helpers.GetPointer("Secret")), Name: "secret", - Namespace: (*v1beta1.Namespace)(helpers.GetStringPointer("diff-ns")), + Namespace: (*v1beta1.Namespace)(helpers.GetPointer("diff-ns")), }, }, } @@ -211,7 +211,7 @@ func TestBuildGateway(t *testing.T) { ) v1beta1.Listener { return v1beta1.Listener{ Name: v1beta1.SectionName(name), - Hostname: (*v1beta1.Hostname)(helpers.GetStringPointer(hostname)), + Hostname: (*v1beta1.Hostname)(helpers.GetPointer(hostname)), Port: v1beta1.PortNumber(port), Protocol: protocol, TLS: tls, From 448a8dbe9cd9896e174ec528a4b3447907df80dd Mon Sep 17 00:00:00 2001 From: Kate Osborn Date: Tue, 27 Jun 2023 14:19:12 -0600 Subject: [PATCH 07/10] Use type in call to GetPointer where possible --- internal/state/graph/gateway_test.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/internal/state/graph/gateway_test.go b/internal/state/graph/gateway_test.go index c4f44625c5..b4de39b351 100644 --- a/internal/state/graph/gateway_test.go +++ b/internal/state/graph/gateway_test.go @@ -148,12 +148,12 @@ func TestBuildGateway(t *testing.T) { } listenerAllowedRoutes := v1beta1.Listener{ Name: "listener-with-allowed-routes", - Hostname: (*v1beta1.Hostname)(helpers.GetPointer("foo.example.com")), + Hostname: helpers.GetPointer[v1beta1.Hostname]("foo.example.com"), Port: 80, Protocol: v1beta1.HTTPProtocolType, AllowedRoutes: &v1beta1.AllowedRoutes{ Kinds: []v1beta1.RouteGroupKind{ - {Kind: "HTTPRoute", Group: helpers.GetPointer(v1beta1.Group(v1beta1.GroupName))}, + {Kind: "HTTPRoute", Group: helpers.GetPointer[v1beta1.Group](v1beta1.GroupName)}, }, Namespaces: &v1beta1.RouteNamespaces{ From: helpers.GetPointer(v1beta1.NamespacesFromSelector), @@ -173,9 +173,9 @@ func TestBuildGateway(t *testing.T) { Mode: helpers.GetPointer(v1beta1.TLSModeTerminate), CertificateRefs: []v1beta1.SecretObjectReference{ { - Kind: (*v1beta1.Kind)(helpers.GetPointer("Secret")), + Kind: helpers.GetPointer[v1beta1.Kind]("Secret"), Name: "secret", - Namespace: (*v1beta1.Namespace)(helpers.GetPointer("test")), + Namespace: helpers.GetPointer[v1beta1.Namespace]("test"), }, }, } @@ -184,9 +184,9 @@ func TestBuildGateway(t *testing.T) { Mode: helpers.GetPointer(v1beta1.TLSModeTerminate), CertificateRefs: []v1beta1.SecretObjectReference{ { - Kind: (*v1beta1.Kind)(helpers.GetPointer("Secret")), + Kind: helpers.GetPointer[v1beta1.Kind]("Secret"), Name: "does-not-exist", - Namespace: (*v1beta1.Namespace)(helpers.GetPointer("test")), + Namespace: helpers.GetPointer[v1beta1.Namespace]("test"), }, }, } @@ -195,9 +195,9 @@ func TestBuildGateway(t *testing.T) { Mode: helpers.GetPointer(v1beta1.TLSModeTerminate), CertificateRefs: []v1beta1.SecretObjectReference{ { - Kind: (*v1beta1.Kind)(helpers.GetPointer("Secret")), + Kind: helpers.GetPointer[v1beta1.Kind]("Secret"), Name: "secret", - Namespace: (*v1beta1.Namespace)(helpers.GetPointer("diff-ns")), + Namespace: helpers.GetPointer[v1beta1.Namespace]("diff-ns"), }, }, } @@ -408,7 +408,7 @@ func TestBuildGateway(t *testing.T) { { Group: "core", Kind: "Secret", - Name: helpers.GetPointer(v1beta1.ObjectName("secret")), + Name: helpers.GetPointer[v1beta1.ObjectName]("secret"), }, }, }, From 0db9b1704f226a8e8e5870ef8d43c4579d276333 Mon Sep 17 00:00:00 2001 From: Kate Osborn Date: Tue, 27 Jun 2023 14:20:36 -0600 Subject: [PATCH 08/10] Change name of yaml file --- examples/https-termination/README.md | 2 +- .../{cafe-secret.yaml => certificate-ns-and-cafe-secret.yaml} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename examples/https-termination/{cafe-secret.yaml => certificate-ns-and-cafe-secret.yaml} (100%) diff --git a/examples/https-termination/README.md b/examples/https-termination/README.md index c7abe87433..997494ab0c 100644 --- a/examples/https-termination/README.md +++ b/examples/https-termination/README.md @@ -44,7 +44,7 @@ reference a Secret in a different Namespace. 1. Create the Namespace `certificate` and a Secret with a TLS certificate and key: ``` - kubectl apply -f cafe-secret.yaml + kubectl apply -f certificate-ns-and-cafe-secret.yaml ``` The TLS certificate and key in this Secret are used to terminate the TLS connections for the cafe application. diff --git a/examples/https-termination/cafe-secret.yaml b/examples/https-termination/certificate-ns-and-cafe-secret.yaml similarity index 100% rename from examples/https-termination/cafe-secret.yaml rename to examples/https-termination/certificate-ns-and-cafe-secret.yaml From f869c1a3f088399acf1fe83b3efeb74aaa9d13ce Mon Sep 17 00:00:00 2001 From: Kate Osborn Date: Tue, 27 Jun 2023 15:03:24 -0600 Subject: [PATCH 09/10] Add graph test --- internal/state/graph/graph_test.go | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/internal/state/graph/graph_test.go b/internal/state/graph/graph_test.go index 1787338bdc..f4d2a42858 100644 --- a/internal/state/graph/graph_test.go +++ b/internal/state/graph/graph_test.go @@ -127,9 +127,9 @@ func TestBuildGraph(t *testing.T) { Mode: helpers.GetTLSModePointer(v1beta1.TLSModeTerminate), CertificateRefs: []v1beta1.SecretObjectReference{ { - Kind: (*v1beta1.Kind)(helpers.GetStringPointer("Secret")), + Kind: helpers.GetPointer[v1beta1.Kind]("Secret"), Name: "secret", - Namespace: (*v1beta1.Namespace)(helpers.GetStringPointer("test")), + Namespace: helpers.GetPointer[v1beta1.Namespace]("certificate"), }, }, }, @@ -145,6 +145,27 @@ func TestBuildGraph(t *testing.T) { svc := &v1.Service{ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "foo"}} + rg := &v1beta1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rg", + Namespace: "certificate", + }, + Spec: v1beta1.ReferenceGrantSpec{ + From: []v1beta1.ReferenceGrantFrom{ + { + Group: v1beta1.GroupName, + Kind: "Gateway", + Namespace: "test", + }, + }, + To: []v1beta1.ReferenceGrantTo{ + { + Kind: "Secret", + }, + }, + }, + } + createStateWithGatewayClass := func(gc *v1beta1.GatewayClass) ClusterState { return ClusterState{ GatewayClasses: map[types.NamespacedName]*v1beta1.GatewayClass{ @@ -162,6 +183,9 @@ func TestBuildGraph(t *testing.T) { Services: map[types.NamespacedName]*v1.Service{ client.ObjectKeyFromObject(svc): svc, }, + ReferenceGrants: map[types.NamespacedName]*v1beta1.ReferenceGrant{ + client.ObjectKeyFromObject(rg): rg, + }, } } @@ -199,7 +223,7 @@ func TestBuildGraph(t *testing.T) { secretMemoryMgr := &secretsfakes.FakeSecretDiskMemoryManager{} secretMemoryMgr.RequestCalls(func(nsname types.NamespacedName) (string, error) { - if (nsname == types.NamespacedName{Namespace: "test", Name: "secret"}) { + if (nsname == types.NamespacedName{Namespace: "certificate", Name: "secret"}) { return secretPath, nil } panic("unexpected secret request") From d7d4cd08a6207842a6f3e85118681870ae4ce4dd Mon Sep 17 00:00:00 2001 From: Kate Osborn Date: Wed, 28 Jun 2023 09:46:41 -0600 Subject: [PATCH 10/10] Update conformance RBAC --- conformance/tests/conformance-rbac.yaml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/conformance/tests/conformance-rbac.yaml b/conformance/tests/conformance-rbac.yaml index b6037bb09a..3378881af0 100644 --- a/conformance/tests/conformance-rbac.yaml +++ b/conformance/tests/conformance-rbac.yaml @@ -30,18 +30,13 @@ rules: - delete - get - list -- apiGroups: - - gateway.networking.k8s.io - resources: - - gatewayclasses - verbs: - - get - - list - apiGroups: - gateway.networking.k8s.io resources: - gateways - httproutes + - referencegrants + - gatewayclasses verbs: - create - delete