From a73ab1d42f16f87f32a33d00a9c378dee606ae7a Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Thu, 19 Oct 2023 16:55:24 +0200 Subject: [PATCH] prevent usage of routeSelectors in a gateway AuthPolicy --- api/v1beta2/authpolicy_types.go | 39 +++++++ api/v1beta2/authpolicy_types_test.go | 161 +++++++++++++++++++++++++++ 2 files changed, 200 insertions(+) diff --git a/api/v1beta2/authpolicy_types.go b/api/v1beta2/authpolicy_types.go index 459c7205a..a44003e2b 100644 --- a/api/v1beta2/authpolicy_types.go +++ b/api/v1beta2/authpolicy_types.go @@ -238,9 +238,48 @@ func (ap *AuthPolicy) Validate() error { if ap.Spec.TargetRef.Namespace != nil && string(*ap.Spec.TargetRef.Namespace) != ap.Namespace { return fmt.Errorf("invalid targetRef.Namespace %s. Currently only supporting references to the same namespace", *ap.Spec.TargetRef.Namespace) } + + // prevents usage of routeSelectors in a gateway AuthPolicy + if ap.Spec.TargetRef.Kind == ("Gateway") { + containRouteSelectors := func(config map[string]RouteSelectorsGetter) bool { + if config == nil { + return false + } + for _, config := range config { + if len(config.GetRouteSelectors()) > 0 { + return true + } + } + return false + } + configs := []map[string]RouteSelectorsGetter{ + {"": ap.Spec}, + toRouteSelectorGetterMap(ap.Spec.AuthScheme.Authentication), + toRouteSelectorGetterMap(ap.Spec.AuthScheme.Metadata), + toRouteSelectorGetterMap(ap.Spec.AuthScheme.Authorization), + toRouteSelectorGetterMap(ap.Spec.AuthScheme.Callbacks), + } + if r := ap.Spec.AuthScheme.Response; r != nil { + configs = append(configs, toRouteSelectorGetterMap(r.Success.Headers), toRouteSelectorGetterMap(r.Success.DynamicMetadata)) + } + for _, config := range configs { + if containRouteSelectors(config) { + return fmt.Errorf("route selectors not supported when targeting a Gateway") + } + } + } + return nil } +func toRouteSelectorGetterMap[T RouteSelectorsGetter](m map[string]T) map[string]RouteSelectorsGetter { + result := make(map[string]RouteSelectorsGetter) + for k, v := range m { + result[k] = v + } + return result +} + func (ap *AuthPolicy) GetTargetRef() gatewayapiv1alpha2.PolicyTargetReference { return ap.Spec.TargetRef } diff --git a/api/v1beta2/authpolicy_types_test.go b/api/v1beta2/authpolicy_types_test.go index d349e16f4..f31713608 100644 --- a/api/v1beta2/authpolicy_types_test.go +++ b/api/v1beta2/authpolicy_types_test.go @@ -11,6 +11,7 @@ import ( gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gatewayapiv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + authorinoapi "github.com/kuadrant/authorino/api/v1beta2" "github.com/kuadrant/kuadrant-operator/pkg/common" ) @@ -239,6 +240,166 @@ func TestAuthPolicyGetRulesHostnames(t *testing.T) { } } +func TestAuthPolicyValidate(t *testing.T) { + testCases := []struct { + name string + policy *AuthPolicy + valid bool + message string + }{ + { + name: "valid policy targeting a httproute", + policy: &AuthPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy", + Namespace: "my-namespace", + }, + Spec: AuthPolicySpec{ + TargetRef: gatewayapiv1alpha2.PolicyTargetReference{ + Group: "gateway.networking.k8s.io", + Kind: "HTTPRoute", + Name: "my-route", + }, + }, + }, + valid: true, + }, + { + name: "valid policy targeting a gateway", + policy: &AuthPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy", + Namespace: "my-namespace", + }, + Spec: AuthPolicySpec{ + TargetRef: gatewayapiv1alpha2.PolicyTargetReference{ + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Name: "my-gw", + }, + }, + }, + valid: true, + }, + { + name: "invalig targetRef group", + policy: &AuthPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy", + Namespace: "my-namespace", + }, + Spec: AuthPolicySpec{ + TargetRef: gatewayapiv1alpha2.PolicyTargetReference{ + Group: "not-gateway.networking.k8s.io.group", + Kind: "HTTPRoute", + Name: "my-non-gwapi-route", + }, + }, + }, + message: "invalid targetRef.Group not-gateway.networking.k8s.io.group. The only supported group is gateway.networking.k8s.io", + }, + { + name: "invalid targetRef kind", + policy: &AuthPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy", + Namespace: "my-namespace", + }, + Spec: AuthPolicySpec{ + TargetRef: gatewayapiv1alpha2.PolicyTargetReference{ + Group: "gateway.networking.k8s.io", + Kind: "TCPRoute", + Name: "my-tcp-route", + }, + }, + }, + message: "invalid targetRef.Kind TCPRoute. The only supported kinds are HTTPRoute and Gateway", + }, + { + name: "invalid usage of top-level route selectors with a gateway targetRef", + policy: &AuthPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy", + Namespace: "my-namespace", + }, + Spec: AuthPolicySpec{ + TargetRef: gatewayapiv1alpha2.PolicyTargetReference{ + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Name: "my-gw", + }, + RouteSelectors: []RouteSelector{ + { + Hostnames: []gatewayapiv1beta1.Hostname{"*.foo.io"}, + Matches: []gatewayapiv1beta1.HTTPRouteMatch{ + { + Path: &gatewayapiv1beta1.HTTPPathMatch{ + Value: ptr.To("/foo"), + }, + }, + }, + }, + }, + }, + }, + message: "route selectors not supported when targeting a Gateway", + }, + { + name: "invalid usage of config-level route selectors with a gateway targetRef", + policy: &AuthPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy", + Namespace: "my-namespace", + }, + Spec: AuthPolicySpec{ + TargetRef: gatewayapiv1alpha2.PolicyTargetReference{ + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Name: "my-gw", + }, + AuthScheme: AuthSchemeSpec{ + Authentication: map[string]AuthenticationSpec{ + "my-rule": { + AuthenticationSpec: authorinoapi.AuthenticationSpec{ + AuthenticationMethodSpec: authorinoapi.AuthenticationMethodSpec{ + AnonymousAccess: &authorinoapi.AnonymousAccessSpec{}, + }, + }, + CommonAuthRuleSpec: CommonAuthRuleSpec{ + RouteSelectors: []RouteSelector{ + { + Hostnames: []gatewayapiv1beta1.Hostname{"*.foo.io"}, + Matches: []gatewayapiv1beta1.HTTPRouteMatch{ + { + Path: &gatewayapiv1beta1.HTTPPathMatch{ + Value: ptr.To("/foo"), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + message: "route selectors not supported when targeting a Gateway", + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := tc.policy.Validate() + if tc.valid && result != nil { + t.Errorf("Expected policy to be valid, got %t", result) + } + if !tc.valid && result == nil { + t.Error("Expected policy to be invalid, got no validation error") + } + }) + } +} + func testBuildRouteSelector() RouteSelector { return RouteSelector{ Hostnames: []gatewayapiv1beta1.Hostname{"toystore.kuadrant.io"},