diff --git a/api/v1beta2/authpolicy_types.go b/api/v1beta2/authpolicy_types.go index ea5f1b231..e41e65110 100644 --- a/api/v1beta2/authpolicy_types.go +++ b/api/v1beta2/authpolicy_types.go @@ -223,52 +223,10 @@ func (ap *AuthPolicy) TargetKey() client.ObjectKey { } func (ap *AuthPolicy) Validate() error { - if ap.Spec.TargetRef.Group != ("gateway.networking.k8s.io") { - return fmt.Errorf("invalid targetRef.Group %s. The only supported group is gateway.networking.k8s.io", ap.Spec.TargetRef.Group) - } - - switch kind := ap.Spec.TargetRef.Kind; kind { - case - "HTTPRoute", - "Gateway": - default: - return fmt.Errorf("invalid targetRef.Kind %s. The only supported kinds are HTTPRoute and Gateway", kind) - } - 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 } @@ -333,11 +291,3 @@ func (l *AuthPolicyList) GetItems() []common.KuadrantPolicy { func init() { SchemeBuilder.Register(&AuthPolicy{}, &AuthPolicyList{}) } - -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 -} diff --git a/api/v1beta2/authpolicy_types_test.go b/api/v1beta2/authpolicy_types_test.go index 5f11f3f86..7b645bd3c 100644 --- a/api/v1beta2/authpolicy_types_test.go +++ b/api/v1beta2/authpolicy_types_test.go @@ -247,145 +247,6 @@ func TestAuthPolicyValidate(t *testing.T) { 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: "invalid 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: []gatewayapiv1.Hostname{"*.foo.io"}, - Matches: []gatewayapiv1.HTTPRouteMatch{ - { - Path: &gatewayapiv1.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: []gatewayapiv1.Hostname{"*.foo.io"}, - Matches: []gatewayapiv1.HTTPRouteMatch{ - { - Path: &gatewayapiv1.HTTPPathMatch{ - Value: ptr.To("/foo"), - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - message: "route selectors not supported when targeting a Gateway", - }, { name: "invalid targetRef namespace", policy: &AuthPolicy{ diff --git a/api/v1beta2/ratelimitpolicy_types.go b/api/v1beta2/ratelimitpolicy_types.go index 0e245ffa1..56ff90d5b 100644 --- a/api/v1beta2/ratelimitpolicy_types.go +++ b/api/v1beta2/ratelimitpolicy_types.go @@ -114,8 +114,11 @@ func (l Limit) CountersAsStringList() []string { } // RateLimitPolicySpec defines the desired state of RateLimitPolicy +// +kubebuilder:validation:XValidation:rule="self.targetRef.kind != 'Gateway' || !has(self.limits) || !self.limits.exists(x, has(self.limits[x].routeSelectors))",message="route selectors not supported when targeting a Gateway" type RateLimitPolicySpec struct { // TargetRef identifies an API object to apply policy to. + // +kubebuilder:validation:XValidation:rule="self.group == 'gateway.networking.k8s.io'",message="Invalid targetRef.group. The only supported value is 'gateway.networking.k8s.io'" + // +kubebuilder:validation:XValidation:rule="self.kind == 'HTTPRoute' || self.kind == 'Gateway'",message="Invalid targetRef.kind. The only supported values are 'HTTPRoute' and 'Gateway'" TargetRef gatewayapiv1alpha2.PolicyTargetReference `json:"targetRef"` // Limits holds the struct of limits indexed by a unique name @@ -174,27 +177,10 @@ type RateLimitPolicy struct { } func (r *RateLimitPolicy) Validate() error { - if r.Spec.TargetRef.Group != ("gateway.networking.k8s.io") { - return fmt.Errorf("invalid targetRef.Group %s. The only supported group is gateway.networking.k8s.io", r.Spec.TargetRef.Group) - } - - if r.Spec.TargetRef.Kind != ("HTTPRoute") && r.Spec.TargetRef.Kind != ("Gateway") { - return fmt.Errorf("invalid targetRef.Kind %s. The only supported kind types are HTTPRoute and Gateway", r.Spec.TargetRef.Kind) - } - if r.Spec.TargetRef.Namespace != nil && string(*r.Spec.TargetRef.Namespace) != r.Namespace { return fmt.Errorf("invalid targetRef.Namespace %s. Currently only supporting references to the same namespace", *r.Spec.TargetRef.Namespace) } - // prevents usage of routeSelectors in a gateway RLP - if r.Spec.TargetRef.Kind == ("Gateway") { - for _, limit := range r.Spec.Limits { - if len(limit.RouteSelectors) > 0 { - return fmt.Errorf("route selectors not supported when targeting a Gateway") - } - } - } - return nil } diff --git a/api/v1beta2/ratelimitpolicy_types_test.go b/api/v1beta2/ratelimitpolicy_types_test.go index b1fec0b24..af895e33b 100644 --- a/api/v1beta2/ratelimitpolicy_types_test.go +++ b/api/v1beta2/ratelimitpolicy_types_test.go @@ -44,49 +44,13 @@ func testBuildBasicHTTPRouteRLP(name string) *RateLimitPolicy { // TestRateLimitPolicyValidation calls rlp.Validate() // for a valid return value. func TestRateLimitPolicyValidation(t *testing.T) { - // valid httproute rlp name := "httproute-a" - rlp := testBuildBasicHTTPRouteRLP(name) - err := rlp.Validate() - if err != nil { - t.Fatalf(`rlp.Validate() returned error "%v", wanted nil`, err) - } - - // valid gateway rlp - name = "gateway-a" - rlp = testBuildBasicGatewayRLP(name) - err = rlp.Validate() - if err != nil { - t.Fatalf(`rlp.Validate() returned error "%v", wanted nil`, err) - } - - // invalid group - rlp = testBuildBasicHTTPRouteRLP(name) - rlp.Spec.TargetRef.Group = gatewayapiv1.Group("foo.example.com") - err = rlp.Validate() - if err == nil { - t.Fatal(`rlp.Validate() did not return error and should`) - } - if !strings.Contains(err.Error(), "invalid targetRef.Group") { - t.Fatalf(`rlp.Validate() did not return expected error. Instead: %v`, err) - } - - // invalid kind - rlp = testBuildBasicHTTPRouteRLP(name) - rlp.Spec.TargetRef.Kind = gatewayapiv1.Kind("Foo") - err = rlp.Validate() - if err == nil { - t.Fatal(`rlp.Validate() did not return error and should`) - } - if !strings.Contains(err.Error(), "invalid targetRef.Kind") { - t.Fatalf(`rlp.Validate() did not return expected error. Instead: %v`, err) - } // Different namespace - rlp = testBuildBasicHTTPRouteRLP(name) + rlp := testBuildBasicHTTPRouteRLP(name) otherNS := gatewayapiv1.Namespace(rlp.GetNamespace() + "other") rlp.Spec.TargetRef.Namespace = &otherNS - err = rlp.Validate() + err := rlp.Validate() if err == nil { t.Fatal(`rlp.Validate() did not return error and should`) } diff --git a/bundle/manifests/kuadrant.io_ratelimitpolicies.yaml b/bundle/manifests/kuadrant.io_ratelimitpolicies.yaml index 6795d0063..b6243ef61 100644 --- a/bundle/manifests/kuadrant.io_ratelimitpolicies.yaml +++ b/bundle/manifests/kuadrant.io_ratelimitpolicies.yaml @@ -429,9 +429,19 @@ spec: - kind - name type: object + x-kubernetes-validations: + - message: Invalid targetRef.group. The only supported value is 'gateway.networking.k8s.io' + rule: self.group == 'gateway.networking.k8s.io' + - message: Invalid targetRef.kind. The only supported values are 'HTTPRoute' + and 'Gateway' + rule: self.kind == 'HTTPRoute' || self.kind == 'Gateway' required: - targetRef type: object + x-kubernetes-validations: + - message: route selectors not supported when targeting a Gateway + rule: self.targetRef.kind != 'Gateway' || !has(self.limits) || !self.limits.exists(x, + has(self.limits[x].routeSelectors)) status: description: RateLimitPolicyStatus defines the observed state of RateLimitPolicy properties: diff --git a/config/crd/bases/kuadrant.io_ratelimitpolicies.yaml b/config/crd/bases/kuadrant.io_ratelimitpolicies.yaml index 2eb74a7d2..49ed01196 100644 --- a/config/crd/bases/kuadrant.io_ratelimitpolicies.yaml +++ b/config/crd/bases/kuadrant.io_ratelimitpolicies.yaml @@ -428,9 +428,19 @@ spec: - kind - name type: object + x-kubernetes-validations: + - message: Invalid targetRef.group. The only supported value is 'gateway.networking.k8s.io' + rule: self.group == 'gateway.networking.k8s.io' + - message: Invalid targetRef.kind. The only supported values are 'HTTPRoute' + and 'Gateway' + rule: self.kind == 'HTTPRoute' || self.kind == 'Gateway' required: - targetRef type: object + x-kubernetes-validations: + - message: route selectors not supported when targeting a Gateway + rule: self.targetRef.kind != 'Gateway' || !has(self.limits) || !self.limits.exists(x, + has(self.limits[x].routeSelectors)) status: description: RateLimitPolicyStatus defines the observed state of RateLimitPolicy properties: diff --git a/controllers/authpolicy_controller_test.go b/controllers/authpolicy_controller_test.go index dba7cd1fd..dede13448 100644 --- a/controllers/authpolicy_controller_test.go +++ b/controllers/authpolicy_controller_test.go @@ -10,6 +10,8 @@ import ( "strings" "time" + authorinoapi "github.com/kuadrant/authorino/api/v1beta2" + api "github.com/kuadrant/kuadrant-operator/api/v1beta2" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" secv1beta1resources "istio.io/client-go/pkg/apis/security/v1beta1" @@ -23,9 +25,6 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - - authorinoapi "github.com/kuadrant/authorino/api/v1beta2" - api "github.com/kuadrant/kuadrant-operator/api/v1beta2" ) const ( @@ -1260,6 +1259,320 @@ var _ = Describe("AuthPolicy controller", func() { Context("TODO: Targeted resource does not exist", func() {}) }) +var _ = Describe("AuthPolicy CEL Validations", func() { + var testNamespace string + + BeforeEach(func() { + CreateNamespace(&testNamespace) + }) + + AfterEach(DeleteNamespaceCallback(&testNamespace)) + + Context("Spec TargetRef Validations", func() { + It("Valid policy targeting HTTPRoute", func() { + policy := &api.AuthPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy", + Namespace: testNamespace, + }, + Spec: api.AuthPolicySpec{ + TargetRef: gatewayapiv1alpha2.PolicyTargetReference{ + Group: "gateway.networking.k8s.io", + Kind: "HTTPRoute", + Name: "my-route", + }, + }, + } + err := k8sClient.Create(context.Background(), policy) + Expect(err).To(BeNil()) + }) + + It("Valid policy targeting Gateway", func() { + policy := &api.AuthPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy", + Namespace: testNamespace, + }, + Spec: api.AuthPolicySpec{ + TargetRef: gatewayapiv1alpha2.PolicyTargetReference{ + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Name: "my-gw", + }, + }, + } + err := k8sClient.Create(context.Background(), policy) + Expect(err).To(BeNil()) + }) + + It("Invalid Target Ref Group", func() { + policy := &api.AuthPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy", + Namespace: testNamespace, + }, + Spec: api.AuthPolicySpec{ + TargetRef: gatewayapiv1alpha2.PolicyTargetReference{ + Group: "not-gateway.networking.k8s.io", + Kind: "HTTPRoute", + Name: "my-route", + }, + }, + } + err := k8sClient.Create(context.Background(), policy) + Expect(err).To(Not(BeNil())) + Expect(strings.Contains(err.Error(), "Invalid targetRef.group. The only supported value is 'gateway.networking.k8s.io'")).To(BeTrue()) + }) + + It("Invalid Target Ref Kind", func() { + policy := &api.AuthPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy", + Namespace: testNamespace, + }, + Spec: api.AuthPolicySpec{ + TargetRef: gatewayapiv1alpha2.PolicyTargetReference{ + Group: "gateway.networking.k8s.io", + Kind: "TCPRoute", + Name: "my-route", + }, + }, + } + err := k8sClient.Create(context.Background(), policy) + Expect(err).To(Not(BeNil())) + Expect(strings.Contains(err.Error(), "Invalid targetRef.kind. The only supported values are 'HTTPRoute' and 'Gateway'")).To(BeTrue()) + }) + }) + + Context("Route Selector Validation", func() { + const ( + gateWayRouteSelectorErrorMessage = "route selectors not supported when targeting a Gateway" + ) + + var ( + routeSelectors = []api.RouteSelector{ + { + Hostnames: []gatewayapiv1.Hostname{"*.foo.io"}, + Matches: []gatewayapiv1.HTTPRouteMatch{ + { + Path: &gatewayapiv1.HTTPPathMatch{ + Value: ptr.To("/foo"), + }, + }, + }, + }, + } + + commonAuthRuleSpec = api.CommonAuthRuleSpec{RouteSelectors: routeSelectors} + ) + + It("invalid usage of top-level route selectors with a gateway targetRef", func() { + policy := &api.AuthPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy", + Namespace: testNamespace, + }, + Spec: api.AuthPolicySpec{ + TargetRef: gatewayapiv1alpha2.PolicyTargetReference{ + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Name: "my-gw", + }, + RouteSelectors: routeSelectors, + }, + } + + err := k8sClient.Create(context.Background(), policy) + Expect(err).To(Not(BeNil())) + Expect(strings.Contains(err.Error(), gateWayRouteSelectorErrorMessage)).To(BeTrue()) + }) + + It("invalid usage of config-level route selectors with a gateway targetRef - authentication", func() { + policy := &api.AuthPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy", + Namespace: testNamespace, + }, + Spec: api.AuthPolicySpec{ + TargetRef: gatewayapiv1alpha2.PolicyTargetReference{ + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Name: "my-gw", + }, + AuthScheme: api.AuthSchemeSpec{ + Authentication: map[string]api.AuthenticationSpec{ + "my-rule": { + AuthenticationSpec: authorinoapi.AuthenticationSpec{ + AuthenticationMethodSpec: authorinoapi.AuthenticationMethodSpec{ + AnonymousAccess: &authorinoapi.AnonymousAccessSpec{}, + }, + }, + CommonAuthRuleSpec: commonAuthRuleSpec, + }, + }, + }, + }, + } + + err := k8sClient.Create(context.Background(), policy) + Expect(err).To(Not(BeNil())) + Expect(strings.Contains(err.Error(), gateWayRouteSelectorErrorMessage)).To(BeTrue()) + }) + + It("invalid usage of config-level route selectors with a gateway targetRef - metadata", func() { + policy := &api.AuthPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy", + Namespace: testNamespace, + }, + Spec: api.AuthPolicySpec{ + TargetRef: gatewayapiv1alpha2.PolicyTargetReference{ + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Name: "my-gw", + }, + AuthScheme: api.AuthSchemeSpec{ + Metadata: map[string]api.MetadataSpec{ + "my-metadata": { + CommonAuthRuleSpec: commonAuthRuleSpec, + }, + }, + }, + }, + } + + err := k8sClient.Create(context.Background(), policy) + Expect(err).To(Not(BeNil())) + Expect(strings.Contains(err.Error(), gateWayRouteSelectorErrorMessage)).To(BeTrue()) + }) + + It("invalid usage of config-level route selectors with a gateway targetRef - authorization", func() { + policy := &api.AuthPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy", + Namespace: testNamespace, + }, + Spec: api.AuthPolicySpec{ + TargetRef: gatewayapiv1alpha2.PolicyTargetReference{ + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Name: "my-gw", + }, + AuthScheme: api.AuthSchemeSpec{ + Authorization: map[string]api.AuthorizationSpec{ + "my-authZ": { + CommonAuthRuleSpec: commonAuthRuleSpec, + }, + }, + }, + }, + } + + err := k8sClient.Create(context.Background(), policy) + Expect(err).To(Not(BeNil())) + Expect(strings.Contains(err.Error(), gateWayRouteSelectorErrorMessage)).To(BeTrue()) + }) + + It("invalid usage of config-level route selectors with a gateway targetRef - response success headers", func() { + policy := &api.AuthPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy", + Namespace: testNamespace, + }, + Spec: api.AuthPolicySpec{ + TargetRef: gatewayapiv1alpha2.PolicyTargetReference{ + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Name: "my-gw", + }, + AuthScheme: api.AuthSchemeSpec{ + Response: &api.ResponseSpec{ + Success: api.WrappedSuccessResponseSpec{ + Headers: map[string]api.HeaderSuccessResponseSpec{ + "header": { + SuccessResponseSpec: api.SuccessResponseSpec{ + CommonAuthRuleSpec: commonAuthRuleSpec, + }, + }, + }, + }, + }, + }, + }, + } + + err := k8sClient.Create(context.Background(), policy) + Expect(err).To(Not(BeNil())) + Expect(strings.Contains(err.Error(), gateWayRouteSelectorErrorMessage)).To(BeTrue()) + }) + + It("invalid usage of config-level route selectors with a gateway targetRef - response success dynamic metadata", func() { + policy := &api.AuthPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy", + Namespace: testNamespace, + }, + Spec: api.AuthPolicySpec{ + TargetRef: gatewayapiv1alpha2.PolicyTargetReference{ + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Name: "my-gw", + }, + AuthScheme: api.AuthSchemeSpec{ + Response: &api.ResponseSpec{ + Success: api.WrappedSuccessResponseSpec{ + DynamicMetadata: map[string]api.SuccessResponseSpec{ + "header": { + CommonAuthRuleSpec: commonAuthRuleSpec, + }, + }, + }, + }, + }, + }, + } + + err := k8sClient.Create(context.Background(), policy) + Expect(err).To(Not(BeNil())) + Expect(strings.Contains(err.Error(), gateWayRouteSelectorErrorMessage)).To(BeTrue()) + }) + + It("invalid usage of config-level route selectors with a gateway targetRef - callbacks", func() { + policy := &api.AuthPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy", + Namespace: testNamespace, + }, + Spec: api.AuthPolicySpec{ + TargetRef: gatewayapiv1alpha2.PolicyTargetReference{ + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Name: "my-gw", + }, + AuthScheme: api.AuthSchemeSpec{ + Callbacks: map[string]api.CallbackSpec{ + "callback": { + CallbackSpec: authorinoapi.CallbackSpec{ + CallbackMethodSpec: authorinoapi.CallbackMethodSpec{ + Http: &authorinoapi.HttpEndpointSpec{ + Url: "test.com", + }, + }, + }, + CommonAuthRuleSpec: commonAuthRuleSpec, + }, + }, + }, + }, + } + + err := k8sClient.Create(context.Background(), policy) + Expect(err).To(Not(BeNil())) + Expect(strings.Contains(err.Error(), gateWayRouteSelectorErrorMessage)).To(BeTrue()) + }) + }) +}) + func testBasicAuthScheme() api.AuthSchemeSpec { return api.AuthSchemeSpec{ Authentication: map[string]api.AuthenticationSpec{ diff --git a/controllers/authpolicy_status.go b/controllers/authpolicy_status.go index e936f1a10..23e4a573b 100644 --- a/controllers/authpolicy_status.go +++ b/controllers/authpolicy_status.go @@ -3,9 +3,9 @@ package controllers import ( "context" "fmt" + "slices" "github.com/go-logr/logr" - "golang.org/x/exp/slices" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/controllers/kuadrant_status.go b/controllers/kuadrant_status.go index ed1b97726..7142dc9c7 100644 --- a/controllers/kuadrant_status.go +++ b/controllers/kuadrant_status.go @@ -3,9 +3,9 @@ package controllers import ( "context" "fmt" + "slices" "github.com/go-logr/logr" - "golang.org/x/exp/slices" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" diff --git a/controllers/ratelimitpolicy_controller_test.go b/controllers/ratelimitpolicy_controller_test.go index b59dc0fd3..1b055f6c6 100644 --- a/controllers/ratelimitpolicy_controller_test.go +++ b/controllers/ratelimitpolicy_controller_test.go @@ -5,25 +5,25 @@ package controllers import ( "context" "encoding/json" + "strings" "time" + kuadrantv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2" + "github.com/kuadrant/kuadrant-operator/pkg/common" + "github.com/kuadrant/kuadrant-operator/pkg/rlptools" + "github.com/kuadrant/kuadrant-operator/pkg/rlptools/wasm" + limitadorv1alpha1 "github.com/kuadrant/limitador-operator/api/v1alpha1" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" istioclientgoextensionv1alpha1 "istio.io/client-go/pkg/apis/extensions/v1alpha1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" logf "sigs.k8s.io/controller-runtime/pkg/log" gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - - kuadrantv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2" - "github.com/kuadrant/kuadrant-operator/pkg/common" - "github.com/kuadrant/kuadrant-operator/pkg/rlptools" - "github.com/kuadrant/kuadrant-operator/pkg/rlptools/wasm" - limitadorv1alpha1 "github.com/kuadrant/limitador-operator/api/v1alpha1" - "k8s.io/utils/ptr" ) var _ = Describe("RateLimitPolicy controller", func() { @@ -622,6 +622,139 @@ var _ = Describe("RateLimitPolicy controller", func() { }) }) +var _ = Describe("RateLimitPolicy CEL Validations", func() { + var testNamespace string + + BeforeEach(func() { + CreateNamespace(&testNamespace) + }) + + AfterEach(DeleteNamespaceCallback(&testNamespace)) + + Context("Spec TargetRef Validations", func() { + It("Valid policy targeting HTTPRoute", func() { + policy := &kuadrantv1beta2.RateLimitPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy", + Namespace: testNamespace, + }, + Spec: kuadrantv1beta2.RateLimitPolicySpec{ + TargetRef: gatewayapiv1alpha2.PolicyTargetReference{ + Group: "gateway.networking.k8s.io", + Kind: "HTTPRoute", + Name: "my-route", + }, + }, + } + err := k8sClient.Create(context.Background(), policy) + Expect(err).To(BeNil()) + }) + + It("Valid policy targeting Gateway", func() { + policy := &kuadrantv1beta2.RateLimitPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy", + Namespace: testNamespace, + }, + Spec: kuadrantv1beta2.RateLimitPolicySpec{ + TargetRef: gatewayapiv1alpha2.PolicyTargetReference{ + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Name: "my-gw", + }, + }, + } + err := k8sClient.Create(context.Background(), policy) + Expect(err).To(BeNil()) + }) + + It("Invalid Target Ref Group", func() { + policy := &kuadrantv1beta2.RateLimitPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy", + Namespace: testNamespace, + }, + Spec: kuadrantv1beta2.RateLimitPolicySpec{ + TargetRef: gatewayapiv1alpha2.PolicyTargetReference{ + Group: "not-gateway.networking.k8s.io", + Kind: "HTTPRoute", + Name: "my-route", + }, + }, + } + err := k8sClient.Create(context.Background(), policy) + Expect(err).To(Not(BeNil())) + Expect(strings.Contains(err.Error(), "Invalid targetRef.group. The only supported value is 'gateway.networking.k8s.io'")).To(BeTrue()) + }) + + It("Invalid Target Ref Kind", func() { + policy := &kuadrantv1beta2.RateLimitPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy", + Namespace: testNamespace, + }, + Spec: kuadrantv1beta2.RateLimitPolicySpec{ + TargetRef: gatewayapiv1alpha2.PolicyTargetReference{ + Group: "gateway.networking.k8s.io", + Kind: "TCPRoute", + Name: "my-route", + }, + }, + } + err := k8sClient.Create(context.Background(), policy) + Expect(err).To(Not(BeNil())) + Expect(strings.Contains(err.Error(), "Invalid targetRef.kind. The only supported values are 'HTTPRoute' and 'Gateway'")).To(BeTrue()) + }) + }) + + Context("Route Selector Validation", func() { + const ( + gateWayRouteSelectorErrorMessage = "route selectors not supported when targeting a Gateway" + ) + + It("invalid usage of limit route selectors with a gateway targetRef", func() { + policy := &kuadrantv1beta2.RateLimitPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-policy", + Namespace: testNamespace, + }, + Spec: kuadrantv1beta2.RateLimitPolicySpec{ + TargetRef: gatewayapiv1alpha2.PolicyTargetReference{ + Group: "gateway.networking.k8s.io", + Kind: "Gateway", + Name: "my-gw", + }, + Limits: map[string]kuadrantv1beta2.Limit{ + "l1": { + Rates: []kuadrantv1beta2.Rate{ + { + Limit: 1, Duration: 3, Unit: kuadrantv1beta2.TimeUnit("minute"), + }, + }, + RouteSelectors: []kuadrantv1beta2.RouteSelector{ + { + Hostnames: []gatewayapiv1.Hostname{"*.foo.io"}, + Matches: []gatewayapiv1.HTTPRouteMatch{ + { + Path: &gatewayapiv1.HTTPPathMatch{ + Value: ptr.To("/foo"), + }, + }, + }, + }, + }, + }, + }, + }, + } + + err := k8sClient.Create(context.Background(), policy) + Expect(err).To(Not(BeNil())) + Expect(strings.Contains(err.Error(), gateWayRouteSelectorErrorMessage)).To(BeTrue()) + }) + }) +}) + func testRLPIsAvailable(rlpKey client.ObjectKey) func() bool { return func() bool { existingRLP := &kuadrantv1beta2.RateLimitPolicy{} diff --git a/controllers/ratelimitpolicy_istio_wasmplugin.go b/controllers/ratelimitpolicy_istio_wasmplugin.go index 54e58a72b..5802b8d3f 100644 --- a/controllers/ratelimitpolicy_istio_wasmplugin.go +++ b/controllers/ratelimitpolicy_istio_wasmplugin.go @@ -3,9 +3,9 @@ package controllers import ( "context" "fmt" + "slices" "github.com/go-logr/logr" - "golang.org/x/exp/slices" istioextensionsv1alpha1 "istio.io/api/extensions/v1alpha1" istioclientgoextensionv1alpha1 "istio.io/client-go/pkg/apis/extensions/v1alpha1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/controllers/ratelimitpolicy_status.go b/controllers/ratelimitpolicy_status.go index 14e3e079c..5fda71235 100644 --- a/controllers/ratelimitpolicy_status.go +++ b/controllers/ratelimitpolicy_status.go @@ -3,9 +3,9 @@ package controllers import ( "context" "fmt" + "slices" "github.com/go-logr/logr" - "golang.org/x/exp/slices" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/go.mod b/go.mod index b0641888b..148a053ba 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,6 @@ require ( github.com/onsi/ginkgo/v2 v2.12.0 github.com/onsi/gomega v1.28.0 go.uber.org/zap v1.26.0 - golang.org/x/exp v0.0.0-20231006140011-7918f672742d golang.org/x/sync v0.4.0 google.golang.org/protobuf v1.31.0 gotest.tools v2.2.0+incompatible @@ -139,6 +138,7 @@ require ( go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.14.0 // indirect + golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.13.0 // indirect golang.org/x/sys v0.14.1-0.20231108175955-e4099bfacb8c // indirect diff --git a/pkg/common/apimachinery_status_conditions.go b/pkg/common/apimachinery_status_conditions.go index 68ef12537..a0f8640be 100644 --- a/pkg/common/apimachinery_status_conditions.go +++ b/pkg/common/apimachinery_status_conditions.go @@ -2,9 +2,9 @@ package common import ( "encoding/json" + "slices" "sort" - "golang.org/x/exp/slices" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) diff --git a/pkg/common/common.go b/pkg/common/common.go index e201d8b6f..20a8b8496 100644 --- a/pkg/common/common.go +++ b/pkg/common/common.go @@ -18,9 +18,9 @@ package common import ( "fmt" + "slices" "strings" - "golang.org/x/exp/slices" "sigs.k8s.io/controller-runtime/pkg/client" gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" diff --git a/pkg/common/gatewayapi_utils.go b/pkg/common/gatewayapi_utils.go index ee19d8b73..229a8f3a4 100644 --- a/pkg/common/gatewayapi_utils.go +++ b/pkg/common/gatewayapi_utils.go @@ -5,9 +5,9 @@ import ( "encoding/json" "fmt" "reflect" + "slices" "strings" - "golang.org/x/exp/slices" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/types" diff --git a/pkg/common/gatewayapi_utils_test.go b/pkg/common/gatewayapi_utils_test.go index eb4f24a70..464ad0e94 100644 --- a/pkg/common/gatewayapi_utils_test.go +++ b/pkg/common/gatewayapi_utils_test.go @@ -6,9 +6,9 @@ import ( "context" "fmt" "reflect" + "slices" "testing" - "golang.org/x/exp/slices" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime"