From 2670b85b38107e0d677551f852bcd1f12f523f5b Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Tue, 22 Oct 2024 13:41:35 +0200 Subject: [PATCH] Prepare AuthPolicy type for the merge strategy * Structure of named patterns changed from `patterns: map[string][]PatternExpression` to `patterns: map[string]{allOf: []PatternExpression}`. * `spec.response.success.dynamicMetadata` field renamed `spec.response.success.filters`, documented as meant for exporting data to other filters managed by Kuadrant only. Signed-off-by: Guilherme Cassolato --- api/v1/merge_strategies.go | 22 +- api/v1beta3/authpolicy_types.go | 709 +++++++++++++----- api/v1beta3/ratelimitpolicy_types.go | 34 +- api/v1beta3/topology.go | 39 - api/v1beta3/zz_generated.deepcopy.go | 385 +++++++--- ...adrant-operator.clusterserviceversion.yaml | 38 +- .../manifests/kuadrant.io_authpolicies.yaml | 299 ++++---- .../templates/manifests.yaml | 335 +++++---- .../crd/bases/kuadrant.io_authpolicies.yaml | 299 ++++---- config/rbac/role.yaml | 36 - controllers/authpolicy_authconfig.go | 48 +- controllers/authpolicy_controller.go | 36 - ...thpolicy_envoysecuritypolicy_controller.go | 209 ------ ...cy_istio_authorizationpolicy_controller.go | 367 --------- ...thpolicy_istio_authorizationpolicy_test.go | 345 --------- ...ecuritypolicy_referencegrant_controller.go | 166 ---- controllers/limitador_limits_reconciler.go | 6 +- controllers/ratelimit_workflow.go | 6 +- controllers/ratelimitpolicy_status_updater.go | 4 +- controllers/test_common.go | 54 -- main.go | 48 -- pkg/library/mappers/httproute.go | 92 --- pkg/library/mappers/httproute_test.go | 137 ---- .../authpolicy/authpolicy_controller_test.go | 362 +++++---- .../target_status_controller_test.go | 58 +- tests/commons.go | 22 +- ...icy_envoysecuritypolicy_controller_test.go | 276 ------- ...typolicy_referencegrant_controller_test.go | 271 ------- ...icy_controller_authorizationpolicy_test.go | 328 -------- 29 files changed, 1633 insertions(+), 3398 deletions(-) delete mode 100644 api/v1beta3/topology.go delete mode 100644 controllers/authpolicy_envoysecuritypolicy_controller.go delete mode 100644 controllers/authpolicy_istio_authorizationpolicy_controller.go delete mode 100644 controllers/authpolicy_istio_authorizationpolicy_test.go delete mode 100644 controllers/envoysecuritypolicy_referencegrant_controller.go delete mode 100644 pkg/library/mappers/httproute.go delete mode 100644 pkg/library/mappers/httproute_test.go delete mode 100644 tests/envoygateway/authpolicy_envoysecuritypolicy_controller_test.go delete mode 100644 tests/envoygateway/envoysecuritypolicy_referencegrant_controller_test.go delete mode 100644 tests/istio/authpolicy_controller_authorizationpolicy_test.go diff --git a/api/v1/merge_strategies.go b/api/v1/merge_strategies.go index 09977500f..f169c5f73 100644 --- a/api/v1/merge_strategies.go +++ b/api/v1/merge_strategies.go @@ -32,9 +32,20 @@ const ( PolicyRuleMergeStrategy = "merge" ) -type MergeableRule struct { - Spec any - Source string +// NewMergeableRule creates a new MergeableRule with a default source if the rule does not have one. +func NewMergeableRule(rule MergeableRule, defaultSource string) MergeableRule { + if rule.GetSource() == "" { + return rule.WithSource(defaultSource) + } + return rule +} + +// MergeableRule is a policy rule that contains a spec which can be traced back to its source, +// i.e. to the policy where the rule spec was defined. +type MergeableRule interface { + GetSpec() any + GetSource() string + WithSource(string) MergeableRule } // +kubebuilder:object:generate=false @@ -99,10 +110,7 @@ func PolicyRuleDefaultsMergeStrategy(source, target machinery.Policy) machinery. // add extra rules from the source for ruleID, rule := range sourceMergeablePolicy.Rules() { if _, ok := targetMergeablePolicy.Rules()[ruleID]; !ok { - rules[ruleID] = MergeableRule{ - Spec: rule.Spec, - Source: source.GetLocator(), - } + rules[ruleID] = rule.WithSource(source.GetLocator()) } } diff --git a/api/v1beta3/authpolicy_types.go b/api/v1beta3/authpolicy_types.go index 032ba57fa..1fae7565a 100644 --- a/api/v1beta3/authpolicy_types.go +++ b/api/v1beta3/authpolicy_types.go @@ -1,137 +1,604 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package v1beta3 import ( - "context" + "encoding/json" + "fmt" + "strings" "github.com/go-logr/logr" "github.com/google/go-cmp/cmp" - authorinoapi "github.com/kuadrant/authorino/api/v1beta2" + authorinov1beta2 "github.com/kuadrant/authorino/api/v1beta2" + "github.com/kuadrant/policy-machinery/machinery" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/controller-runtime/pkg/client" gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + kuadrantv1 "github.com/kuadrant/kuadrant-operator/api/v1" kuadrantgatewayapi "github.com/kuadrant/kuadrant-operator/pkg/library/gatewayapi" "github.com/kuadrant/kuadrant-operator/pkg/library/kuadrant" "github.com/kuadrant/kuadrant-operator/pkg/library/utils" ) -var ( - AuthPolicyGVK schema.GroupVersionKind = schema.GroupVersionKind{ - Group: GroupVersion.Group, - Version: GroupVersion.Version, - Kind: "AuthPolicy", - } -) - const ( + // TODO: remove after fixing the integration tests that still depend on these AuthPolicyBackReferenceAnnotationName = "kuadrant.io/authpolicies" AuthPolicyDirectReferenceAnnotationName = "kuadrant.io/authpolicy" ) +var ( + AuthPolicyGroupKind = schema.GroupKind{Group: SchemeGroupVersion.Group, Kind: "AuthPolicy"} + AuthPoliciesResource = SchemeGroupVersion.WithResource("authpolicies") +) + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:metadata:labels="gateway.networking.k8s.io/policy=inherited" +// +kubebuilder:printcolumn:name="Accepted",type=string,JSONPath=`.status.conditions[?(@.type=="Accepted")].status`,description="AuthPolicy Accepted",priority=2 +// +kubebuilder:printcolumn:name="Enforced",type=string,JSONPath=`.status.conditions[?(@.type=="Enforced")].status`,description="AuthPolicy Enforced",priority=2 +// +kubebuilder:printcolumn:name="TargetKind",type="string",JSONPath=".spec.targetRef.kind",description="Kind of the object to which the policy aaplies",priority=2 +// +kubebuilder:printcolumn:name="TargetName",type="string",JSONPath=".spec.targetRef.name",description="Name of the object to which the policy applies",priority=2 +// +kubebuilder:printcolumn:name="TargetSection",type="string",JSONPath=".spec.targetRef.sectionName",description="Name of the section within the object to which the policy applies ",priority=2 +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" + +// AuthPolicy enables authentication and authorization for service workloads in a Gateway API network +type AuthPolicy struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec AuthPolicySpec `json:"spec,omitempty"` + Status AuthPolicyStatus `json:"status,omitempty"` +} + +var _ machinery.Policy = &AuthPolicy{} + +func (p *AuthPolicy) GetNamespace() string { + return p.Namespace +} + +func (p *AuthPolicy) GetName() string { + return p.Name +} + +func (p *AuthPolicy) GetLocator() string { + return machinery.LocatorFromObject(p) +} + +// TODO: remove +func (p *AuthPolicy) IsAtomicOverride() bool { + return p.Spec.Overrides != nil && p.Spec.Overrides.Strategy == kuadrantv1.AtomicMergeStrategy +} + +// DEPRECATED: Use GetTargetRefs instead +func (p *AuthPolicy) GetTargetRef() gatewayapiv1alpha2.LocalPolicyTargetReference { + return p.Spec.TargetRef.LocalPolicyTargetReference +} + +func (p *AuthPolicy) GetTargetRefs() []machinery.PolicyTargetReference { + return []machinery.PolicyTargetReference{ + machinery.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReferenceWithSectionName: p.Spec.TargetRef, + PolicyNamespace: p.Namespace, + }, + } +} + +func (p *AuthPolicy) GetMergeStrategy() machinery.MergeStrategy { + if spec := p.Spec.Defaults; spec != nil { + return kuadrantv1.DefaultsMergeStrategy(spec.Strategy) + } + if spec := p.Spec.Overrides; spec != nil { + return kuadrantv1.OverridesMergeStrategy(spec.Strategy) + } + return kuadrantv1.AtomicDefaultsMergeStrategy +} + +func (p *AuthPolicy) Merge(other machinery.Policy) machinery.Policy { + source, ok := other.(*AuthPolicy) + if !ok { + return p + } + return source.GetMergeStrategy()(source, p) +} + +var _ kuadrantv1.MergeablePolicy = &AuthPolicy{} + +func (p *AuthPolicy) Empty() bool { + return p.Spec.Proper().AuthScheme == nil +} + +func (p *AuthPolicy) Rules() map[string]kuadrantv1.MergeableRule { + rules := make(map[string]kuadrantv1.MergeableRule) + policyLocator := p.GetLocator() + spec := p.Spec.Proper() + + for ruleID := range spec.NamedPatterns { + rule := spec.NamedPatterns[ruleID] + rules[fmt.Sprintf("patterns#%s", ruleID)] = kuadrantv1.NewMergeableRule(&rule, policyLocator) + } + + for ruleID := range spec.Conditions { + rule := spec.Conditions[ruleID] + rules[fmt.Sprintf("conditions#%d", ruleID)] = kuadrantv1.NewMergeableRule(&rule, policyLocator) + } + + if spec.AuthScheme == nil { + return rules + } + + for ruleID := range spec.AuthScheme.Authentication { + rule := spec.AuthScheme.Authentication[ruleID] + rules[fmt.Sprintf("authentication#%s", ruleID)] = kuadrantv1.NewMergeableRule(&rule, policyLocator) + } + + for ruleID := range spec.AuthScheme.Metadata { + rule := spec.AuthScheme.Metadata[ruleID] + rules[fmt.Sprintf("metadata#%s", ruleID)] = kuadrantv1.NewMergeableRule(&rule, policyLocator) + } + + for ruleID := range spec.AuthScheme.Authorization { + rule := spec.AuthScheme.Authorization[ruleID] + rules[fmt.Sprintf("authorization#%s", ruleID)] = kuadrantv1.NewMergeableRule(&rule, policyLocator) + } + + for ruleID := range spec.AuthScheme.Callbacks { + rule := spec.AuthScheme.Callbacks[ruleID] + rules[fmt.Sprintf("callbacks#%s", ruleID)] = kuadrantv1.NewMergeableRule(&rule, policyLocator) + } + + if spec.AuthScheme.Response == nil { + return rules + } + + { + rule := spec.AuthScheme.Response.Unauthenticated + rules["response.unauthenticated#"] = kuadrantv1.NewMergeableRule(rule, policyLocator) + } + { + rule := spec.AuthScheme.Response.Unauthorized + rules["response.unauthorized#"] = kuadrantv1.NewMergeableRule(rule, policyLocator) + } + + for ruleID := range spec.AuthScheme.Response.Success.Headers { + rule := spec.AuthScheme.Response.Success.Headers[ruleID] + rules[fmt.Sprintf("response.success.headers#%s", ruleID)] = kuadrantv1.NewMergeableRule(&rule, policyLocator) + } + + for ruleID := range spec.AuthScheme.Response.Success.DynamicMetadata { + rule := spec.AuthScheme.Response.Success.DynamicMetadata[ruleID] + rules[fmt.Sprintf("response.success.metadata#%s", ruleID)] = kuadrantv1.NewMergeableRule(&rule, policyLocator) + } + + return rules +} + +func (p *AuthPolicy) SetRules(rules map[string]kuadrantv1.MergeableRule) { + ensureNamedPatterns := func() { + if p.Spec.Proper().NamedPatterns == nil { + p.Spec.Proper().NamedPatterns = make(map[string]MergeablePatternExpressions) + } + } + + ensureAuthScheme := func() { + if p.Spec.Proper().AuthScheme == nil { + p.Spec.Proper().AuthScheme = &AuthSchemeSpec{} + } + } + + ensureAuthentication := func() { + ensureAuthScheme() + if p.Spec.Proper().AuthScheme.Authentication == nil { + p.Spec.Proper().AuthScheme.Authentication = make(map[string]MergeableAuthenticationSpec) + } + } + + ensureMetadata := func() { + ensureAuthScheme() + if p.Spec.Proper().AuthScheme.Metadata == nil { + p.Spec.Proper().AuthScheme.Metadata = make(map[string]MergeableMetadataSpec) + } + } + + ensureAuthorization := func() { + ensureAuthScheme() + if p.Spec.Proper().AuthScheme.Authorization == nil { + p.Spec.Proper().AuthScheme.Authorization = make(map[string]MergeableAuthorizationSpec) + } + } + + ensureResponse := func() { + ensureAuthScheme() + if p.Spec.Proper().AuthScheme.Response == nil { + p.Spec.Proper().AuthScheme.Response = &MergeableResponseSpec{} + } + } + + ensureResponseSuccessHeaders := func() { + ensureResponse() + if p.Spec.Proper().AuthScheme.Response.Success.Headers == nil { + p.Spec.Proper().AuthScheme.Response.Success.Headers = make(map[string]MergeableHeaderSuccessResponseSpec) + } + } + + ensureResponseSuccessDynamicMetadata := func() { + ensureResponse() + if p.Spec.Proper().AuthScheme.Response.Success.DynamicMetadata == nil { + p.Spec.Proper().AuthScheme.Response.Success.DynamicMetadata = make(map[string]MergeableSuccessResponseSpec) + } + } + + ensureCallbacks := func() { + ensureAuthScheme() + if p.Spec.Proper().AuthScheme.Callbacks == nil { + p.Spec.Proper().AuthScheme.Callbacks = make(map[string]MergeableCallbackSpec) + } + } + + for id := range rules { + rule := rules[id] + parts := strings.SplitN(id, "#", 2) + group := parts[0] + ruleID := parts[len(parts)-1] + + if strings.HasPrefix(group, "response.") { + ensureResponse() + } + + switch group { + case "patterns": + ensureNamedPatterns() + p.Spec.Proper().NamedPatterns[ruleID] = *rule.(*MergeablePatternExpressions) + case "conditions": + p.Spec.Proper().Conditions = append(p.Spec.Proper().Conditions, *rule.(*MergeablePatternExpressionOrRef)) + case "authentication": + ensureAuthentication() + p.Spec.Proper().AuthScheme.Authentication[ruleID] = *rule.(*MergeableAuthenticationSpec) + case "metadata": + ensureMetadata() + p.Spec.Proper().AuthScheme.Metadata[ruleID] = *rule.(*MergeableMetadataSpec) + case "authorization": + ensureAuthorization() + p.Spec.Proper().AuthScheme.Authorization[ruleID] = *rule.(*MergeableAuthorizationSpec) + case "response.unauthenticated": + ensureResponse() + p.Spec.Proper().AuthScheme.Response.Unauthenticated = rule.(*MergeableDenyWithSpec) + case "response.unauthorized": + ensureResponse() + p.Spec.Proper().AuthScheme.Response.Unauthorized = rule.(*MergeableDenyWithSpec) + case "response.success.headers": + ensureResponseSuccessHeaders() + p.Spec.Proper().AuthScheme.Response.Success.Headers[ruleID] = *rule.(*MergeableHeaderSuccessResponseSpec) + case "response.success.metadata": + ensureResponseSuccessDynamicMetadata() + p.Spec.Proper().AuthScheme.Response.Success.DynamicMetadata[ruleID] = *rule.(*MergeableSuccessResponseSpec) + case "callbacks": + ensureCallbacks() + p.Spec.Proper().AuthScheme.Callbacks[ruleID] = *rule.(*MergeableCallbackSpec) + } + } +} + +// DEPRECATED. impl: kuadrant.Policy +func (p *AuthPolicy) GetStatus() kuadrantgatewayapi.PolicyStatus { + return &p.Status +} + +// DEPRECATED. impl: kuadrant.Policy +func (p *AuthPolicy) PolicyClass() kuadrantgatewayapi.PolicyClass { + return kuadrantgatewayapi.InheritedPolicy +} + +// DEPRECATED. impl: kuadrant.Policy +func (p *AuthPolicy) GetWrappedNamespace() gatewayapiv1.Namespace { + return gatewayapiv1.Namespace(p.GetNamespace()) +} + +// DEPRECATED. impl: kuadrant.Policy +func (p *AuthPolicy) GetRulesHostnames() []string { + return []string{} +} + +// DEPRECATED. impl: kuadrant.Policy +func (p *AuthPolicy) Kind() string { + return AuthPolicyGroupKind.Kind +} + +// TODO: remove +func (p *AuthPolicy) BackReferenceAnnotationName() string { + return AuthPolicyBackReferenceAnnotationName +} + +// TODO: remove +func (p *AuthPolicy) DirectReferenceAnnotationName() string { + return AuthPolicyDirectReferenceAnnotationName +} + +// TODO: remove +func (p *AuthPolicy) TargetProgrammedGatewaysOnly() bool { + return true +} + +// +kubebuilder:validation:XValidation:rule="!(has(self.defaults) && has(self.rules))",message="Implicit and explicit defaults are mutually exclusive" +// +kubebuilder:validation:XValidation:rule="!(has(self.defaults) && has(self.overrides))",message="Overrides and explicit defaults are mutually exclusive" +// +kubebuilder:validation:XValidation:rule="!(has(self.overrides) && has(self.rules))",message="Overrides and implicit defaults are mutually exclusive" +type AuthPolicySpec struct { + // Reference to the object to which this policy applies. + // +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.LocalPolicyTargetReferenceWithSectionName `json:"targetRef"` + + // Rules to apply as defaults. Can be overridden by more specific policiy rules lower in the hierarchy and by less specific policy overrides. + // Use one of: defaults, overrides, or bare set of policy rules (implicit defaults). + // +optional + Defaults *MergeableAuthPolicySpec `json:"defaults,omitempty"` + + // Rules to apply as overrides. Override all policy rules lower in the hierarchy. Can be overridden by less specific policy overrides. + // Use one of: defaults, overrides, or bare set of policy rules (implicit defaults). + // +optional + Overrides *MergeableAuthPolicySpec `json:"overrides,omitempty"` + + // Bare set of policy rules (implicit defaults). + // Use one of: defaults, overrides, or bare set of policy rules (implicit defaults). + AuthPolicySpecProper `json:""` +} + +// UnmarshalJSON unmarshals the AuthPolicySpec from JSON byte array. +// This should not be needed, but runtime.DefaultUnstructuredConverter.FromUnstructured does not work well with embedded structs. +func (s *AuthPolicySpec) UnmarshalJSON(j []byte) error { + targetRef := struct { + gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName `json:"targetRef"` + }{} + if err := json.Unmarshal(j, &targetRef); err != nil { + return err + } + s.TargetRef = targetRef.LocalPolicyTargetReferenceWithSectionName + + defaults := &struct { + *MergeableAuthPolicySpec `json:"defaults,omitempty"` + }{} + if err := json.Unmarshal(j, defaults); err != nil { + return err + } + s.Defaults = defaults.MergeableAuthPolicySpec + + overrides := &struct { + *MergeableAuthPolicySpec `json:"overrides,omitempty"` + }{} + if err := json.Unmarshal(j, overrides); err != nil { + return err + } + s.Overrides = overrides.MergeableAuthPolicySpec + + proper := struct { + AuthPolicySpecProper `json:""` + }{} + if err := json.Unmarshal(j, &proper); err != nil { + return err + } + s.AuthPolicySpecProper = proper.AuthPolicySpecProper + + return nil +} + +func (s *AuthPolicySpec) Proper() *AuthPolicySpecProper { + if s.Defaults != nil { + return &s.Defaults.AuthPolicySpecProper + } + + if s.Overrides != nil { + return &s.Overrides.AuthPolicySpecProper + } + + return &s.AuthPolicySpecProper +} + +type MergeableAuthPolicySpec struct { + // Strategy defines the merge strategy to apply when merging this policy with other policies. + // +kubebuilder:validation:Enum=atomic;merge + // +kubebuilder:default=atomic + Strategy string `json:"strategy,omitempty"` + + AuthPolicySpecProper `json:""` +} + +// AuthPolicySpecProper contains common shared fields for defaults and overrides +type AuthPolicySpecProper struct { + // Named sets of patterns that can be referred in `when` conditions and in pattern-matching authorization policy rules. + // +optional + NamedPatterns map[string]MergeablePatternExpressions `json:"patterns,omitempty"` + + // Overall conditions for the AuthPolicy to be enforced. + // If omitted, the AuthPolicy will be enforced at all requests to the protected routes. + // If present, all conditions must match for the AuthPolicy to be enforced; otherwise, the authorization service skips the AuthPolicy and returns to the auth request with status OK. + // +optional + Conditions []MergeablePatternExpressionOrRef `json:"when,omitempty"` + + // The auth rules of the policy. + // See Authorino's AuthConfig CRD for more details. + AuthScheme *AuthSchemeSpec `json:"rules,omitempty"` +} + type AuthSchemeSpec struct { // Authentication configs. // At least one config MUST evaluate to a valid identity object for the auth request to be successful. // +optional - Authentication map[string]authorinoapi.AuthenticationSpec `json:"authentication,omitempty"` + // +kubebuilder:validation:MaxProperties=10 + Authentication map[string]MergeableAuthenticationSpec `json:"authentication,omitempty"` // Metadata sources. // Authorino fetches auth metadata as JSON from sources specified in this config. // +optional - Metadata map[string]authorinoapi.MetadataSpec `json:"metadata,omitempty"` + // +kubebuilder:validation:MaxProperties=10 + Metadata map[string]MergeableMetadataSpec `json:"metadata,omitempty"` // Authorization policies. // All policies MUST evaluate to "allowed = true" for the auth request be successful. // +optional - Authorization map[string]authorinoapi.AuthorizationSpec `json:"authorization,omitempty"` + // +kubebuilder:validation:MaxProperties=10 + Authorization map[string]MergeableAuthorizationSpec `json:"authorization,omitempty"` // Response items. // Authorino builds custom responses to the client of the auth request. // +optional - Response *ResponseSpec `json:"response,omitempty"` + Response *MergeableResponseSpec `json:"response,omitempty"` // Callback functions. // Authorino sends callbacks at the end of the auth pipeline to the endpoints specified in this config. // +optional - Callbacks map[string]authorinoapi.CallbackSpec `json:"callbacks,omitempty"` + // +kubebuilder:validation:MaxProperties=10 + Callbacks map[string]MergeableCallbackSpec `json:"callbacks,omitempty"` +} + +type MergeablePatternExpressions struct { + authorinov1beta2.PatternExpressions `json:"allOf"` + Source string `json:"-"` +} + +func (r *MergeablePatternExpressions) GetSpec() any { return r.PatternExpressions } +func (r *MergeablePatternExpressions) GetSource() string { return r.Source } +func (r *MergeablePatternExpressions) WithSource(source string) kuadrantv1.MergeableRule { + r.Source = source + return r } -type ResponseSpec struct { +type MergeablePatternExpressionOrRef struct { + authorinov1beta2.PatternExpressionOrRef `json:",inline"` + Source string `json:"-"` +} + +func (r *MergeablePatternExpressionOrRef) GetSpec() any { return r.PatternExpressionOrRef } +func (r *MergeablePatternExpressionOrRef) GetSource() string { return r.Source } +func (r *MergeablePatternExpressionOrRef) WithSource(source string) kuadrantv1.MergeableRule { + r.Source = source + return r +} + +type MergeableAuthenticationSpec struct { + authorinov1beta2.AuthenticationSpec `json:",inline"` + Source string `json:"-"` +} + +func (r *MergeableAuthenticationSpec) GetSpec() any { return r.AuthenticationSpec } +func (r *MergeableAuthenticationSpec) GetSource() string { return r.Source } +func (r *MergeableAuthenticationSpec) WithSource(source string) kuadrantv1.MergeableRule { + r.Source = source + return r +} + +type MergeableMetadataSpec struct { + authorinov1beta2.MetadataSpec `json:",inline"` + Source string `json:"-"` +} + +func (r *MergeableMetadataSpec) GetSpec() any { return r.MetadataSpec } +func (r *MergeableMetadataSpec) GetSource() string { return r.Source } +func (r *MergeableMetadataSpec) WithSource(source string) kuadrantv1.MergeableRule { + r.Source = source + return r +} + +type MergeableAuthorizationSpec struct { + authorinov1beta2.AuthorizationSpec `json:",inline"` + Source string `json:"-"` +} + +func (r *MergeableAuthorizationSpec) GetSpec() any { return r.AuthorizationSpec } +func (r *MergeableAuthorizationSpec) GetSource() string { return r.Source } +func (r *MergeableAuthorizationSpec) WithSource(source string) kuadrantv1.MergeableRule { + r.Source = source + return r +} + +// Settings of the custom auth response. +type MergeableResponseSpec struct { // Customizations on the denial status attributes when the request is unauthenticated. // For integration of Authorino via proxy, the proxy must honour the response status attributes specified in this config. // Default: 401 Unauthorized // +optional - Unauthenticated *authorinoapi.DenyWithSpec `json:"unauthenticated,omitempty"` + Unauthenticated *MergeableDenyWithSpec `json:"unauthenticated,omitempty"` // Customizations on the denial status attributes when the request is unauthorized. // For integration of Authorino via proxy, the proxy must honour the response status attributes specified in this config. // Default: 403 Forbidden // +optional - Unauthorized *authorinoapi.DenyWithSpec `json:"unauthorized,omitempty"` + Unauthorized *MergeableDenyWithSpec `json:"unauthorized,omitempty"` // Response items to be included in the auth response when the request is authenticated and authorized. // For integration of Authorino via proxy, the proxy must use these settings to propagate dynamic metadata and/or inject data in the request. // +optional - Success WrappedSuccessResponseSpec `json:"success,omitempty"` + Success MergeableWrappedSuccessResponseSpec `json:"success,omitempty"` } -type WrappedSuccessResponseSpec struct { - // Custom success response items wrapped as HTTP headers. - // For integration of Authorino via proxy, the proxy must use these settings to inject data in the request. - Headers map[string]HeaderSuccessResponseSpec `json:"headers,omitempty"` - - // Custom success response items wrapped as HTTP headers. - // For integration of Authorino via proxy, the proxy must use these settings to propagate dynamic metadata. - // See https://www.envoyproxy.io/docs/envoy/latest/configuration/advanced/well_known_dynamic_metadata - DynamicMetadata map[string]authorinoapi.SuccessResponseSpec `json:"dynamicMetadata,omitempty"` +type MergeableDenyWithSpec struct { + authorinov1beta2.DenyWithSpec `json:",inline"` + Source string `json:"-"` } -type HeaderSuccessResponseSpec struct { - authorinoapi.SuccessResponseSpec `json:""` +func (r *MergeableDenyWithSpec) GetSpec() any { return r.DenyWithSpec } +func (r *MergeableDenyWithSpec) GetSource() string { return r.Source } +func (r *MergeableDenyWithSpec) WithSource(source string) kuadrantv1.MergeableRule { + r.Source = source + return r } -// Mutual Exclusivity Validation -// +kubebuilder:validation:XValidation:rule="!(has(self.defaults) && (has(self.patterns) || has(self.when) || has(self.rules)))",message="Implicit and explicit defaults are mutually exclusive" -// +kubebuilder:validation:XValidation:rule="!(has(self.overrides) && (has(self.patterns) || has(self.when) || has(self.rules)))",message="Implicit defaults and explicit overrides are mutually exclusive" -// +kubebuilder:validation:XValidation:rule="!(has(self.overrides) && has(self.defaults))",message="Explicit overrides and explicit defaults are mutually exclusive" -// +kubebuilder:validation:XValidation:rule="!(has(self.overrides) && self.targetRef.kind == 'HTTPRoute')",message="Overrides are not allowed for policies targeting a HTTPRoute resource" -type AuthPolicySpec 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.LocalPolicyTargetReference `json:"targetRef"` +type MergeableWrappedSuccessResponseSpec struct { + // Custom headers to inject in the request. + Headers map[string]MergeableHeaderSuccessResponseSpec `json:"headers,omitempty"` - // Defaults define explicit default values for this policy and for policies inheriting this policy. - // Defaults are mutually exclusive with implicit defaults defined by AuthPolicyCommonSpec. - // +optional - Defaults *AuthPolicyCommonSpec `json:"defaults,omitempty"` + // Custom data made available to other filters managed by Kuadrant (i.e. Rate Limit) + DynamicMetadata map[string]MergeableSuccessResponseSpec `json:"filters,omitempty"` +} - // Overrides define explicit override values for this policy. - // Overrides are mutually exclusive with explicit and implicit defaults defined by AuthPolicyCommonSpec. - // +optional - Overrides *AuthPolicyCommonSpec `json:"overrides,omitempty"` +type MergeableHeaderSuccessResponseSpec struct { + authorinov1beta2.HeaderSuccessResponseSpec `json:",inline"` + Source string `json:"-"` +} - // AuthPolicyCommonSpec defines implicit default values for this policy and for policies inheriting this policy. - // AuthPolicyCommonSpec is mutually exclusive with explicit defaults defined by Defaults. - AuthPolicyCommonSpec `json:""` +func (r *MergeableHeaderSuccessResponseSpec) GetSpec() any { return r.HeaderSuccessResponseSpec } +func (r *MergeableHeaderSuccessResponseSpec) GetSource() string { return r.Source } +func (r *MergeableHeaderSuccessResponseSpec) WithSource(source string) kuadrantv1.MergeableRule { + r.Source = source + return r } -// AuthPolicyCommonSpec contains common shared fields for defaults and overrides -type AuthPolicyCommonSpec struct { - // Named sets of patterns that can be referred in `when` conditions and in pattern-matching authorization policy rules. - // +optional - NamedPatterns map[string]authorinoapi.PatternExpressions `json:"patterns,omitempty"` +type MergeableSuccessResponseSpec struct { + authorinov1beta2.SuccessResponseSpec `json:",inline"` + Source string `json:"-"` +} - // Overall conditions for the AuthPolicy to be enforced. - // If omitted, the AuthPolicy will be enforced at all requests to the protected routes. - // If present, all conditions must match for the AuthPolicy to be enforced; otherwise, the authorization service skips the AuthPolicy and returns to the auth request with status OK. - // +optional - Conditions []authorinoapi.PatternExpressionOrRef `json:"when,omitempty"` +func (r *MergeableSuccessResponseSpec) GetSpec() any { return r.SuccessResponseSpec } +func (r *MergeableSuccessResponseSpec) GetSource() string { return r.Source } +func (r *MergeableSuccessResponseSpec) WithSource(source string) kuadrantv1.MergeableRule { + r.Source = source + return r +} - // The auth rules of the policy. - // See Authorino's AuthConfig CRD for more details. - AuthScheme *AuthSchemeSpec `json:"rules,omitempty"` +type MergeableCallbackSpec struct { + authorinov1beta2.CallbackSpec `json:",inline"` + Source string `json:"-"` +} + +func (r *MergeableCallbackSpec) GetSpec() any { return r.CallbackSpec } +func (r *MergeableCallbackSpec) GetSource() string { return r.Source } +func (r *MergeableCallbackSpec) WithSource(source string) kuadrantv1.MergeableRule { + r.Source = source + return r } type AuthPolicyStatus struct { @@ -171,82 +638,6 @@ func (s *AuthPolicyStatus) GetConditions() []metav1.Condition { return s.Conditions } -var _ kuadrant.Policy = &AuthPolicy{} -var _ kuadrant.Referrer = &AuthPolicy{} - -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status -// +kubebuilder:metadata:labels="gateway.networking.k8s.io/policy=inherited" -// +kubebuilder:printcolumn:name="Accepted",type=string,JSONPath=`.status.conditions[?(@.type=="Accepted")].status`,description="AuthPolicy Accepted",priority=2 -// +kubebuilder:printcolumn:name="Enforced",type=string,JSONPath=`.status.conditions[?(@.type=="Enforced")].status`,description="AuthPolicy Enforced",priority=2 -// +kubebuilder:printcolumn:name="TargetRefKind",type="string",JSONPath=".spec.targetRef.kind",description="Type of the referenced Gateway API resource",priority=2 -// +kubebuilder:printcolumn:name="TargetRefName",type="string",JSONPath=".spec.targetRef.name",description="Name of the referenced Gateway API resource",priority=2 -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" - -// AuthPolicy enables authentication and authorization for service workloads in a Gateway API network -type AuthPolicy struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec AuthPolicySpec `json:"spec,omitempty"` - Status AuthPolicyStatus `json:"status,omitempty"` -} - -func (ap *AuthPolicy) IsAtomicOverride() bool { - return ap.Spec.Overrides != nil -} - -func (ap *AuthPolicy) GetTargetRef() gatewayapiv1alpha2.LocalPolicyTargetReference { - return ap.Spec.TargetRef -} - -func (ap *AuthPolicy) GetStatus() kuadrantgatewayapi.PolicyStatus { - return &ap.Status -} - -func (ap *AuthPolicy) GetWrappedNamespace() gatewayapiv1.Namespace { - return gatewayapiv1.Namespace(ap.Namespace) -} - -// GetRulesHostnames -// in v1beta2 this returned the list of route selectors -// in v1beta3 this should work with section name, once implemented. -func (ap *AuthPolicy) GetRulesHostnames() []string { - return make([]string, 0) -} - -func (ap *AuthPolicy) Kind() string { - return NewAuthPolicyType().GetGVK().Kind -} - -func (ap *AuthPolicy) TargetProgrammedGatewaysOnly() bool { - return true -} - -func (ap *AuthPolicy) PolicyClass() kuadrantgatewayapi.PolicyClass { - return kuadrantgatewayapi.InheritedPolicy -} - -func (ap *AuthPolicy) BackReferenceAnnotationName() string { - return NewAuthPolicyType().BackReferenceAnnotationName() -} - -func (ap *AuthPolicy) DirectReferenceAnnotationName() string { - return NewAuthPolicyType().DirectReferenceAnnotationName() -} - -func (ap *AuthPolicySpec) CommonSpec() *AuthPolicyCommonSpec { - if ap.Defaults != nil { - return ap.Defaults - } - - if ap.Overrides != nil { - return ap.Overrides - } - - return &ap.AuthPolicyCommonSpec -} - //+kubebuilder:object:root=true // AuthPolicyList contains a list of AuthPolicy @@ -256,47 +647,13 @@ type AuthPolicyList struct { Items []AuthPolicy `json:"items"` } +// DEPRECATED. impl: kuadrant.PolicyList func (l *AuthPolicyList) GetItems() []kuadrant.Policy { return utils.Map(l.Items, func(item AuthPolicy) kuadrant.Policy { return &item }) } -type authPolicyType struct{} - -func NewAuthPolicyType() kuadrantgatewayapi.PolicyType { - return &authPolicyType{} -} - -func (a authPolicyType) GetGVK() schema.GroupVersionKind { - return AuthPolicyGVK -} -func (a authPolicyType) GetInstance() client.Object { - return &AuthPolicy{ - TypeMeta: metav1.TypeMeta{ - Kind: AuthPolicyGVK.Kind, - APIVersion: GroupVersion.String(), - }, - } -} - -func (a authPolicyType) GetList(ctx context.Context, cl client.Client, listOpts ...client.ListOption) ([]kuadrantgatewayapi.Policy, error) { - list := &AuthPolicyList{} - err := cl.List(ctx, list, listOpts...) - if err != nil { - return nil, err - } - return utils.Map(list.Items, func(p AuthPolicy) kuadrantgatewayapi.Policy { return &p }), nil -} - -func (a authPolicyType) BackReferenceAnnotationName() string { - return AuthPolicyBackReferenceAnnotationName -} - -func (a authPolicyType) DirectReferenceAnnotationName() string { - return AuthPolicyDirectReferenceAnnotationName -} - func init() { SchemeBuilder.Register(&AuthPolicy{}, &AuthPolicyList{}) } diff --git a/api/v1beta3/ratelimitpolicy_types.go b/api/v1beta3/ratelimitpolicy_types.go index 4c4adecdf..772952942 100644 --- a/api/v1beta3/ratelimitpolicy_types.go +++ b/api/v1beta3/ratelimitpolicy_types.go @@ -123,17 +123,11 @@ func (p *RateLimitPolicy) Empty() bool { func (p *RateLimitPolicy) Rules() map[string]kuadrantv1.MergeableRule { rules := make(map[string]kuadrantv1.MergeableRule) + policyLocator := p.GetLocator() for ruleID := range p.Spec.Proper().Limits { limit := p.Spec.Proper().Limits[ruleID] - origin := limit.Origin - if origin == "" { - origin = p.GetLocator() - } - rules[ruleID] = kuadrantv1.MergeableRule{ - Spec: limit, - Source: origin, - } + rules[ruleID] = kuadrantv1.NewMergeableRule(&limit, policyLocator) } return rules @@ -145,10 +139,7 @@ func (p *RateLimitPolicy) SetRules(rules map[string]kuadrantv1.MergeableRule) { } for ruleID := range rules { - rule := rules[ruleID] - limit := rule.Spec.(Limit) - limit.Origin = rule.Source - p.Spec.Proper().Limits[ruleID] = limit + p.Spec.Proper().Limits[ruleID] = *rules[ruleID].(*Limit) } } @@ -293,8 +284,8 @@ type Limit struct { // +optional Rates []Rate `json:"rates,omitempty"` - // origin stores the resource where the limit is originally defined (internal use) - Origin string `json:"-"` + // Source stores the locator of the policy where the limit is orignaly defined (internal use) + Source string `json:"-"` } func (l Limit) CountersAsStringList() []string { @@ -304,6 +295,21 @@ func (l Limit) CountersAsStringList() []string { return utils.Map(l.Counters, func(counter ContextSelector) string { return string(counter) }) } +var _ kuadrantv1.MergeableRule = &Limit{} + +func (l *Limit) GetSpec() any { + return l +} + +func (l *Limit) GetSource() string { + return l.Source +} + +func (l *Limit) WithSource(source string) kuadrantv1.MergeableRule { + l.Source = source + return l +} + // +kubebuilder:validation:Enum:=second;minute;hour;day type TimeUnit string diff --git a/api/v1beta3/topology.go b/api/v1beta3/topology.go deleted file mode 100644 index 7543ae1f7..000000000 --- a/api/v1beta3/topology.go +++ /dev/null @@ -1,39 +0,0 @@ -package v1beta3 - -// Contains of this file allow the AuthPolicy and RateLimitPolicy to adhere to the machinery.Policy interface - -import ( - "github.com/kuadrant/policy-machinery/machinery" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -var ( - AuthPoliciesResource = GroupVersion.WithResource("authpolicies") - - AuthPolicyGroupKind = schema.GroupKind{Group: GroupVersion.Group, Kind: "AuthPolicy"} -) - -var _ machinery.Policy = &AuthPolicy{} - -func (ap *AuthPolicy) GetTargetRefs() []machinery.PolicyTargetReference { - return []machinery.PolicyTargetReference{ - machinery.LocalPolicyTargetReference{ - LocalPolicyTargetReference: ap.Spec.TargetRef, - PolicyNamespace: ap.Namespace, - }, - } -} - -func (ap *AuthPolicy) GetMergeStrategy() machinery.MergeStrategy { - return func(policy machinery.Policy, _ machinery.Policy) machinery.Policy { - return policy - } -} - -func (ap *AuthPolicy) Merge(other machinery.Policy) machinery.Policy { - return other -} - -func (ap *AuthPolicy) GetLocator() string { - return machinery.LocatorFromObject(ap) -} diff --git a/api/v1beta3/zz_generated.deepcopy.go b/api/v1beta3/zz_generated.deepcopy.go index ad41e4958..e54e1a628 100644 --- a/api/v1beta3/zz_generated.deepcopy.go +++ b/api/v1beta3/zz_generated.deepcopy.go @@ -53,49 +53,6 @@ func (in *AuthPolicy) DeepCopyObject() runtime.Object { return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AuthPolicyCommonSpec) DeepCopyInto(out *AuthPolicyCommonSpec) { - *out = *in - if in.NamedPatterns != nil { - in, out := &in.NamedPatterns, &out.NamedPatterns - *out = make(map[string]v1beta2.PatternExpressions, len(*in)) - for key, val := range *in { - var outVal []v1beta2.PatternExpression - if val == nil { - (*out)[key] = nil - } else { - inVal := (*in)[key] - in, out := &inVal, &outVal - *out = make(v1beta2.PatternExpressions, len(*in)) - copy(*out, *in) - } - (*out)[key] = outVal - } - } - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]v1beta2.PatternExpressionOrRef, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.AuthScheme != nil { - in, out := &in.AuthScheme, &out.AuthScheme - *out = new(AuthSchemeSpec) - (*in).DeepCopyInto(*out) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthPolicyCommonSpec. -func (in *AuthPolicyCommonSpec) DeepCopy() *AuthPolicyCommonSpec { - if in == nil { - return nil - } - out := new(AuthPolicyCommonSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AuthPolicyList) DeepCopyInto(out *AuthPolicyList) { *out = *in @@ -131,18 +88,18 @@ func (in *AuthPolicyList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AuthPolicySpec) DeepCopyInto(out *AuthPolicySpec) { *out = *in - out.TargetRef = in.TargetRef + in.TargetRef.DeepCopyInto(&out.TargetRef) if in.Defaults != nil { in, out := &in.Defaults, &out.Defaults - *out = new(AuthPolicyCommonSpec) + *out = new(MergeableAuthPolicySpec) (*in).DeepCopyInto(*out) } if in.Overrides != nil { in, out := &in.Overrides, &out.Overrides - *out = new(AuthPolicyCommonSpec) + *out = new(MergeableAuthPolicySpec) (*in).DeepCopyInto(*out) } - in.AuthPolicyCommonSpec.DeepCopyInto(&out.AuthPolicyCommonSpec) + in.AuthPolicySpecProper.DeepCopyInto(&out.AuthPolicySpecProper) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthPolicySpec. @@ -155,6 +112,40 @@ func (in *AuthPolicySpec) DeepCopy() *AuthPolicySpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AuthPolicySpecProper) DeepCopyInto(out *AuthPolicySpecProper) { + *out = *in + if in.NamedPatterns != nil { + in, out := &in.NamedPatterns, &out.NamedPatterns + *out = make(map[string]MergeablePatternExpressions, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]MergeablePatternExpressionOrRef, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.AuthScheme != nil { + in, out := &in.AuthScheme, &out.AuthScheme + *out = new(AuthSchemeSpec) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthPolicySpecProper. +func (in *AuthPolicySpecProper) DeepCopy() *AuthPolicySpecProper { + if in == nil { + return nil + } + out := new(AuthPolicySpecProper) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AuthPolicyStatus) DeepCopyInto(out *AuthPolicyStatus) { *out = *in @@ -182,33 +173,33 @@ func (in *AuthSchemeSpec) DeepCopyInto(out *AuthSchemeSpec) { *out = *in if in.Authentication != nil { in, out := &in.Authentication, &out.Authentication - *out = make(map[string]v1beta2.AuthenticationSpec, len(*in)) + *out = make(map[string]MergeableAuthenticationSpec, len(*in)) for key, val := range *in { (*out)[key] = *val.DeepCopy() } } if in.Metadata != nil { in, out := &in.Metadata, &out.Metadata - *out = make(map[string]v1beta2.MetadataSpec, len(*in)) + *out = make(map[string]MergeableMetadataSpec, len(*in)) for key, val := range *in { (*out)[key] = *val.DeepCopy() } } if in.Authorization != nil { in, out := &in.Authorization, &out.Authorization - *out = make(map[string]v1beta2.AuthorizationSpec, len(*in)) + *out = make(map[string]MergeableAuthorizationSpec, len(*in)) for key, val := range *in { (*out)[key] = *val.DeepCopy() } } if in.Response != nil { in, out := &in.Response, &out.Response - *out = new(ResponseSpec) + *out = new(MergeableResponseSpec) (*in).DeepCopyInto(*out) } if in.Callbacks != nil { in, out := &in.Callbacks, &out.Callbacks - *out = make(map[string]v1beta2.CallbackSpec, len(*in)) + *out = make(map[string]MergeableCallbackSpec, len(*in)) for key, val := range *in { (*out)[key] = *val.DeepCopy() } @@ -225,22 +216,6 @@ func (in *AuthSchemeSpec) DeepCopy() *AuthSchemeSpec { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HeaderSuccessResponseSpec) DeepCopyInto(out *HeaderSuccessResponseSpec) { - *out = *in - in.SuccessResponseSpec.DeepCopyInto(&out.SuccessResponseSpec) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HeaderSuccessResponseSpec. -func (in *HeaderSuccessResponseSpec) DeepCopy() *HeaderSuccessResponseSpec { - if in == nil { - return nil - } - out := new(HeaderSuccessResponseSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Limit) DeepCopyInto(out *Limit) { *out = *in @@ -271,6 +246,154 @@ func (in *Limit) DeepCopy() *Limit { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MergeableAuthPolicySpec) DeepCopyInto(out *MergeableAuthPolicySpec) { + *out = *in + in.AuthPolicySpecProper.DeepCopyInto(&out.AuthPolicySpecProper) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MergeableAuthPolicySpec. +func (in *MergeableAuthPolicySpec) DeepCopy() *MergeableAuthPolicySpec { + if in == nil { + return nil + } + out := new(MergeableAuthPolicySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MergeableAuthenticationSpec) DeepCopyInto(out *MergeableAuthenticationSpec) { + *out = *in + in.AuthenticationSpec.DeepCopyInto(&out.AuthenticationSpec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MergeableAuthenticationSpec. +func (in *MergeableAuthenticationSpec) DeepCopy() *MergeableAuthenticationSpec { + if in == nil { + return nil + } + out := new(MergeableAuthenticationSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MergeableAuthorizationSpec) DeepCopyInto(out *MergeableAuthorizationSpec) { + *out = *in + in.AuthorizationSpec.DeepCopyInto(&out.AuthorizationSpec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MergeableAuthorizationSpec. +func (in *MergeableAuthorizationSpec) DeepCopy() *MergeableAuthorizationSpec { + if in == nil { + return nil + } + out := new(MergeableAuthorizationSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MergeableCallbackSpec) DeepCopyInto(out *MergeableCallbackSpec) { + *out = *in + in.CallbackSpec.DeepCopyInto(&out.CallbackSpec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MergeableCallbackSpec. +func (in *MergeableCallbackSpec) DeepCopy() *MergeableCallbackSpec { + if in == nil { + return nil + } + out := new(MergeableCallbackSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MergeableDenyWithSpec) DeepCopyInto(out *MergeableDenyWithSpec) { + *out = *in + in.DenyWithSpec.DeepCopyInto(&out.DenyWithSpec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MergeableDenyWithSpec. +func (in *MergeableDenyWithSpec) DeepCopy() *MergeableDenyWithSpec { + if in == nil { + return nil + } + out := new(MergeableDenyWithSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MergeableHeaderSuccessResponseSpec) DeepCopyInto(out *MergeableHeaderSuccessResponseSpec) { + *out = *in + in.HeaderSuccessResponseSpec.DeepCopyInto(&out.HeaderSuccessResponseSpec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MergeableHeaderSuccessResponseSpec. +func (in *MergeableHeaderSuccessResponseSpec) DeepCopy() *MergeableHeaderSuccessResponseSpec { + if in == nil { + return nil + } + out := new(MergeableHeaderSuccessResponseSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MergeableMetadataSpec) DeepCopyInto(out *MergeableMetadataSpec) { + *out = *in + in.MetadataSpec.DeepCopyInto(&out.MetadataSpec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MergeableMetadataSpec. +func (in *MergeableMetadataSpec) DeepCopy() *MergeableMetadataSpec { + if in == nil { + return nil + } + out := new(MergeableMetadataSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MergeablePatternExpressionOrRef) DeepCopyInto(out *MergeablePatternExpressionOrRef) { + *out = *in + in.PatternExpressionOrRef.DeepCopyInto(&out.PatternExpressionOrRef) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MergeablePatternExpressionOrRef. +func (in *MergeablePatternExpressionOrRef) DeepCopy() *MergeablePatternExpressionOrRef { + if in == nil { + return nil + } + out := new(MergeablePatternExpressionOrRef) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MergeablePatternExpressions) DeepCopyInto(out *MergeablePatternExpressions) { + *out = *in + if in.PatternExpressions != nil { + in, out := &in.PatternExpressions, &out.PatternExpressions + *out = make(v1beta2.PatternExpressions, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MergeablePatternExpressions. +func (in *MergeablePatternExpressions) DeepCopy() *MergeablePatternExpressions { + if in == nil { + return nil + } + out := new(MergeablePatternExpressions) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MergeableRateLimitPolicySpec) DeepCopyInto(out *MergeableRateLimitPolicySpec) { *out = *in @@ -287,6 +410,77 @@ func (in *MergeableRateLimitPolicySpec) DeepCopy() *MergeableRateLimitPolicySpec return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MergeableResponseSpec) DeepCopyInto(out *MergeableResponseSpec) { + *out = *in + if in.Unauthenticated != nil { + in, out := &in.Unauthenticated, &out.Unauthenticated + *out = new(MergeableDenyWithSpec) + (*in).DeepCopyInto(*out) + } + if in.Unauthorized != nil { + in, out := &in.Unauthorized, &out.Unauthorized + *out = new(MergeableDenyWithSpec) + (*in).DeepCopyInto(*out) + } + in.Success.DeepCopyInto(&out.Success) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MergeableResponseSpec. +func (in *MergeableResponseSpec) DeepCopy() *MergeableResponseSpec { + if in == nil { + return nil + } + out := new(MergeableResponseSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MergeableSuccessResponseSpec) DeepCopyInto(out *MergeableSuccessResponseSpec) { + *out = *in + in.SuccessResponseSpec.DeepCopyInto(&out.SuccessResponseSpec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MergeableSuccessResponseSpec. +func (in *MergeableSuccessResponseSpec) DeepCopy() *MergeableSuccessResponseSpec { + if in == nil { + return nil + } + out := new(MergeableSuccessResponseSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MergeableWrappedSuccessResponseSpec) DeepCopyInto(out *MergeableWrappedSuccessResponseSpec) { + *out = *in + if in.Headers != nil { + in, out := &in.Headers, &out.Headers + *out = make(map[string]MergeableHeaderSuccessResponseSpec, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } + if in.DynamicMetadata != nil { + in, out := &in.DynamicMetadata, &out.DynamicMetadata + *out = make(map[string]MergeableSuccessResponseSpec, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MergeableWrappedSuccessResponseSpec. +func (in *MergeableWrappedSuccessResponseSpec) DeepCopy() *MergeableWrappedSuccessResponseSpec { + if in == nil { + return nil + } + out := new(MergeableWrappedSuccessResponseSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Rate) DeepCopyInto(out *Rate) { *out = *in @@ -432,32 +626,6 @@ func (in *RateLimitPolicyStatus) DeepCopy() *RateLimitPolicyStatus { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ResponseSpec) DeepCopyInto(out *ResponseSpec) { - *out = *in - if in.Unauthenticated != nil { - in, out := &in.Unauthenticated, &out.Unauthenticated - *out = new(v1beta2.DenyWithSpec) - (*in).DeepCopyInto(*out) - } - if in.Unauthorized != nil { - in, out := &in.Unauthorized, &out.Unauthorized - *out = new(v1beta2.DenyWithSpec) - (*in).DeepCopyInto(*out) - } - in.Success.DeepCopyInto(&out.Success) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResponseSpec. -func (in *ResponseSpec) DeepCopy() *ResponseSpec { - if in == nil { - return nil - } - out := new(ResponseSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WhenCondition) DeepCopyInto(out *WhenCondition) { *out = *in @@ -472,32 +640,3 @@ func (in *WhenCondition) DeepCopy() *WhenCondition { in.DeepCopyInto(out) return out } - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *WrappedSuccessResponseSpec) DeepCopyInto(out *WrappedSuccessResponseSpec) { - *out = *in - if in.Headers != nil { - in, out := &in.Headers, &out.Headers - *out = make(map[string]HeaderSuccessResponseSpec, len(*in)) - for key, val := range *in { - (*out)[key] = *val.DeepCopy() - } - } - if in.DynamicMetadata != nil { - in, out := &in.DynamicMetadata, &out.DynamicMetadata - *out = make(map[string]v1beta2.SuccessResponseSpec, len(*in)) - for key, val := range *in { - (*out)[key] = *val.DeepCopy() - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WrappedSuccessResponseSpec. -func (in *WrappedSuccessResponseSpec) DeepCopy() *WrappedSuccessResponseSpec { - if in == nil { - return nil - } - out := new(WrappedSuccessResponseSpec) - in.DeepCopyInto(out) - return out -} diff --git a/bundle/manifests/kuadrant-operator.clusterserviceversion.yaml b/bundle/manifests/kuadrant-operator.clusterserviceversion.yaml index 0860c05dc..0f266a4b5 100644 --- a/bundle/manifests/kuadrant-operator.clusterserviceversion.yaml +++ b/bundle/manifests/kuadrant-operator.clusterserviceversion.yaml @@ -106,7 +106,7 @@ metadata: capabilities: Basic Install categories: Integration & Delivery containerImage: quay.io/kuadrant/kuadrant-operator:latest - createdAt: "2024-10-22T09:01:33Z" + createdAt: "2024-10-23T14:18:15Z" description: A Kubernetes Operator to manage the lifecycle of the Kuadrant system operators.operatorframework.io/builder: operator-sdk-v1.32.0 operators.operatorframework.io/project_layout: go.kubebuilder.io/v3 @@ -316,18 +316,6 @@ spec: - patch - update - watch - - apiGroups: - - gateway.envoyproxy.io - resources: - - securitypolicies - verbs: - - create - - delete - - get - - list - - patch - - update - - watch - apiGroups: - gateway.networking.k8s.io resources: @@ -379,18 +367,6 @@ spec: - get - patch - update - - apiGroups: - - gateway.networking.k8s.io - resources: - - referencegrants - verbs: - - create - - delete - - get - - list - - patch - - update - - watch - apiGroups: - install.istio.io resources: @@ -618,18 +594,6 @@ spec: - patch - update - watch - - apiGroups: - - security.istio.io - resources: - - authorizationpolicies - verbs: - - create - - delete - - get - - list - - patch - - update - - watch serviceAccountName: kuadrant-operator-controller-manager deployments: - label: diff --git a/bundle/manifests/kuadrant.io_authpolicies.yaml b/bundle/manifests/kuadrant.io_authpolicies.yaml index 21d8e80c8..d8bec4c9f 100644 --- a/bundle/manifests/kuadrant.io_authpolicies.yaml +++ b/bundle/manifests/kuadrant.io_authpolicies.yaml @@ -28,14 +28,19 @@ spec: name: Enforced priority: 2 type: string - - description: Type of the referenced Gateway API resource + - description: Kind of the object to which the policy aaplies jsonPath: .spec.targetRef.kind - name: TargetRefKind + name: TargetKind priority: 2 type: string - - description: Name of the referenced Gateway API resource + - description: Name of the object to which the policy applies jsonPath: .spec.targetRef.name - name: TargetRefName + name: TargetName + priority: 2 + type: string + - description: 'Name of the section within the object to which the policy applies ' + jsonPath: .spec.targetRef.sectionName + name: TargetSection priority: 2 type: string - jsonPath: .metadata.creationTimestamp @@ -65,41 +70,45 @@ spec: metadata: type: object spec: - description: Mutual Exclusivity Validation properties: defaults: description: |- - Defaults define explicit default values for this policy and for policies inheriting this policy. - Defaults are mutually exclusive with implicit defaults defined by AuthPolicyCommonSpec. + Rules to apply as defaults. Can be overridden by more specific policiy rules lower in the hierarchy and by less specific policy overrides. + Use one of: defaults, overrides, or bare set of policy rules (implicit defaults). properties: patterns: additionalProperties: - items: - properties: - operator: - description: |- - The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". - Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) - enum: - - eq - - neq - - incl - - excl - - matches - type: string - selector: - description: |- - Path selector to fetch content from the authorization JSON (e.g. 'request.method'). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - Authorino custom JSON path modifiers are also supported. - type: string - value: - description: |- - The value of reference for the comparison with the content fetched from the authorization JSON. - If used with the "matches" operator, the value must compile to a valid Golang regex. - type: string - type: object - type: array + properties: + allOf: + items: + properties: + operator: + description: |- + The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". + Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) + enum: + - eq + - neq + - incl + - excl + - matches + type: string + selector: + description: |- + Path selector to fetch content from the authorization JSON (e.g. 'request.method'). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + Authorino custom JSON path modifiers are also supported. + type: string + value: + description: |- + The value of reference for the comparison with the content fetched from the authorization JSON. + If used with the "matches" operator, the value must compile to a valid Golang regex. + type: string + type: object + type: array + required: + - allOf + type: object description: Named sets of patterns that can be referred in `when` conditions and in pattern-matching authorization policy rules. type: object @@ -478,6 +487,7 @@ spec: description: |- Authentication configs. At least one config MUST evaluate to a valid identity object for the auth request to be successful. + maxProperties: 10 type: object authorization: additionalProperties: @@ -1065,6 +1075,7 @@ spec: description: |- Authorization policies. All policies MUST evaluate to "allowed = true" for the auth request be successful. + maxProperties: 10 type: object callbacks: additionalProperties: @@ -1352,6 +1363,7 @@ spec: description: |- Callback functions. Authorino sends callbacks at the end of the auth pipeline to the endpoints specified in this config. + maxProperties: 10 type: object metadata: additionalProperties: @@ -1681,6 +1693,7 @@ spec: description: |- Metadata sources. Authorino fetches auth metadata as JSON from sources specified in this config. + maxProperties: 10 type: object response: description: |- @@ -1692,10 +1705,8 @@ spec: Response items to be included in the auth response when the request is authenticated and authorized. For integration of Authorino via proxy, the proxy must use these settings to propagate dynamic metadata and/or inject data in the request. properties: - dynamicMetadata: + filters: additionalProperties: - description: Settings of the success custom response - item. properties: cache: description: |- @@ -1890,10 +1901,8 @@ spec: - signingKeyRefs type: object type: object - description: |- - Custom success response items wrapped as HTTP headers. - For integration of Authorino via proxy, the proxy must use these settings to propagate dynamic metadata. - See https://www.envoyproxy.io/docs/envoy/latest/configuration/advanced/well_known_dynamic_metadata + description: Custom data made available to other filters + managed by Kuadrant (i.e. Rate Limit) type: object headers: additionalProperties: @@ -2091,9 +2100,7 @@ spec: - signingKeyRefs type: object type: object - description: |- - Custom success response items wrapped as HTTP headers. - For integration of Authorino via proxy, the proxy must use these settings to inject data in the request. + description: Custom headers to inject in the request. type: object type: object unauthenticated: @@ -2214,6 +2221,14 @@ spec: type: object type: object type: object + strategy: + default: atomic + description: Strategy defines the merge strategy to apply when + merging this policy with other policies. + enum: + - atomic + - merge + type: string when: description: |- Overall conditions for the AuthPolicy to be enforced. @@ -2265,37 +2280,42 @@ spec: type: object overrides: description: |- - Overrides define explicit override values for this policy. - Overrides are mutually exclusive with explicit and implicit defaults defined by AuthPolicyCommonSpec. + Rules to apply as overrides. Override all policy rules lower in the hierarchy. Can be overridden by less specific policy overrides. + Use one of: defaults, overrides, or bare set of policy rules (implicit defaults). properties: patterns: additionalProperties: - items: - properties: - operator: - description: |- - The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". - Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) - enum: - - eq - - neq - - incl - - excl - - matches - type: string - selector: - description: |- - Path selector to fetch content from the authorization JSON (e.g. 'request.method'). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - Authorino custom JSON path modifiers are also supported. - type: string - value: - description: |- - The value of reference for the comparison with the content fetched from the authorization JSON. - If used with the "matches" operator, the value must compile to a valid Golang regex. - type: string - type: object - type: array + properties: + allOf: + items: + properties: + operator: + description: |- + The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". + Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) + enum: + - eq + - neq + - incl + - excl + - matches + type: string + selector: + description: |- + Path selector to fetch content from the authorization JSON (e.g. 'request.method'). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + Authorino custom JSON path modifiers are also supported. + type: string + value: + description: |- + The value of reference for the comparison with the content fetched from the authorization JSON. + If used with the "matches" operator, the value must compile to a valid Golang regex. + type: string + type: object + type: array + required: + - allOf + type: object description: Named sets of patterns that can be referred in `when` conditions and in pattern-matching authorization policy rules. type: object @@ -2674,6 +2694,7 @@ spec: description: |- Authentication configs. At least one config MUST evaluate to a valid identity object for the auth request to be successful. + maxProperties: 10 type: object authorization: additionalProperties: @@ -3261,6 +3282,7 @@ spec: description: |- Authorization policies. All policies MUST evaluate to "allowed = true" for the auth request be successful. + maxProperties: 10 type: object callbacks: additionalProperties: @@ -3548,6 +3570,7 @@ spec: description: |- Callback functions. Authorino sends callbacks at the end of the auth pipeline to the endpoints specified in this config. + maxProperties: 10 type: object metadata: additionalProperties: @@ -3877,6 +3900,7 @@ spec: description: |- Metadata sources. Authorino fetches auth metadata as JSON from sources specified in this config. + maxProperties: 10 type: object response: description: |- @@ -3888,10 +3912,8 @@ spec: Response items to be included in the auth response when the request is authenticated and authorized. For integration of Authorino via proxy, the proxy must use these settings to propagate dynamic metadata and/or inject data in the request. properties: - dynamicMetadata: + filters: additionalProperties: - description: Settings of the success custom response - item. properties: cache: description: |- @@ -4086,10 +4108,8 @@ spec: - signingKeyRefs type: object type: object - description: |- - Custom success response items wrapped as HTTP headers. - For integration of Authorino via proxy, the proxy must use these settings to propagate dynamic metadata. - See https://www.envoyproxy.io/docs/envoy/latest/configuration/advanced/well_known_dynamic_metadata + description: Custom data made available to other filters + managed by Kuadrant (i.e. Rate Limit) type: object headers: additionalProperties: @@ -4287,9 +4307,7 @@ spec: - signingKeyRefs type: object type: object - description: |- - Custom success response items wrapped as HTTP headers. - For integration of Authorino via proxy, the proxy must use these settings to inject data in the request. + description: Custom headers to inject in the request. type: object type: object unauthenticated: @@ -4410,6 +4428,14 @@ spec: type: object type: object type: object + strategy: + default: atomic + description: Strategy defines the merge strategy to apply when + merging this policy with other policies. + enum: + - atomic + - merge + type: string when: description: |- Overall conditions for the AuthPolicy to be enforced. @@ -4461,32 +4487,37 @@ spec: type: object patterns: additionalProperties: - items: - properties: - operator: - description: |- - The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". - Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) - enum: - - eq - - neq - - incl - - excl - - matches - type: string - selector: - description: |- - Path selector to fetch content from the authorization JSON (e.g. 'request.method'). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - Authorino custom JSON path modifiers are also supported. - type: string - value: - description: |- - The value of reference for the comparison with the content fetched from the authorization JSON. - If used with the "matches" operator, the value must compile to a valid Golang regex. - type: string - type: object - type: array + properties: + allOf: + items: + properties: + operator: + description: |- + The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". + Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) + enum: + - eq + - neq + - incl + - excl + - matches + type: string + selector: + description: |- + Path selector to fetch content from the authorization JSON (e.g. 'request.method'). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + Authorino custom JSON path modifiers are also supported. + type: string + value: + description: |- + The value of reference for the comparison with the content fetched from the authorization JSON. + If used with the "matches" operator, the value must compile to a valid Golang regex. + type: string + type: object + type: array + required: + - allOf + type: object description: Named sets of patterns that can be referred in `when` conditions and in pattern-matching authorization policy rules. type: object @@ -4861,6 +4892,7 @@ spec: description: |- Authentication configs. At least one config MUST evaluate to a valid identity object for the auth request to be successful. + maxProperties: 10 type: object authorization: additionalProperties: @@ -5444,6 +5476,7 @@ spec: description: |- Authorization policies. All policies MUST evaluate to "allowed = true" for the auth request be successful. + maxProperties: 10 type: object callbacks: additionalProperties: @@ -5728,6 +5761,7 @@ spec: description: |- Callback functions. Authorino sends callbacks at the end of the auth pipeline to the endpoints specified in this config. + maxProperties: 10 type: object metadata: additionalProperties: @@ -6053,6 +6087,7 @@ spec: description: |- Metadata sources. Authorino fetches auth metadata as JSON from sources specified in this config. + maxProperties: 10 type: object response: description: |- @@ -6064,10 +6099,8 @@ spec: Response items to be included in the auth response when the request is authenticated and authorized. For integration of Authorino via proxy, the proxy must use these settings to propagate dynamic metadata and/or inject data in the request. properties: - dynamicMetadata: + filters: additionalProperties: - description: Settings of the success custom response - item. properties: cache: description: |- @@ -6261,10 +6294,8 @@ spec: - signingKeyRefs type: object type: object - description: |- - Custom success response items wrapped as HTTP headers. - For integration of Authorino via proxy, the proxy must use these settings to propagate dynamic metadata. - See https://www.envoyproxy.io/docs/envoy/latest/configuration/advanced/well_known_dynamic_metadata + description: Custom data made available to other filters + managed by Kuadrant (i.e. Rate Limit) type: object headers: additionalProperties: @@ -6461,9 +6492,7 @@ spec: - signingKeyRefs type: object type: object - description: |- - Custom success response items wrapped as HTTP headers. - For integration of Authorino via proxy, the proxy must use these settings to inject data in the request. + description: Custom headers to inject in the request. type: object type: object unauthenticated: @@ -6585,7 +6614,7 @@ spec: type: object type: object targetRef: - description: TargetRef identifies an API object to apply policy to. + description: Reference to the object to which this policy applies. properties: group: description: Group is the group of the target resource. @@ -6603,6 +6632,25 @@ spec: maxLength: 253 minLength: 1 type: string + sectionName: + description: |- + SectionName is the name of a section within the target resource. When + unspecified, this targetRef targets the entire resource. In the following + resources, SectionName is interpreted as the following: + + + * Gateway: Listener name + * HTTPRoute: HTTPRouteRule name + * Service: Port name + + + If a SectionName is specified, but does not exist on the targeted object, + the Policy must fail to attach, and the policy implementation should record + a `ResolvedRefs` or similar Condition in the Policy's status. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string required: - group - kind @@ -6667,16 +6715,11 @@ spec: type: object x-kubernetes-validations: - message: Implicit and explicit defaults are mutually exclusive - rule: '!(has(self.defaults) && (has(self.patterns) || has(self.when) - || has(self.rules)))' - - message: Implicit defaults and explicit overrides are mutually exclusive - rule: '!(has(self.overrides) && (has(self.patterns) || has(self.when) - || has(self.rules)))' - - message: Explicit overrides and explicit defaults are mutually exclusive - rule: '!(has(self.overrides) && has(self.defaults))' - - message: Overrides are not allowed for policies targeting a HTTPRoute - resource - rule: '!(has(self.overrides) && self.targetRef.kind == ''HTTPRoute'')' + rule: '!(has(self.defaults) && has(self.rules))' + - message: Overrides and explicit defaults are mutually exclusive + rule: '!(has(self.defaults) && has(self.overrides))' + - message: Overrides and implicit defaults are mutually exclusive + rule: '!(has(self.overrides) && has(self.rules))' status: properties: conditions: diff --git a/charts/kuadrant-operator/templates/manifests.yaml b/charts/kuadrant-operator/templates/manifests.yaml index 3c2c33014..79a55bcd2 100644 --- a/charts/kuadrant-operator/templates/manifests.yaml +++ b/charts/kuadrant-operator/templates/manifests.yaml @@ -28,14 +28,19 @@ spec: name: Enforced priority: 2 type: string - - description: Type of the referenced Gateway API resource + - description: Kind of the object to which the policy aaplies jsonPath: .spec.targetRef.kind - name: TargetRefKind + name: TargetKind priority: 2 type: string - - description: Name of the referenced Gateway API resource + - description: Name of the object to which the policy applies jsonPath: .spec.targetRef.name - name: TargetRefName + name: TargetName + priority: 2 + type: string + - description: 'Name of the section within the object to which the policy applies ' + jsonPath: .spec.targetRef.sectionName + name: TargetSection priority: 2 type: string - jsonPath: .metadata.creationTimestamp @@ -65,41 +70,45 @@ spec: metadata: type: object spec: - description: Mutual Exclusivity Validation properties: defaults: description: |- - Defaults define explicit default values for this policy and for policies inheriting this policy. - Defaults are mutually exclusive with implicit defaults defined by AuthPolicyCommonSpec. + Rules to apply as defaults. Can be overridden by more specific policiy rules lower in the hierarchy and by less specific policy overrides. + Use one of: defaults, overrides, or bare set of policy rules (implicit defaults). properties: patterns: additionalProperties: - items: - properties: - operator: - description: |- - The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". - Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) - enum: - - eq - - neq - - incl - - excl - - matches - type: string - selector: - description: |- - Path selector to fetch content from the authorization JSON (e.g. 'request.method'). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - Authorino custom JSON path modifiers are also supported. - type: string - value: - description: |- - The value of reference for the comparison with the content fetched from the authorization JSON. - If used with the "matches" operator, the value must compile to a valid Golang regex. - type: string - type: object - type: array + properties: + allOf: + items: + properties: + operator: + description: |- + The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". + Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) + enum: + - eq + - neq + - incl + - excl + - matches + type: string + selector: + description: |- + Path selector to fetch content from the authorization JSON (e.g. 'request.method'). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + Authorino custom JSON path modifiers are also supported. + type: string + value: + description: |- + The value of reference for the comparison with the content fetched from the authorization JSON. + If used with the "matches" operator, the value must compile to a valid Golang regex. + type: string + type: object + type: array + required: + - allOf + type: object description: Named sets of patterns that can be referred in `when` conditions and in pattern-matching authorization policy rules. type: object @@ -478,6 +487,7 @@ spec: description: |- Authentication configs. At least one config MUST evaluate to a valid identity object for the auth request to be successful. + maxProperties: 10 type: object authorization: additionalProperties: @@ -1065,6 +1075,7 @@ spec: description: |- Authorization policies. All policies MUST evaluate to "allowed = true" for the auth request be successful. + maxProperties: 10 type: object callbacks: additionalProperties: @@ -1352,6 +1363,7 @@ spec: description: |- Callback functions. Authorino sends callbacks at the end of the auth pipeline to the endpoints specified in this config. + maxProperties: 10 type: object metadata: additionalProperties: @@ -1681,6 +1693,7 @@ spec: description: |- Metadata sources. Authorino fetches auth metadata as JSON from sources specified in this config. + maxProperties: 10 type: object response: description: |- @@ -1692,10 +1705,8 @@ spec: Response items to be included in the auth response when the request is authenticated and authorized. For integration of Authorino via proxy, the proxy must use these settings to propagate dynamic metadata and/or inject data in the request. properties: - dynamicMetadata: + filters: additionalProperties: - description: Settings of the success custom response - item. properties: cache: description: |- @@ -1890,10 +1901,8 @@ spec: - signingKeyRefs type: object type: object - description: |- - Custom success response items wrapped as HTTP headers. - For integration of Authorino via proxy, the proxy must use these settings to propagate dynamic metadata. - See https://www.envoyproxy.io/docs/envoy/latest/configuration/advanced/well_known_dynamic_metadata + description: Custom data made available to other filters + managed by Kuadrant (i.e. Rate Limit) type: object headers: additionalProperties: @@ -2091,9 +2100,7 @@ spec: - signingKeyRefs type: object type: object - description: |- - Custom success response items wrapped as HTTP headers. - For integration of Authorino via proxy, the proxy must use these settings to inject data in the request. + description: Custom headers to inject in the request. type: object type: object unauthenticated: @@ -2214,6 +2221,14 @@ spec: type: object type: object type: object + strategy: + default: atomic + description: Strategy defines the merge strategy to apply when + merging this policy with other policies. + enum: + - atomic + - merge + type: string when: description: |- Overall conditions for the AuthPolicy to be enforced. @@ -2265,37 +2280,42 @@ spec: type: object overrides: description: |- - Overrides define explicit override values for this policy. - Overrides are mutually exclusive with explicit and implicit defaults defined by AuthPolicyCommonSpec. + Rules to apply as overrides. Override all policy rules lower in the hierarchy. Can be overridden by less specific policy overrides. + Use one of: defaults, overrides, or bare set of policy rules (implicit defaults). properties: patterns: additionalProperties: - items: - properties: - operator: - description: |- - The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". - Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) - enum: - - eq - - neq - - incl - - excl - - matches - type: string - selector: - description: |- - Path selector to fetch content from the authorization JSON (e.g. 'request.method'). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - Authorino custom JSON path modifiers are also supported. - type: string - value: - description: |- - The value of reference for the comparison with the content fetched from the authorization JSON. - If used with the "matches" operator, the value must compile to a valid Golang regex. - type: string - type: object - type: array + properties: + allOf: + items: + properties: + operator: + description: |- + The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". + Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) + enum: + - eq + - neq + - incl + - excl + - matches + type: string + selector: + description: |- + Path selector to fetch content from the authorization JSON (e.g. 'request.method'). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + Authorino custom JSON path modifiers are also supported. + type: string + value: + description: |- + The value of reference for the comparison with the content fetched from the authorization JSON. + If used with the "matches" operator, the value must compile to a valid Golang regex. + type: string + type: object + type: array + required: + - allOf + type: object description: Named sets of patterns that can be referred in `when` conditions and in pattern-matching authorization policy rules. type: object @@ -2674,6 +2694,7 @@ spec: description: |- Authentication configs. At least one config MUST evaluate to a valid identity object for the auth request to be successful. + maxProperties: 10 type: object authorization: additionalProperties: @@ -3261,6 +3282,7 @@ spec: description: |- Authorization policies. All policies MUST evaluate to "allowed = true" for the auth request be successful. + maxProperties: 10 type: object callbacks: additionalProperties: @@ -3548,6 +3570,7 @@ spec: description: |- Callback functions. Authorino sends callbacks at the end of the auth pipeline to the endpoints specified in this config. + maxProperties: 10 type: object metadata: additionalProperties: @@ -3877,6 +3900,7 @@ spec: description: |- Metadata sources. Authorino fetches auth metadata as JSON from sources specified in this config. + maxProperties: 10 type: object response: description: |- @@ -3888,10 +3912,8 @@ spec: Response items to be included in the auth response when the request is authenticated and authorized. For integration of Authorino via proxy, the proxy must use these settings to propagate dynamic metadata and/or inject data in the request. properties: - dynamicMetadata: + filters: additionalProperties: - description: Settings of the success custom response - item. properties: cache: description: |- @@ -4086,10 +4108,8 @@ spec: - signingKeyRefs type: object type: object - description: |- - Custom success response items wrapped as HTTP headers. - For integration of Authorino via proxy, the proxy must use these settings to propagate dynamic metadata. - See https://www.envoyproxy.io/docs/envoy/latest/configuration/advanced/well_known_dynamic_metadata + description: Custom data made available to other filters + managed by Kuadrant (i.e. Rate Limit) type: object headers: additionalProperties: @@ -4287,9 +4307,7 @@ spec: - signingKeyRefs type: object type: object - description: |- - Custom success response items wrapped as HTTP headers. - For integration of Authorino via proxy, the proxy must use these settings to inject data in the request. + description: Custom headers to inject in the request. type: object type: object unauthenticated: @@ -4410,6 +4428,14 @@ spec: type: object type: object type: object + strategy: + default: atomic + description: Strategy defines the merge strategy to apply when + merging this policy with other policies. + enum: + - atomic + - merge + type: string when: description: |- Overall conditions for the AuthPolicy to be enforced. @@ -4461,32 +4487,37 @@ spec: type: object patterns: additionalProperties: - items: - properties: - operator: - description: |- - The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". - Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) - enum: - - eq - - neq - - incl - - excl - - matches - type: string - selector: - description: |- - Path selector to fetch content from the authorization JSON (e.g. 'request.method'). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - Authorino custom JSON path modifiers are also supported. - type: string - value: - description: |- - The value of reference for the comparison with the content fetched from the authorization JSON. - If used with the "matches" operator, the value must compile to a valid Golang regex. - type: string - type: object - type: array + properties: + allOf: + items: + properties: + operator: + description: |- + The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". + Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) + enum: + - eq + - neq + - incl + - excl + - matches + type: string + selector: + description: |- + Path selector to fetch content from the authorization JSON (e.g. 'request.method'). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + Authorino custom JSON path modifiers are also supported. + type: string + value: + description: |- + The value of reference for the comparison with the content fetched from the authorization JSON. + If used with the "matches" operator, the value must compile to a valid Golang regex. + type: string + type: object + type: array + required: + - allOf + type: object description: Named sets of patterns that can be referred in `when` conditions and in pattern-matching authorization policy rules. type: object @@ -4861,6 +4892,7 @@ spec: description: |- Authentication configs. At least one config MUST evaluate to a valid identity object for the auth request to be successful. + maxProperties: 10 type: object authorization: additionalProperties: @@ -5444,6 +5476,7 @@ spec: description: |- Authorization policies. All policies MUST evaluate to "allowed = true" for the auth request be successful. + maxProperties: 10 type: object callbacks: additionalProperties: @@ -5728,6 +5761,7 @@ spec: description: |- Callback functions. Authorino sends callbacks at the end of the auth pipeline to the endpoints specified in this config. + maxProperties: 10 type: object metadata: additionalProperties: @@ -6053,6 +6087,7 @@ spec: description: |- Metadata sources. Authorino fetches auth metadata as JSON from sources specified in this config. + maxProperties: 10 type: object response: description: |- @@ -6064,10 +6099,8 @@ spec: Response items to be included in the auth response when the request is authenticated and authorized. For integration of Authorino via proxy, the proxy must use these settings to propagate dynamic metadata and/or inject data in the request. properties: - dynamicMetadata: + filters: additionalProperties: - description: Settings of the success custom response - item. properties: cache: description: |- @@ -6261,10 +6294,8 @@ spec: - signingKeyRefs type: object type: object - description: |- - Custom success response items wrapped as HTTP headers. - For integration of Authorino via proxy, the proxy must use these settings to propagate dynamic metadata. - See https://www.envoyproxy.io/docs/envoy/latest/configuration/advanced/well_known_dynamic_metadata + description: Custom data made available to other filters + managed by Kuadrant (i.e. Rate Limit) type: object headers: additionalProperties: @@ -6461,9 +6492,7 @@ spec: - signingKeyRefs type: object type: object - description: |- - Custom success response items wrapped as HTTP headers. - For integration of Authorino via proxy, the proxy must use these settings to inject data in the request. + description: Custom headers to inject in the request. type: object type: object unauthenticated: @@ -6585,7 +6614,7 @@ spec: type: object type: object targetRef: - description: TargetRef identifies an API object to apply policy to. + description: Reference to the object to which this policy applies. properties: group: description: Group is the group of the target resource. @@ -6603,6 +6632,25 @@ spec: maxLength: 253 minLength: 1 type: string + sectionName: + description: |- + SectionName is the name of a section within the target resource. When + unspecified, this targetRef targets the entire resource. In the following + resources, SectionName is interpreted as the following: + + + * Gateway: Listener name + * HTTPRoute: HTTPRouteRule name + * Service: Port name + + + If a SectionName is specified, but does not exist on the targeted object, + the Policy must fail to attach, and the policy implementation should record + a `ResolvedRefs` or similar Condition in the Policy's status. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string required: - group - kind @@ -6667,16 +6715,11 @@ spec: type: object x-kubernetes-validations: - message: Implicit and explicit defaults are mutually exclusive - rule: '!(has(self.defaults) && (has(self.patterns) || has(self.when) - || has(self.rules)))' - - message: Implicit defaults and explicit overrides are mutually exclusive - rule: '!(has(self.overrides) && (has(self.patterns) || has(self.when) - || has(self.rules)))' - - message: Explicit overrides and explicit defaults are mutually exclusive - rule: '!(has(self.overrides) && has(self.defaults))' - - message: Overrides are not allowed for policies targeting a HTTPRoute - resource - rule: '!(has(self.overrides) && self.targetRef.kind == ''HTTPRoute'')' + rule: '!(has(self.defaults) && has(self.rules))' + - message: Overrides and explicit defaults are mutually exclusive + rule: '!(has(self.defaults) && has(self.overrides))' + - message: Overrides and implicit defaults are mutually exclusive + rule: '!(has(self.overrides) && has(self.rules))' status: properties: conditions: @@ -8564,18 +8607,6 @@ rules: - patch - update - watch -- apiGroups: - - gateway.envoyproxy.io - resources: - - securitypolicies - verbs: - - create - - delete - - get - - list - - patch - - update - - watch - apiGroups: - gateway.networking.k8s.io resources: @@ -8627,18 +8658,6 @@ rules: - get - patch - update -- apiGroups: - - gateway.networking.k8s.io - resources: - - referencegrants - verbs: - - create - - delete - - get - - list - - patch - - update - - watch - apiGroups: - install.istio.io resources: @@ -8866,18 +8885,6 @@ rules: - patch - update - watch -- apiGroups: - - security.istio.io - resources: - - authorizationpolicies - verbs: - - create - - delete - - get - - list - - patch - - update - - watch --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding diff --git a/config/crd/bases/kuadrant.io_authpolicies.yaml b/config/crd/bases/kuadrant.io_authpolicies.yaml index 307d7eca4..763165749 100644 --- a/config/crd/bases/kuadrant.io_authpolicies.yaml +++ b/config/crd/bases/kuadrant.io_authpolicies.yaml @@ -27,14 +27,19 @@ spec: name: Enforced priority: 2 type: string - - description: Type of the referenced Gateway API resource + - description: Kind of the object to which the policy aaplies jsonPath: .spec.targetRef.kind - name: TargetRefKind + name: TargetKind priority: 2 type: string - - description: Name of the referenced Gateway API resource + - description: Name of the object to which the policy applies jsonPath: .spec.targetRef.name - name: TargetRefName + name: TargetName + priority: 2 + type: string + - description: 'Name of the section within the object to which the policy applies ' + jsonPath: .spec.targetRef.sectionName + name: TargetSection priority: 2 type: string - jsonPath: .metadata.creationTimestamp @@ -64,41 +69,45 @@ spec: metadata: type: object spec: - description: Mutual Exclusivity Validation properties: defaults: description: |- - Defaults define explicit default values for this policy and for policies inheriting this policy. - Defaults are mutually exclusive with implicit defaults defined by AuthPolicyCommonSpec. + Rules to apply as defaults. Can be overridden by more specific policiy rules lower in the hierarchy and by less specific policy overrides. + Use one of: defaults, overrides, or bare set of policy rules (implicit defaults). properties: patterns: additionalProperties: - items: - properties: - operator: - description: |- - The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". - Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) - enum: - - eq - - neq - - incl - - excl - - matches - type: string - selector: - description: |- - Path selector to fetch content from the authorization JSON (e.g. 'request.method'). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - Authorino custom JSON path modifiers are also supported. - type: string - value: - description: |- - The value of reference for the comparison with the content fetched from the authorization JSON. - If used with the "matches" operator, the value must compile to a valid Golang regex. - type: string - type: object - type: array + properties: + allOf: + items: + properties: + operator: + description: |- + The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". + Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) + enum: + - eq + - neq + - incl + - excl + - matches + type: string + selector: + description: |- + Path selector to fetch content from the authorization JSON (e.g. 'request.method'). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + Authorino custom JSON path modifiers are also supported. + type: string + value: + description: |- + The value of reference for the comparison with the content fetched from the authorization JSON. + If used with the "matches" operator, the value must compile to a valid Golang regex. + type: string + type: object + type: array + required: + - allOf + type: object description: Named sets of patterns that can be referred in `when` conditions and in pattern-matching authorization policy rules. type: object @@ -477,6 +486,7 @@ spec: description: |- Authentication configs. At least one config MUST evaluate to a valid identity object for the auth request to be successful. + maxProperties: 10 type: object authorization: additionalProperties: @@ -1064,6 +1074,7 @@ spec: description: |- Authorization policies. All policies MUST evaluate to "allowed = true" for the auth request be successful. + maxProperties: 10 type: object callbacks: additionalProperties: @@ -1351,6 +1362,7 @@ spec: description: |- Callback functions. Authorino sends callbacks at the end of the auth pipeline to the endpoints specified in this config. + maxProperties: 10 type: object metadata: additionalProperties: @@ -1680,6 +1692,7 @@ spec: description: |- Metadata sources. Authorino fetches auth metadata as JSON from sources specified in this config. + maxProperties: 10 type: object response: description: |- @@ -1691,10 +1704,8 @@ spec: Response items to be included in the auth response when the request is authenticated and authorized. For integration of Authorino via proxy, the proxy must use these settings to propagate dynamic metadata and/or inject data in the request. properties: - dynamicMetadata: + filters: additionalProperties: - description: Settings of the success custom response - item. properties: cache: description: |- @@ -1889,10 +1900,8 @@ spec: - signingKeyRefs type: object type: object - description: |- - Custom success response items wrapped as HTTP headers. - For integration of Authorino via proxy, the proxy must use these settings to propagate dynamic metadata. - See https://www.envoyproxy.io/docs/envoy/latest/configuration/advanced/well_known_dynamic_metadata + description: Custom data made available to other filters + managed by Kuadrant (i.e. Rate Limit) type: object headers: additionalProperties: @@ -2090,9 +2099,7 @@ spec: - signingKeyRefs type: object type: object - description: |- - Custom success response items wrapped as HTTP headers. - For integration of Authorino via proxy, the proxy must use these settings to inject data in the request. + description: Custom headers to inject in the request. type: object type: object unauthenticated: @@ -2213,6 +2220,14 @@ spec: type: object type: object type: object + strategy: + default: atomic + description: Strategy defines the merge strategy to apply when + merging this policy with other policies. + enum: + - atomic + - merge + type: string when: description: |- Overall conditions for the AuthPolicy to be enforced. @@ -2264,37 +2279,42 @@ spec: type: object overrides: description: |- - Overrides define explicit override values for this policy. - Overrides are mutually exclusive with explicit and implicit defaults defined by AuthPolicyCommonSpec. + Rules to apply as overrides. Override all policy rules lower in the hierarchy. Can be overridden by less specific policy overrides. + Use one of: defaults, overrides, or bare set of policy rules (implicit defaults). properties: patterns: additionalProperties: - items: - properties: - operator: - description: |- - The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". - Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) - enum: - - eq - - neq - - incl - - excl - - matches - type: string - selector: - description: |- - Path selector to fetch content from the authorization JSON (e.g. 'request.method'). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - Authorino custom JSON path modifiers are also supported. - type: string - value: - description: |- - The value of reference for the comparison with the content fetched from the authorization JSON. - If used with the "matches" operator, the value must compile to a valid Golang regex. - type: string - type: object - type: array + properties: + allOf: + items: + properties: + operator: + description: |- + The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". + Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) + enum: + - eq + - neq + - incl + - excl + - matches + type: string + selector: + description: |- + Path selector to fetch content from the authorization JSON (e.g. 'request.method'). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + Authorino custom JSON path modifiers are also supported. + type: string + value: + description: |- + The value of reference for the comparison with the content fetched from the authorization JSON. + If used with the "matches" operator, the value must compile to a valid Golang regex. + type: string + type: object + type: array + required: + - allOf + type: object description: Named sets of patterns that can be referred in `when` conditions and in pattern-matching authorization policy rules. type: object @@ -2673,6 +2693,7 @@ spec: description: |- Authentication configs. At least one config MUST evaluate to a valid identity object for the auth request to be successful. + maxProperties: 10 type: object authorization: additionalProperties: @@ -3260,6 +3281,7 @@ spec: description: |- Authorization policies. All policies MUST evaluate to "allowed = true" for the auth request be successful. + maxProperties: 10 type: object callbacks: additionalProperties: @@ -3547,6 +3569,7 @@ spec: description: |- Callback functions. Authorino sends callbacks at the end of the auth pipeline to the endpoints specified in this config. + maxProperties: 10 type: object metadata: additionalProperties: @@ -3876,6 +3899,7 @@ spec: description: |- Metadata sources. Authorino fetches auth metadata as JSON from sources specified in this config. + maxProperties: 10 type: object response: description: |- @@ -3887,10 +3911,8 @@ spec: Response items to be included in the auth response when the request is authenticated and authorized. For integration of Authorino via proxy, the proxy must use these settings to propagate dynamic metadata and/or inject data in the request. properties: - dynamicMetadata: + filters: additionalProperties: - description: Settings of the success custom response - item. properties: cache: description: |- @@ -4085,10 +4107,8 @@ spec: - signingKeyRefs type: object type: object - description: |- - Custom success response items wrapped as HTTP headers. - For integration of Authorino via proxy, the proxy must use these settings to propagate dynamic metadata. - See https://www.envoyproxy.io/docs/envoy/latest/configuration/advanced/well_known_dynamic_metadata + description: Custom data made available to other filters + managed by Kuadrant (i.e. Rate Limit) type: object headers: additionalProperties: @@ -4286,9 +4306,7 @@ spec: - signingKeyRefs type: object type: object - description: |- - Custom success response items wrapped as HTTP headers. - For integration of Authorino via proxy, the proxy must use these settings to inject data in the request. + description: Custom headers to inject in the request. type: object type: object unauthenticated: @@ -4409,6 +4427,14 @@ spec: type: object type: object type: object + strategy: + default: atomic + description: Strategy defines the merge strategy to apply when + merging this policy with other policies. + enum: + - atomic + - merge + type: string when: description: |- Overall conditions for the AuthPolicy to be enforced. @@ -4460,32 +4486,37 @@ spec: type: object patterns: additionalProperties: - items: - properties: - operator: - description: |- - The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". - Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) - enum: - - eq - - neq - - incl - - excl - - matches - type: string - selector: - description: |- - Path selector to fetch content from the authorization JSON (e.g. 'request.method'). - Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. - Authorino custom JSON path modifiers are also supported. - type: string - value: - description: |- - The value of reference for the comparison with the content fetched from the authorization JSON. - If used with the "matches" operator, the value must compile to a valid Golang regex. - type: string - type: object - type: array + properties: + allOf: + items: + properties: + operator: + description: |- + The binary operator to be applied to the content fetched from the authorization JSON, for comparison with "value". + Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) + enum: + - eq + - neq + - incl + - excl + - matches + type: string + selector: + description: |- + Path selector to fetch content from the authorization JSON (e.g. 'request.method'). + Any pattern supported by https://pkg.go.dev/github.com/tidwall/gjson can be used. + Authorino custom JSON path modifiers are also supported. + type: string + value: + description: |- + The value of reference for the comparison with the content fetched from the authorization JSON. + If used with the "matches" operator, the value must compile to a valid Golang regex. + type: string + type: object + type: array + required: + - allOf + type: object description: Named sets of patterns that can be referred in `when` conditions and in pattern-matching authorization policy rules. type: object @@ -4860,6 +4891,7 @@ spec: description: |- Authentication configs. At least one config MUST evaluate to a valid identity object for the auth request to be successful. + maxProperties: 10 type: object authorization: additionalProperties: @@ -5443,6 +5475,7 @@ spec: description: |- Authorization policies. All policies MUST evaluate to "allowed = true" for the auth request be successful. + maxProperties: 10 type: object callbacks: additionalProperties: @@ -5727,6 +5760,7 @@ spec: description: |- Callback functions. Authorino sends callbacks at the end of the auth pipeline to the endpoints specified in this config. + maxProperties: 10 type: object metadata: additionalProperties: @@ -6052,6 +6086,7 @@ spec: description: |- Metadata sources. Authorino fetches auth metadata as JSON from sources specified in this config. + maxProperties: 10 type: object response: description: |- @@ -6063,10 +6098,8 @@ spec: Response items to be included in the auth response when the request is authenticated and authorized. For integration of Authorino via proxy, the proxy must use these settings to propagate dynamic metadata and/or inject data in the request. properties: - dynamicMetadata: + filters: additionalProperties: - description: Settings of the success custom response - item. properties: cache: description: |- @@ -6260,10 +6293,8 @@ spec: - signingKeyRefs type: object type: object - description: |- - Custom success response items wrapped as HTTP headers. - For integration of Authorino via proxy, the proxy must use these settings to propagate dynamic metadata. - See https://www.envoyproxy.io/docs/envoy/latest/configuration/advanced/well_known_dynamic_metadata + description: Custom data made available to other filters + managed by Kuadrant (i.e. Rate Limit) type: object headers: additionalProperties: @@ -6460,9 +6491,7 @@ spec: - signingKeyRefs type: object type: object - description: |- - Custom success response items wrapped as HTTP headers. - For integration of Authorino via proxy, the proxy must use these settings to inject data in the request. + description: Custom headers to inject in the request. type: object type: object unauthenticated: @@ -6584,7 +6613,7 @@ spec: type: object type: object targetRef: - description: TargetRef identifies an API object to apply policy to. + description: Reference to the object to which this policy applies. properties: group: description: Group is the group of the target resource. @@ -6602,6 +6631,25 @@ spec: maxLength: 253 minLength: 1 type: string + sectionName: + description: |- + SectionName is the name of a section within the target resource. When + unspecified, this targetRef targets the entire resource. In the following + resources, SectionName is interpreted as the following: + + + * Gateway: Listener name + * HTTPRoute: HTTPRouteRule name + * Service: Port name + + + If a SectionName is specified, but does not exist on the targeted object, + the Policy must fail to attach, and the policy implementation should record + a `ResolvedRefs` or similar Condition in the Policy's status. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string required: - group - kind @@ -6666,16 +6714,11 @@ spec: type: object x-kubernetes-validations: - message: Implicit and explicit defaults are mutually exclusive - rule: '!(has(self.defaults) && (has(self.patterns) || has(self.when) - || has(self.rules)))' - - message: Implicit defaults and explicit overrides are mutually exclusive - rule: '!(has(self.overrides) && (has(self.patterns) || has(self.when) - || has(self.rules)))' - - message: Explicit overrides and explicit defaults are mutually exclusive - rule: '!(has(self.overrides) && has(self.defaults))' - - message: Overrides are not allowed for policies targeting a HTTPRoute - resource - rule: '!(has(self.overrides) && self.targetRef.kind == ''HTTPRoute'')' + rule: '!(has(self.defaults) && has(self.rules))' + - message: Overrides and explicit defaults are mutually exclusive + rule: '!(has(self.defaults) && has(self.overrides))' + - message: Overrides and implicit defaults are mutually exclusive + rule: '!(has(self.overrides) && has(self.rules))' status: properties: conditions: diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 2b2b0c1ef..59633edd7 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -166,18 +166,6 @@ rules: - patch - update - watch -- apiGroups: - - gateway.envoyproxy.io - resources: - - securitypolicies - verbs: - - create - - delete - - get - - list - - patch - - update - - watch - apiGroups: - gateway.networking.k8s.io resources: @@ -229,18 +217,6 @@ rules: - get - patch - update -- apiGroups: - - gateway.networking.k8s.io - resources: - - referencegrants - verbs: - - create - - delete - - get - - list - - patch - - update - - watch - apiGroups: - install.istio.io resources: @@ -468,15 +444,3 @@ rules: - patch - update - watch -- apiGroups: - - security.istio.io - resources: - - authorizationpolicies - verbs: - - create - - delete - - get - - list - - patch - - update - - watch diff --git a/controllers/authpolicy_authconfig.go b/controllers/authpolicy_authconfig.go index 92192fa6e..bd408ffba 100644 --- a/controllers/authpolicy_authconfig.go +++ b/controllers/authpolicy_authconfig.go @@ -9,6 +9,7 @@ import ( "github.com/go-logr/logr" authorinoapi "github.com/kuadrant/authorino/api/v1beta2" + "github.com/samber/lo" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" @@ -131,16 +132,21 @@ func (r *AuthPolicyReconciler) desiredAuthConfig(ctx context.Context, ap *kuadra // hosts authConfig.Spec.Hosts = hosts - commonSpec := ap.Spec.CommonSpec() + commonSpec := ap.Spec.Proper() // named patterns if namedPatterns := commonSpec.NamedPatterns; len(namedPatterns) > 0 { - authConfig.Spec.NamedPatterns = namedPatterns + authConfig.Spec.NamedPatterns = make(map[string]authorinoapi.PatternExpressions, len(namedPatterns)) + for name, pattern := range namedPatterns { + authConfig.Spec.NamedPatterns[name] = pattern.PatternExpressions + } } conditionsFromHTTPRoute := authorinoConditionsFromHTTPRoute(route) if len(conditionsFromHTTPRoute) > 0 || len(commonSpec.Conditions) > 0 { - authConfig.Spec.Conditions = append(commonSpec.Conditions, conditionsFromHTTPRoute...) + authConfig.Spec.Conditions = append(lo.Map(commonSpec.Conditions, func(c kuadrantv1beta3.MergeablePatternExpressionOrRef, _ int) authorinoapi.PatternExpressionOrRef { + return c.PatternExpressionOrRef + }), conditionsFromHTTPRoute...) } // return early if authScheme is nil @@ -150,34 +156,46 @@ func (r *AuthPolicyReconciler) desiredAuthConfig(ctx context.Context, ap *kuadra // authentication if authentication := commonSpec.AuthScheme.Authentication; len(authentication) > 0 { - authConfig.Spec.Authentication = authorinoSpecsFromConfigs(authentication, func(config authorinoapi.AuthenticationSpec) authorinoapi.AuthenticationSpec { - return config + authConfig.Spec.Authentication = authorinoSpecsFromConfigs(authentication, func(config kuadrantv1beta3.MergeableAuthenticationSpec) authorinoapi.AuthenticationSpec { + return config.AuthenticationSpec }) } // metadata if metadata := commonSpec.AuthScheme.Metadata; len(metadata) > 0 { - authConfig.Spec.Metadata = authorinoSpecsFromConfigs(metadata, func(config authorinoapi.MetadataSpec) authorinoapi.MetadataSpec { return config }) + authConfig.Spec.Metadata = authorinoSpecsFromConfigs(metadata, func(config kuadrantv1beta3.MergeableMetadataSpec) authorinoapi.MetadataSpec { + return config.MetadataSpec + }) } // authorization if authorization := commonSpec.AuthScheme.Authorization; len(authorization) > 0 { - authConfig.Spec.Authorization = authorinoSpecsFromConfigs(authorization, func(config authorinoapi.AuthorizationSpec) authorinoapi.AuthorizationSpec { - return config + authConfig.Spec.Authorization = authorinoSpecsFromConfigs(authorization, func(config kuadrantv1beta3.MergeableAuthorizationSpec) authorinoapi.AuthorizationSpec { + return config.AuthorizationSpec }) } // response if response := commonSpec.AuthScheme.Response; response != nil { + var unauthenticated *authorinoapi.DenyWithSpec + if response.Unauthenticated != nil { + unauthenticated = &response.Unauthenticated.DenyWithSpec + } + + var unauthorized *authorinoapi.DenyWithSpec + if response.Unauthorized != nil { + unauthorized = &response.Unauthorized.DenyWithSpec + } + authConfig.Spec.Response = &authorinoapi.ResponseSpec{ - Unauthenticated: response.Unauthenticated, - Unauthorized: response.Unauthorized, + Unauthenticated: unauthenticated, + Unauthorized: unauthorized, Success: authorinoapi.WrappedSuccessResponseSpec{ - Headers: authorinoSpecsFromConfigs(response.Success.Headers, func(config kuadrantv1beta3.HeaderSuccessResponseSpec) authorinoapi.HeaderSuccessResponseSpec { + Headers: authorinoSpecsFromConfigs(response.Success.Headers, func(config kuadrantv1beta3.MergeableHeaderSuccessResponseSpec) authorinoapi.HeaderSuccessResponseSpec { return authorinoapi.HeaderSuccessResponseSpec{SuccessResponseSpec: config.SuccessResponseSpec} }), - DynamicMetadata: authorinoSpecsFromConfigs(response.Success.DynamicMetadata, func(config authorinoapi.SuccessResponseSpec) authorinoapi.SuccessResponseSpec { - return config + DynamicMetadata: authorinoSpecsFromConfigs(response.Success.DynamicMetadata, func(config kuadrantv1beta3.MergeableSuccessResponseSpec) authorinoapi.SuccessResponseSpec { + return config.SuccessResponseSpec }), }, } @@ -185,7 +203,9 @@ func (r *AuthPolicyReconciler) desiredAuthConfig(ctx context.Context, ap *kuadra // callbacks if callbacks := commonSpec.AuthScheme.Callbacks; len(callbacks) > 0 { - authConfig.Spec.Callbacks = authorinoSpecsFromConfigs(callbacks, func(config authorinoapi.CallbackSpec) authorinoapi.CallbackSpec { return config }) + authConfig.Spec.Callbacks = authorinoSpecsFromConfigs(callbacks, func(config kuadrantv1beta3.MergeableCallbackSpec) authorinoapi.CallbackSpec { + return config.CallbackSpec + }) } return authConfig, nil diff --git a/controllers/authpolicy_controller.go b/controllers/authpolicy_controller.go index aad00a34f..b0c7e5580 100644 --- a/controllers/authpolicy_controller.go +++ b/controllers/authpolicy_controller.go @@ -6,19 +6,14 @@ import ( "fmt" "github.com/go-logr/logr" - authorinoapi "github.com/kuadrant/authorino/api/v1beta2" apierrors "k8s.io/apimachinery/pkg/api/errors" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "sigs.k8s.io/controller-runtime/pkg/handler" - "sigs.k8s.io/controller-runtime/pkg/reconcile" gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" kuadrantv1beta3 "github.com/kuadrant/kuadrant-operator/api/v1beta3" - kuadrantgatewayapi "github.com/kuadrant/kuadrant-operator/pkg/library/gatewayapi" "github.com/kuadrant/kuadrant-operator/pkg/library/kuadrant" - "github.com/kuadrant/kuadrant-operator/pkg/library/mappers" "github.com/kuadrant/kuadrant-operator/pkg/library/reconcilers" ) @@ -245,34 +240,3 @@ func (r *AuthPolicyReconciler) reconcileRouteParentGatewayPolicies(ctx context.C } return nil } - -// SetupWithManager sets up the controller with the Manager. -func (r *AuthPolicyReconciler) SetupWithManager(mgr ctrl.Manager) error { - ok, err := kuadrantgatewayapi.IsGatewayAPIInstalled(mgr.GetRESTMapper()) - if err != nil { - return err - } - if !ok { - r.Logger().Info("AuthPolicy controller disabled. GatewayAPI was not found") - return nil - } - - httpRouteEventMapper := mappers.NewHTTPRouteEventMapper(mappers.WithLogger(r.Logger().WithName("httproute.mapper")), mappers.WithClient(mgr.GetClient())) - gatewayEventMapper := mappers.NewGatewayEventMapper( - kuadrantv1beta3.NewAuthPolicyType(), - mappers.WithLogger(r.Logger().WithName("gateway.mapper")), - mappers.WithClient(mgr.GetClient()), - ) - - return ctrl.NewControllerManagedBy(mgr). - For(&kuadrantv1beta3.AuthPolicy{}). - Owns(&authorinoapi.AuthConfig{}). - Watches( - &gatewayapiv1.HTTPRoute{}, - handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, object client.Object) []reconcile.Request { - return httpRouteEventMapper.MapToPolicy(ctx, object, kuadrantv1beta3.NewAuthPolicyType()) - }), - ). - Watches(&gatewayapiv1.Gateway{}, handler.EnqueueRequestsFromMapFunc(gatewayEventMapper.Map)). - Complete(r) -} diff --git a/controllers/authpolicy_envoysecuritypolicy_controller.go b/controllers/authpolicy_envoysecuritypolicy_controller.go deleted file mode 100644 index f7225ac51..000000000 --- a/controllers/authpolicy_envoysecuritypolicy_controller.go +++ /dev/null @@ -1,209 +0,0 @@ -package controllers - -import ( - "context" - "encoding/json" - "fmt" - - egv1alpha1 "github.com/envoyproxy/gateway/api/v1alpha1" - "github.com/go-logr/logr" - "github.com/samber/lo" - 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" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/handler" - gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" - gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - - kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" - kuadrantv1beta3 "github.com/kuadrant/kuadrant-operator/api/v1beta3" - kuadrantenvoygateway "github.com/kuadrant/kuadrant-operator/pkg/envoygateway" - "github.com/kuadrant/kuadrant-operator/pkg/kuadranttools" - kuadrantgatewayapi "github.com/kuadrant/kuadrant-operator/pkg/library/gatewayapi" - "github.com/kuadrant/kuadrant-operator/pkg/library/kuadrant" - "github.com/kuadrant/kuadrant-operator/pkg/library/mappers" - "github.com/kuadrant/kuadrant-operator/pkg/library/reconcilers" - "github.com/kuadrant/kuadrant-operator/pkg/library/utils" -) - -// AuthPolicyEnvoySecurityPolicyReconciler reconciles SecurityPolicy objects for auth -type AuthPolicyEnvoySecurityPolicyReconciler struct { - *reconcilers.BaseReconciler -} - -//+kubebuilder:rbac:groups=gateway.envoyproxy.io,resources=securitypolicies,verbs=get;list;watch;create;update;patch;delete - -func (r *AuthPolicyEnvoySecurityPolicyReconciler) Reconcile(eventCtx context.Context, req ctrl.Request) (ctrl.Result, error) { - logger := r.Logger().WithValues("Kuadrant", req.NamespacedName) - logger.Info("Reconciling auth SecurityPolicy") - ctx := logr.NewContext(eventCtx, logger) - - kObj := &kuadrantv1beta1.Kuadrant{} - if err := r.Client().Get(ctx, req.NamespacedName, kObj); err != nil { - if apierrors.IsNotFound(err) { - logger.Info("no kuadrant object found") - return ctrl.Result{}, nil - } - logger.Error(err, "failed to get kuadrant object") - return ctrl.Result{}, err - } - - if logger.V(1).Enabled() { - jsonData, err := json.MarshalIndent(kObj, "", " ") - if err != nil { - return ctrl.Result{}, err - } - logger.V(1).Info(string(jsonData)) - } - - topology, err := kuadranttools.TopologyForPolicies(ctx, r.Client(), kuadrantv1beta3.NewAuthPolicyType()) - if err != nil { - return ctrl.Result{}, err - } - - for _, policy := range topology.Policies() { - err := r.reconcileSecurityPolicy(ctx, policy, kObj.Namespace) - if err != nil { - return ctrl.Result{}, err - } - } - - return ctrl.Result{}, nil -} - -func (r *AuthPolicyEnvoySecurityPolicyReconciler) reconcileSecurityPolicy(ctx context.Context, policy kuadrantgatewayapi.PolicyNode, kuadrantNamespace string) error { - logger, _ := logr.FromContext(ctx) - logger = logger.WithName("reconcileSecurityPolicy") - - esp := envoySecurityPolicy(policy, kuadrantNamespace) - if err := r.SetOwnerReference(policy.Policy, esp); err != nil { - return err - } - - if err := r.ReconcileResource(ctx, &egv1alpha1.SecurityPolicy{}, esp, kuadrantenvoygateway.EnvoySecurityPolicyMutator); err != nil && !apierrors.IsAlreadyExists(err) { - logger.Error(err, "failed to reconcile envoy SecurityPolicy resource") - return err - } - - return nil -} - -func envoySecurityPolicy(policy kuadrantgatewayapi.PolicyNode, kuadrantNamespace string) *egv1alpha1.SecurityPolicy { - esp := &egv1alpha1.SecurityPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: EnvoySecurityPolicyName(policy.GetName()), - Namespace: policy.GetNamespace(), - Labels: map[string]string{ - kuadrant.KuadrantNamespaceAnnotation: kuadrantNamespace, - }, - }, - Spec: egv1alpha1.SecurityPolicySpec{ - PolicyTargetReferences: egv1alpha1.PolicyTargetReferences{ - TargetRefs: []gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{}, - }, - ExtAuth: &egv1alpha1.ExtAuth{ - GRPC: &egv1alpha1.GRPCExtAuthService{ - BackendRefs: []egv1alpha1.BackendRef{ - { - BackendObjectReference: gatewayapiv1.BackendObjectReference{ - Name: kuadrant.AuthorinoServiceName, - Kind: ptr.To[gatewayapiv1.Kind]("Service"), - Namespace: ptr.To(gatewayapiv1.Namespace(kuadrantNamespace)), - Port: ptr.To(gatewayapiv1.PortNumber(50051)), - }, - }, - }, - }, - }, - }, - } - kuadrant.AnnotateObject(esp, kuadrantNamespace) - - // if targetref has been deleted, or - // if gateway target and not programmed, or - // route target which is not accepted by any parent; - // tag for deletion - targetRef := policy.TargetRef() - if (targetRef == nil || targetRef.GetGatewayNode() != nil && meta.IsStatusConditionFalse(targetRef.GetGatewayNode().Status.Conditions, string(gatewayapiv1.GatewayConditionProgrammed))) || - (targetRef.GetRouteNode() != nil && !lo.ContainsBy(targetRef.GetRouteNode().Status.Parents, func(p gatewayapiv1.RouteParentStatus) bool { - return meta.IsStatusConditionTrue(p.Conditions, string(gatewayapiv1.RouteConditionAccepted)) - })) { - utils.TagObjectToDelete(esp) - return esp - } - - targetNetworkObjectGvk := targetRef.GetObject().GetObjectKind().GroupVersionKind() - esp.Spec.PolicyTargetReferences.TargetRefs = append(esp.Spec.PolicyTargetReferences.TargetRefs, - gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ - LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.Group(targetNetworkObjectGvk.Group), - Kind: gatewayapiv1.Kind(targetNetworkObjectGvk.Kind), - Name: gatewayapiv1.ObjectName(targetRef.GetObject().GetName()), - }, - }) - - return esp -} - -func EnvoySecurityPolicyName(targetName string) string { - return fmt.Sprintf("for-%s", targetName) -} - -// SetupWithManager sets up the controller with the Manager. -func (r *AuthPolicyEnvoySecurityPolicyReconciler) SetupWithManager(mgr ctrl.Manager) error { - ok, err := kuadrantenvoygateway.IsEnvoyGatewaySecurityPolicyInstalled(mgr.GetRESTMapper()) - if err != nil { - return err - } - if !ok { - r.Logger().Info("EnvoyGateway SecurityPolicy controller disabled. EnvoyGateway API was not found") - return nil - } - - ok, err = kuadrantgatewayapi.IsGatewayAPIInstalled(mgr.GetRESTMapper()) - if err != nil { - return err - } - if !ok { - r.Logger().Info("EnvoyGateway SecurityPolicy controller disabled. GatewayAPI was not found") - return nil - } - - securityPolicyToKuadrantEventMapper := mappers.NewSecurityPolicyToKuadrantEventMapper( - mappers.WithLogger(r.Logger().WithName("securityPolicyToKuadrantEventMapper")), - mappers.WithClient(r.Client()), - ) - policyToKuadrantEventMapper := mappers.NewPolicyToKuadrantEventMapper( - mappers.WithLogger(r.Logger().WithName("policyToKuadrantEventMapper")), - mappers.WithClient(r.Client()), - ) - gatewayToKuadrantEventMapper := mappers.NewGatewayToKuadrantEventMapper( - mappers.WithLogger(r.Logger().WithName("gatewayToKuadrantEventMapper")), - mappers.WithClient(r.Client()), - ) - httpRouteToKuadrantEventMapper := mappers.NewHTTPRouteToKuadrantEventMapper( - mappers.WithLogger(r.Logger().WithName("httpRouteToKuadrantEventMapper")), - mappers.WithClient(r.Client()), - ) - return ctrl.NewControllerManagedBy(mgr). - For(&kuadrantv1beta1.Kuadrant{}). - Watches( - &egv1alpha1.SecurityPolicy{}, - handler.EnqueueRequestsFromMapFunc(securityPolicyToKuadrantEventMapper.Map), - ). - Watches( - &kuadrantv1beta3.AuthPolicy{}, - handler.EnqueueRequestsFromMapFunc(policyToKuadrantEventMapper.Map), - ). - Watches( - &gatewayapiv1.Gateway{}, - handler.EnqueueRequestsFromMapFunc(gatewayToKuadrantEventMapper.Map), - ). - Watches( - &gatewayapiv1.HTTPRoute{}, - handler.EnqueueRequestsFromMapFunc(httpRouteToKuadrantEventMapper.Map), - ). - Complete(r) -} diff --git a/controllers/authpolicy_istio_authorizationpolicy_controller.go b/controllers/authpolicy_istio_authorizationpolicy_controller.go deleted file mode 100644 index 5253cb3be..000000000 --- a/controllers/authpolicy_istio_authorizationpolicy_controller.go +++ /dev/null @@ -1,367 +0,0 @@ -package controllers - -import ( - "context" - "encoding/json" - "fmt" - - "github.com/go-logr/logr" - "github.com/google/uuid" - "github.com/samber/lo" - istiosecurity "istio.io/api/security/v1beta1" - istiov1beta1 "istio.io/client-go/pkg/apis/security/v1beta1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/utils/env" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/handler" - gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" - gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - - kuadrantv1beta3 "github.com/kuadrant/kuadrant-operator/api/v1beta3" - "github.com/kuadrant/kuadrant-operator/pkg/common" - kuadrantistioutils "github.com/kuadrant/kuadrant-operator/pkg/istio" - "github.com/kuadrant/kuadrant-operator/pkg/kuadranttools" - kuadrantgatewayapi "github.com/kuadrant/kuadrant-operator/pkg/library/gatewayapi" - "github.com/kuadrant/kuadrant-operator/pkg/library/kuadrant" - "github.com/kuadrant/kuadrant-operator/pkg/library/mappers" - "github.com/kuadrant/kuadrant-operator/pkg/library/reconcilers" - "github.com/kuadrant/kuadrant-operator/pkg/library/utils" -) - -var KuadrantExtAuthProviderName = env.GetString("AUTH_PROVIDER", "kuadrant-authorization") - -// AuthPolicyIstioAuthorizationPolicyReconciler reconciles IstioAuthorizationPolicy objects for auth -type AuthPolicyIstioAuthorizationPolicyReconciler struct { - *reconcilers.BaseReconciler -} - -//+kubebuilder:rbac:groups=security.istio.io,resources=authorizationpolicies,verbs=get;list;watch;create;update;patch;delete - -func (r *AuthPolicyIstioAuthorizationPolicyReconciler) Reconcile(eventCtx context.Context, req ctrl.Request) (ctrl.Result, error) { - logger := r.Logger().WithValues("Gateway", req.NamespacedName, "request id", uuid.NewString()) - logger.Info("Reconciling istio AuthorizationPolicy") - ctx := logr.NewContext(eventCtx, logger) - - gw := &gatewayapiv1.Gateway{} - if err := r.Client().Get(ctx, req.NamespacedName, gw); err != nil { - if apierrors.IsNotFound(err) { - logger.Info("no gateway found") - return ctrl.Result{}, nil - } - logger.Error(err, "failed to get gateway") - return ctrl.Result{}, err - } - - if logger.V(1).Enabled() { - jsonData, err := json.MarshalIndent(gw, "", " ") - if err != nil { - return ctrl.Result{}, err - } - logger.V(1).Info(string(jsonData)) - } - - if !kuadrant.IsKuadrantManaged(gw) { - return ctrl.Result{}, nil - } - - topology, err := kuadranttools.TopologyFromGateway(ctx, r.Client(), gw, kuadrantv1beta3.NewAuthPolicyType()) - if err != nil { - return ctrl.Result{}, err - } - topologyIndex := kuadrantgatewayapi.NewTopologyIndexes(topology) - policies := lo.FilterMap(topologyIndex.PoliciesFromGateway(gw), func(policy kuadrantgatewayapi.Policy, _ int) (*kuadrantv1beta3.AuthPolicy, bool) { - ap, ok := policy.(*kuadrantv1beta3.AuthPolicy) - if !ok { - return nil, false - } - return ap, true - }) - - for _, policy := range policies { - iap, err := r.istioAuthorizationPolicy(ctx, gw, policy, topologyIndex, topology) - if err != nil { - return ctrl.Result{}, err - } - - if policy.GetDeletionTimestamp() != nil { - utils.TagObjectToDelete(iap) - } - - if err := r.ReconcileResource(ctx, &istiov1beta1.AuthorizationPolicy{}, iap, kuadrantistioutils.AuthorizationPolicyMutator); err != nil && !apierrors.IsAlreadyExists(err) { - logger.Error(err, "failed to reconcile IstioAuthorizationPolicy resource") - return ctrl.Result{}, err - } - } - - return ctrl.Result{}, nil -} - -func (r *AuthPolicyIstioAuthorizationPolicyReconciler) istioAuthorizationPolicy(ctx context.Context, gateway *gatewayapiv1.Gateway, ap *kuadrantv1beta3.AuthPolicy, topologyIndex *kuadrantgatewayapi.TopologyIndexes, topology *kuadrantgatewayapi.Topology) (*istiov1beta1.AuthorizationPolicy, error) { - logger, _ := logr.FromContext(ctx) - logger = logger.WithName("istioAuthorizationPolicy") - - iap := &istiov1beta1.AuthorizationPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: IstioAuthorizationPolicyName(gateway.Name, ap.GetTargetRef()), - Namespace: gateway.Namespace, - Labels: istioAuthorizationPolicyLabels(client.ObjectKeyFromObject(gateway), client.ObjectKeyFromObject(ap)), - }, - Spec: istiosecurity.AuthorizationPolicy{ - Action: istiosecurity.AuthorizationPolicy_CUSTOM, - TargetRef: kuadrantistioutils.PolicyTargetRefFromGateway(gateway), - ActionDetail: &istiosecurity.AuthorizationPolicy_Provider{ - Provider: &istiosecurity.AuthorizationPolicy_ExtensionProvider{ - Name: KuadrantExtAuthProviderName, - }, - }, - }, - } - - gwHostnames := kuadrantgatewayapi.GatewayHostnames(gateway) - if len(gwHostnames) == 0 { - gwHostnames = []gatewayapiv1.Hostname{"*"} - } - - var route *gatewayapiv1.HTTPRoute - var routeHostnames []gatewayapiv1.Hostname - targetNetworkObject := topologyIndex.GetPolicyTargetObject(ap) - - switch obj := targetNetworkObject.(type) { - case *gatewayapiv1.HTTPRoute: - route = obj - if len(route.Spec.Hostnames) > 0 { - routeHostnames = kuadrantgatewayapi.FilterValidSubdomains(gwHostnames, route.Spec.Hostnames) - } else { - routeHostnames = gwHostnames - } - case *gatewayapiv1.Gateway: - // fake a single httproute with all rules from all httproutes accepted by the gateway, - // that do not have an authpolicy of its own, so we can generate wasm rules for those cases - rules := make([]gatewayapiv1.HTTPRouteRule, 0) - routes := topology.Routes() - for idx := range routes { - route := routes[idx].Route() - // skip routes that have an authpolicy of its own - if route.GetAnnotations()[common.AuthPolicyBackRefAnnotation] != "" { - continue - } - rules = append(rules, route.Spec.Rules...) - } - if len(rules) == 0 { - logger.V(1).Info("no httproutes attached to the targeted gateway, skipping istio authorizationpolicy for the gateway authpolicy") - utils.TagObjectToDelete(iap) - return iap, nil - } - route = &gatewayapiv1.HTTPRoute{ - Spec: gatewayapiv1.HTTPRouteSpec{ - Hostnames: gwHostnames, - Rules: rules, - }, - } - routeHostnames = gwHostnames - } - - rules := istioAuthorizationPolicyRulesFromHTTPRoute(route) - if len(rules) > 0 { - // make sure all istio authorizationpolicy rules include the hosts so we don't send a request to authorino for hosts that are not in the scope of the policy - hosts := utils.HostnamesToStrings(routeHostnames) - for i := range rules { - for j := range rules[i].To { - if len(rules[i].To[j].Operation.Hosts) > 0 { - continue - } - rules[i].To[j].Operation.Hosts = hosts - } - } - iap.Spec.Rules = rules - } - - if err := r.SetOwnerReference(gateway, iap); err != nil { - return nil, err - } - - return iap, nil -} - -func (r *AuthPolicyIstioAuthorizationPolicyReconciler) SetupWithManager(mgr ctrl.Manager) error { - ok, err := kuadrantistioutils.IsAuthorizationPolicyInstalled(mgr.GetRESTMapper()) - if err != nil { - return err - } - if !ok { - r.Logger().Info("Istio AuthorizationPolicy controller disabled. Istio was not found") - return nil - } - - ok, err = kuadrantgatewayapi.IsGatewayAPIInstalled(mgr.GetRESTMapper()) - if err != nil { - return err - } - if !ok { - r.Logger().Info("Istio AuthorizationPolicy controller disabled. GatewayAPI was not found") - return nil - } - - httpRouteToParentGatewaysEventMapper := mappers.NewHTTPRouteToParentGatewaysEventMapper( - mappers.WithLogger(r.Logger().WithName("httpRouteToParentGatewaysEventMapper")), - ) - - apToParentGatewaysEventMapper := mappers.NewPolicyToParentGatewaysEventMapper( - mappers.WithLogger(r.Logger().WithName("authPolicyToParentGatewaysEventMapper")), - mappers.WithClient(r.Client()), - ) - - return ctrl.NewControllerManagedBy(mgr). - For(&gatewayapiv1.Gateway{}). - Owns(&istiov1beta1.AuthorizationPolicy{}). - Watches( - &gatewayapiv1.HTTPRoute{}, - handler.EnqueueRequestsFromMapFunc(httpRouteToParentGatewaysEventMapper.Map), - ). - Watches( - &kuadrantv1beta3.AuthPolicy{}, - handler.EnqueueRequestsFromMapFunc(apToParentGatewaysEventMapper.Map), - ). - Complete(r) -} - -// IstioAuthorizationPolicyName generates the name of an AuthorizationPolicy. -func IstioAuthorizationPolicyName(gwName string, targetRef gatewayapiv1alpha2.LocalPolicyTargetReference) string { - switch targetRef.Kind { - case "Gateway": - return fmt.Sprintf("on-%s", gwName) // Without this, IAP will be named: on--using-; - case "HTTPRoute": - return fmt.Sprintf("on-%s-using-%s", gwName, targetRef.Name) - } - return "" -} - -func istioAuthorizationPolicyLabels(gwKey, apKey client.ObjectKey) map[string]string { - return map[string]string{ - common.AuthPolicyBackRefAnnotation: apKey.Name, - fmt.Sprintf("%s-namespace", common.AuthPolicyBackRefAnnotation): apKey.Namespace, - "gateway-namespace": gwKey.Namespace, - "gateway": gwKey.Name, - } -} - -// istioAuthorizationPolicyRulesFromHTTPRoute builds a list of Istio AuthorizationPolicy rules from an HTTPRoute. -// v1beta2 version of this function used RouteSelectors -// v1beta3 should use Section Names, once implemented -func istioAuthorizationPolicyRulesFromHTTPRoute(route *gatewayapiv1.HTTPRoute) []*istiosecurity.Rule { - istioRules := make([]*istiosecurity.Rule, 0) - for _, rule := range route.Spec.Rules { - istioRules = append(istioRules, istioAuthorizationPolicyRulesFromHTTPRouteRule(rule, []gatewayapiv1.Hostname{"*"})...) - } - - return istioRules -} - -// istioAuthorizationPolicyRulesFromHTTPRouteRule builds a list of Istio AuthorizationPolicy rules from a HTTPRouteRule -// and a list of hostnames. -// * Each combination of HTTPRouteMatch and hostname yields one condition. -// * Rules that specify no explicit HTTPRouteMatch are assumed to match all requests (i.e. implicit catch-all rule.) -// * Empty list of hostnames yields a condition without a hostname pattern expression. -func istioAuthorizationPolicyRulesFromHTTPRouteRule(rule gatewayapiv1.HTTPRouteRule, hostnames []gatewayapiv1.Hostname) (istioRules []*istiosecurity.Rule) { - hosts := []string{} - for _, hostname := range hostnames { - if hostname == "*" { - continue - } - hosts = append(hosts, string(hostname)) - } - - // no http route matches → we only need one simple istio rule or even no rule at all - if len(rule.Matches) == 0 { - if len(hosts) == 0 { - return - } - istioRule := &istiosecurity.Rule{ - To: []*istiosecurity.Rule_To{ - { - Operation: &istiosecurity.Operation{ - Hosts: hosts, - }, - }, - }, - } - istioRules = append(istioRules, istioRule) - return - } - - // http route matches and possibly hostnames → we need one istio rule per http route match - for _, match := range rule.Matches { - istioRule := &istiosecurity.Rule{} - - var operation *istiosecurity.Operation - method := match.Method - path := match.Path - - if len(hosts) > 0 || method != nil || path != nil { - operation = &istiosecurity.Operation{} - } - - // hosts - if len(hosts) > 0 { - operation.Hosts = hosts - } - - // method - if method != nil { - operation.Methods = []string{string(*method)} - } - - // path - if path != nil { - operator := "*" // gateway api defaults to PathMatchPathPrefix - skip := false - if path.Type != nil { - switch *path.Type { - case gatewayapiv1.PathMatchExact: - operator = "" - case gatewayapiv1.PathMatchRegularExpression: - // ignore this rule as it is not supported by Istio - Authorino will check it anyway - skip = true - } - } - if !skip { - value := "/" - if path.Value != nil { - value = *path.Value - } - operation.Paths = []string{fmt.Sprintf("%s%s", value, operator)} - } - } - - if operation != nil { - istioRule.To = []*istiosecurity.Rule_To{ - {Operation: operation}, - } - } - - // headers - if len(match.Headers) > 0 { - istioRule.When = []*istiosecurity.Condition{} - - for idx := range match.Headers { - header := match.Headers[idx] - if header.Type != nil && *header.Type == gatewayapiv1.HeaderMatchRegularExpression { - // skip this rule as it is not supported by Istio - Authorino will check it anyway - continue - } - headerCondition := &istiosecurity.Condition{ - Key: fmt.Sprintf("request.headers[%s]", header.Name), - Values: []string{header.Value}, - } - istioRule.When = append(istioRule.When, headerCondition) - } - } - - // query params: istio does not support query params in authorization policies, so we build them in the authconfig instead - - istioRules = append(istioRules, istioRule) - } - return -} diff --git a/controllers/authpolicy_istio_authorizationpolicy_test.go b/controllers/authpolicy_istio_authorizationpolicy_test.go deleted file mode 100644 index a64baf5d7..000000000 --- a/controllers/authpolicy_istio_authorizationpolicy_test.go +++ /dev/null @@ -1,345 +0,0 @@ -//go:build unit - -package controllers - -import ( - "reflect" - "testing" - - istiosecurity "istio.io/api/security/v1beta1" - "k8s.io/utils/ptr" - gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" -) - -func TestIstioAuthorizationPolicyRulesFromHTTPRouteRule(t *testing.T) { - testCases := []struct { - name string - hostnames []gatewayapiv1.Hostname - rule gatewayapiv1.HTTPRouteRule - expected []*istiosecurity.Rule - }{ - { - name: "No HTTPRouteMatch", - hostnames: []gatewayapiv1.Hostname{"toystore.kuadrant.io"}, - rule: gatewayapiv1.HTTPRouteRule{}, - expected: []*istiosecurity.Rule{ - { - To: []*istiosecurity.Rule_To{ - { - Operation: &istiosecurity.Operation{ - Hosts: []string{"toystore.kuadrant.io"}, - }, - }, - }, - }, - }, - }, - { - name: "Single HTTPRouteMatch", - hostnames: []gatewayapiv1.Hostname{"toystore.kuadrant.io"}, - rule: gatewayapiv1.HTTPRouteRule{ - Matches: []gatewayapiv1.HTTPRouteMatch{ - { - Path: &gatewayapiv1.HTTPPathMatch{ - Type: ptr.To(gatewayapiv1.PathMatchType("PathPrefix")), - Value: ptr.To("/toy"), - }, - }, - }, - }, - expected: []*istiosecurity.Rule{ - { - To: []*istiosecurity.Rule_To{ - { - Operation: &istiosecurity.Operation{ - Hosts: []string{"toystore.kuadrant.io"}, - Paths: []string{"/toy*"}, - }, - }, - }, - }, - }, - }, - { - name: "Multiple HTTPRouteMatches", - hostnames: []gatewayapiv1.Hostname{"toystore.kuadrant.io"}, - rule: gatewayapiv1.HTTPRouteRule{ - Matches: []gatewayapiv1.HTTPRouteMatch{ - { - Path: &gatewayapiv1.HTTPPathMatch{ - Type: ptr.To(gatewayapiv1.PathMatchType("PathPrefix")), - Value: ptr.To("/toy"), - }, - }, - { - Path: &gatewayapiv1.HTTPPathMatch{ - Type: ptr.To(gatewayapiv1.PathMatchType("Exact")), - Value: ptr.To("/foo"), - }, - }, - }, - }, - expected: []*istiosecurity.Rule{ - { - To: []*istiosecurity.Rule_To{ - { - Operation: &istiosecurity.Operation{ - Hosts: []string{"toystore.kuadrant.io"}, - Paths: []string{"/toy*"}, - }, - }, - }, - }, - { - To: []*istiosecurity.Rule_To{ - { - Operation: &istiosecurity.Operation{ - Hosts: []string{"toystore.kuadrant.io"}, - Paths: []string{"/foo"}, - }, - }, - }, - }, - }, - }, - { - name: "Multiple hosts", - hostnames: []gatewayapiv1.Hostname{"toystore.kuadrant.io", "gamestore.kuadrant.io"}, - rule: gatewayapiv1.HTTPRouteRule{ - Matches: []gatewayapiv1.HTTPRouteMatch{ - { - Path: &gatewayapiv1.HTTPPathMatch{ - Type: ptr.To(gatewayapiv1.PathMatchType("PathPrefix")), - Value: ptr.To("/toy"), - }, - }, - }, - }, - expected: []*istiosecurity.Rule{ - { - To: []*istiosecurity.Rule_To{ - { - Operation: &istiosecurity.Operation{ - Hosts: []string{"toystore.kuadrant.io", "gamestore.kuadrant.io"}, - Paths: []string{"/toy*"}, - }, - }, - }, - }, - }, - }, - { - name: "Catch-all host is ignored", - hostnames: []gatewayapiv1.Hostname{"toystore.kuadrant.io", "*"}, - rule: gatewayapiv1.HTTPRouteRule{ - Matches: []gatewayapiv1.HTTPRouteMatch{ - { - Path: &gatewayapiv1.HTTPPathMatch{ - Type: ptr.To(gatewayapiv1.PathMatchType("PathPrefix")), - Value: ptr.To("/toy"), - }, - }, - }, - }, - expected: []*istiosecurity.Rule{ - { - To: []*istiosecurity.Rule_To{ - { - Operation: &istiosecurity.Operation{ - Hosts: []string{"toystore.kuadrant.io"}, - Paths: []string{"/toy*"}, - }, - }, - }, - }, - }, - }, - { - name: "Method", - rule: gatewayapiv1.HTTPRouteRule{ - Matches: []gatewayapiv1.HTTPRouteMatch{ - { - Method: ptr.To(gatewayapiv1.HTTPMethod("GET")), - }, - }, - }, - expected: []*istiosecurity.Rule{ - { - To: []*istiosecurity.Rule_To{ - { - Operation: &istiosecurity.Operation{ - Methods: []string{"GET"}, - }, - }, - }, - }, - }, - }, - { - name: "PathMatchExact", - rule: gatewayapiv1.HTTPRouteRule{ - Matches: []gatewayapiv1.HTTPRouteMatch{ - { - Path: &gatewayapiv1.HTTPPathMatch{ - Type: ptr.To(gatewayapiv1.PathMatchType("Exact")), - Value: ptr.To("/toy"), - }, - }, - }, - }, - expected: []*istiosecurity.Rule{ - { - To: []*istiosecurity.Rule_To{ - { - Operation: &istiosecurity.Operation{ - Paths: []string{"/toy"}, - }, - }, - }, - }, - }, - }, - { - name: "PathMatchPrefix", - rule: gatewayapiv1.HTTPRouteRule{ - Matches: []gatewayapiv1.HTTPRouteMatch{ - { - Path: &gatewayapiv1.HTTPPathMatch{ - Type: ptr.To(gatewayapiv1.PathMatchType("PathPrefix")), - Value: ptr.To("/toy"), - }, - }, - }, - }, - expected: []*istiosecurity.Rule{ - { - To: []*istiosecurity.Rule_To{ - { - Operation: &istiosecurity.Operation{ - Paths: []string{"/toy*"}, - }, - }, - }, - }, - }, - }, - { - name: "PathMatchRegularExpression", - rule: gatewayapiv1.HTTPRouteRule{ - Matches: []gatewayapiv1.HTTPRouteMatch{ - { - Path: &gatewayapiv1.HTTPPathMatch{ - Type: ptr.To(gatewayapiv1.PathMatchType("RegularExpression")), - Value: ptr.To("/toy"), - }, - }, - }, - }, - expected: []*istiosecurity.Rule{ - { - To: []*istiosecurity.Rule_To{ - { - Operation: &istiosecurity.Operation{}, - }, - }, - }, - }, - }, - { - name: "Single header match", - rule: gatewayapiv1.HTTPRouteRule{ - Matches: []gatewayapiv1.HTTPRouteMatch{ - { - Headers: []gatewayapiv1.HTTPHeaderMatch{ - { - Type: ptr.To(gatewayapiv1.HeaderMatchType("Exact")), - Name: "x-foo", - Value: "a-value", - }, - }, - }, - }, - }, - expected: []*istiosecurity.Rule{ - { - When: []*istiosecurity.Condition{ - { - Key: "request.headers[x-foo]", - Values: []string{"a-value"}, - }, - }, - }, - }, - }, - { - name: "Multiple header matches", - rule: gatewayapiv1.HTTPRouteRule{ - Matches: []gatewayapiv1.HTTPRouteMatch{ - { - Headers: []gatewayapiv1.HTTPHeaderMatch{ - { - Type: ptr.To(gatewayapiv1.HeaderMatchType("Exact")), - Name: "x-foo", - Value: "a-value", - }, - { - Type: ptr.To(gatewayapiv1.HeaderMatchType("Exact")), - Name: "x-bar", - Value: "other-value", - }, - }, - }, - }, - }, - expected: []*istiosecurity.Rule{ - { - When: []*istiosecurity.Condition{ - { - Key: "request.headers[x-foo]", - Values: []string{"a-value"}, - }, - { - Key: "request.headers[x-bar]", - Values: []string{"other-value"}, - }, - }, - }, - }, - }, - { - name: "HeaderMatchRegularExpression", - rule: gatewayapiv1.HTTPRouteRule{ - Matches: []gatewayapiv1.HTTPRouteMatch{ - { - Headers: []gatewayapiv1.HTTPHeaderMatch{ - { - Type: ptr.To(gatewayapiv1.HeaderMatchType("RegularExpression")), - Name: "x-foo", - Value: "^a+.*$", - }, - }, - }, - }, - }, - expected: []*istiosecurity.Rule{ - { - When: []*istiosecurity.Condition{}, - }, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - result := istioAuthorizationPolicyRulesFromHTTPRouteRule(tc.rule, tc.hostnames) - if len(result) != len(tc.expected) { - t.Errorf("Expected %d rule, got %d", len(tc.expected), len(result)) - } - for i := range result { - if !reflect.DeepEqual(result[i], tc.expected[i]) { - t.Errorf("Expected rule %d to be %v, got %v", i, tc.expected[i], result[i]) - } - } - }) - } -} diff --git a/controllers/envoysecuritypolicy_referencegrant_controller.go b/controllers/envoysecuritypolicy_referencegrant_controller.go deleted file mode 100644 index e0bcd3d01..000000000 --- a/controllers/envoysecuritypolicy_referencegrant_controller.go +++ /dev/null @@ -1,166 +0,0 @@ -package controllers - -import ( - "context" - "encoding/json" - - egv1alpha1 "github.com/envoyproxy/gateway/api/v1alpha1" - "github.com/go-logr/logr" - "github.com/samber/lo" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/utils/ptr" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/handler" - gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" - gatewayapiv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - - kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" - kuadrantenvoygateway "github.com/kuadrant/kuadrant-operator/pkg/envoygateway" - kuadrantgatewayapi "github.com/kuadrant/kuadrant-operator/pkg/library/gatewayapi" - "github.com/kuadrant/kuadrant-operator/pkg/library/kuadrant" - "github.com/kuadrant/kuadrant-operator/pkg/library/mappers" - "github.com/kuadrant/kuadrant-operator/pkg/library/reconcilers" - "github.com/kuadrant/kuadrant-operator/pkg/library/utils" -) - -const ( - KuadrantReferenceGrantName = "kuadrant-authorization-rg" -) - -// EnvoySecurityPolicyReferenceGrantReconciler reconciles ReferenceGrant objects for auth -type EnvoySecurityPolicyReferenceGrantReconciler struct { - *reconcilers.BaseReconciler -} - -//+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=referencegrants,verbs=get;list;watch;create;update;patch;delete - -func (r *EnvoySecurityPolicyReferenceGrantReconciler) Reconcile(eventCtx context.Context, req ctrl.Request) (ctrl.Result, error) { - logger := r.Logger().WithValues("Kuadrant", req.NamespacedName) - logger.Info("Reconciling SecurityPolicy ReferenceGrant") - ctx := logr.NewContext(eventCtx, logger) - - kObj := &kuadrantv1beta1.Kuadrant{} - if err := r.Client().Get(ctx, req.NamespacedName, kObj); err != nil { - if apierrors.IsNotFound(err) { - logger.Info("no kuadrant object found") - return ctrl.Result{}, nil - } - logger.Error(err, "failed to get kuadrant object") - return ctrl.Result{}, err - } - - if logger.V(1).Enabled() { - jsonData, err := json.MarshalIndent(kObj, "", " ") - if err != nil { - return ctrl.Result{}, err - } - logger.V(1).Info(string(jsonData)) - } - - rg, err := r.securityPolicyReferenceGrant(ctx, kObj.Namespace) - if err != nil { - return ctrl.Result{}, err - } - - if err := r.SetOwnerReference(kObj, rg); err != nil { - logger.Error(err, "failed to set owner reference on envoy SecurityPolicy ReferenceGrant resource") - return ctrl.Result{}, err - } - - if err := r.ReconcileResource(ctx, &gatewayapiv1beta1.ReferenceGrant{}, rg, kuadrantenvoygateway.SecurityPolicyReferenceGrantMutator); err != nil && !apierrors.IsAlreadyExists(err) { - logger.Error(err, "failed to reconcile envoy SecurityPolicy ReferenceGrant resource") - return ctrl.Result{}, err - } - - return ctrl.Result{}, nil -} - -func (r *EnvoySecurityPolicyReferenceGrantReconciler) securityPolicyReferenceGrant(ctx context.Context, kuadrantNamespace string) (*gatewayapiv1beta1.ReferenceGrant, error) { - logger, _ := logr.FromContext(ctx) - logger = logger.WithName("securityPolicyReferenceGrant") - - rg := &gatewayapiv1beta1.ReferenceGrant{ - ObjectMeta: metav1.ObjectMeta{ - Name: KuadrantReferenceGrantName, - Namespace: kuadrantNamespace, - }, - Spec: gatewayapiv1beta1.ReferenceGrantSpec{ - To: []gatewayapiv1beta1.ReferenceGrantTo{ - { - Group: "", - Kind: "Service", - Name: ptr.To[gatewayapiv1.ObjectName](kuadrant.AuthorinoServiceName), - }, - }, - }, - } - - espNamespaces := make(map[string]struct{}) - listOptions := &client.ListOptions{LabelSelector: labels.SelectorFromSet(map[string]string{kuadrant.KuadrantNamespaceAnnotation: kuadrantNamespace})} - espList := &egv1alpha1.SecurityPolicyList{} - if err := r.Client().List(ctx, espList, listOptions); err != nil { - return nil, err - } - - for _, esp := range espList.Items { - // only append namespaces that differ from the kuadrant namespace and are not marked for deletion - if esp.DeletionTimestamp == nil && esp.Namespace != kuadrantNamespace { - espNamespaces[esp.Namespace] = struct{}{} - } - } - - if len(espNamespaces) == 0 { - logger.V(1).Info("no security policies exist outside of the kuadrant namespace, skipping ReferenceGrant") - utils.TagObjectToDelete(rg) - return rg, nil - } - - refGrantFrom := lo.MapToSlice(espNamespaces, func(namespace string, _ struct{}) gatewayapiv1beta1.ReferenceGrantFrom { - return gatewayapiv1beta1.ReferenceGrantFrom{ - Group: egv1alpha1.GroupName, - Kind: egv1alpha1.KindSecurityPolicy, - Namespace: gatewayapiv1.Namespace(namespace), - } - }) - rg.Spec.From = refGrantFrom - - return rg, nil -} - -// SetupWithManager sets up the controller with the Manager. -func (r *EnvoySecurityPolicyReferenceGrantReconciler) SetupWithManager(mgr ctrl.Manager) error { - ok, err := kuadrantenvoygateway.IsEnvoyGatewaySecurityPolicyInstalled(mgr.GetRESTMapper()) - if err != nil { - return err - } - if !ok { - r.Logger().Info("Envoy SecurityPolicy ReferenceGrant controller disabled. EnvoyGateway API was not found") - return nil - } - - ok, err = kuadrantgatewayapi.IsGatewayAPIInstalled(mgr.GetRESTMapper()) - if err != nil { - return err - } - if !ok { - r.Logger().Info("Envoy SecurityPolicy ReferenceGrant controller disabled. GatewayAPI was not found") - return nil - } - - securityPolicyToKuadrantEventMapper := mappers.NewSecurityPolicyToKuadrantEventMapper( - mappers.WithLogger(r.Logger().WithName("securityPolicyToKuadrantEventMapper")), - mappers.WithClient(r.Client()), - ) - - return ctrl.NewControllerManagedBy(mgr). - For(&kuadrantv1beta1.Kuadrant{}). - Owns(&gatewayapiv1beta1.ReferenceGrant{}). - Watches( - &egv1alpha1.SecurityPolicy{}, - handler.EnqueueRequestsFromMapFunc(securityPolicyToKuadrantEventMapper.Map), - ). - Complete(r) -} diff --git a/controllers/limitador_limits_reconciler.go b/controllers/limitador_limits_reconciler.go index 1779fb56d..6fcde547c 100644 --- a/controllers/limitador_limits_reconciler.go +++ b/controllers/limitador_limits_reconciler.go @@ -94,14 +94,14 @@ func (r *limitadorLimitsReconciler) buildLimitadorLimits(ctx context.Context, st limitsNamespace := LimitsNamespaceFromRoute(httpRoute.HTTPRoute) for limitKey, mergeableLimit := range effectivePolicy.Spec.Rules() { policy, found := lo.Find(kuadrantv1.PoliciesInPath(effectivePolicy.Path, isRateLimitPolicyAcceptedAndNotDeletedFunc(state)), func(p machinery.Policy) bool { - return p.GetLocator() == mergeableLimit.Source + return p.GetLocator() == mergeableLimit.GetSource() }) if !found { // should never happen - logger.Error(fmt.Errorf("origin policy %s not found in path %s", mergeableLimit.Source, pathID), "failed to build limitador limit definition") + logger.Error(fmt.Errorf("origin policy %s not found in path %s", mergeableLimit.GetSource(), pathID), "failed to build limitador limit definition") continue } limitIdentifier := LimitNameToLimitadorIdentifier(k8stypes.NamespacedName{Name: policy.GetName(), Namespace: policy.GetNamespace()}, limitKey) - limit := mergeableLimit.Spec.(kuadrantv1beta3.Limit) + limit := mergeableLimit.GetSpec().(kuadrantv1beta3.Limit) rateLimits := lo.Map(limit.Rates, func(rate kuadrantv1beta3.Rate, _ int) limitadorv1alpha1.RateLimit { maxValue, seconds := rate.ToSeconds() return limitadorv1alpha1.RateLimit{ diff --git a/controllers/ratelimit_workflow.go b/controllers/ratelimit_workflow.go index de4ec0ccc..91b1760d5 100644 --- a/controllers/ratelimit_workflow.go +++ b/controllers/ratelimit_workflow.go @@ -180,13 +180,13 @@ func rateLimitWasmActionBuilder(pathID string, effectivePolicy EffectiveRateLimi limitsNamespace := LimitsNamespaceFromRoute(httpRoute.HTTPRoute) return func(uniquePolicyRuleKey string, policyRule kuadrantv1.MergeableRule) (wasm.Action, error) { source, found := lo.Find(policiesInPath, func(p machinery.Policy) bool { - return p.GetLocator() == policyRule.Source + return p.GetLocator() == policyRule.GetSource() }) if !found { // should never happen - return wasm.Action{}, fmt.Errorf("could not find source policy %s in path %s", policyRule.Source, pathID) + return wasm.Action{}, fmt.Errorf("could not find source policy %s in path %s", policyRule.GetSource(), pathID) } limitIdentifier := LimitNameToLimitadorIdentifier(k8stypes.NamespacedName{Name: source.GetName(), Namespace: source.GetNamespace()}, uniquePolicyRuleKey) - limit := policyRule.Spec.(kuadrantv1beta3.Limit) + limit := policyRule.GetSpec().(kuadrantv1beta3.Limit) return wasmActionFromLimit(limit, limitIdentifier, limitsNamespace), nil } } diff --git a/controllers/ratelimitpolicy_status_updater.go b/controllers/ratelimitpolicy_status_updater.go index 727d731c1..59f5c26ce 100644 --- a/controllers/ratelimitpolicy_status_updater.go +++ b/controllers/ratelimitpolicy_status_updater.go @@ -124,10 +124,10 @@ func (r *rateLimitPolicyStatusUpdater) enforcedCondition(policy *kuadrantv1beta3 } effectivePolicyRules := effectivePolicy.Spec.Rules() for _, policyRuleKey := range policyRuleKeys { - if effectivePolicyRule, ok := effectivePolicyRules[policyRuleKey]; !ok || (ok && effectivePolicyRule.Source != policy.GetLocator()) { + if effectivePolicyRule, ok := effectivePolicyRules[policyRuleKey]; !ok || (ok && effectivePolicyRule.GetSource() != policy.GetLocator()) { var overriddenBy string if ok { // TODO(guicassolato): !ok → we cannot tell which policy is overriding the rule, this information is lost when the policy rule is dropped during an atomic override - overriddenBy = effectivePolicyRule.Source + overriddenBy = effectivePolicyRule.GetSource() } overridingPolicies[policyRuleKey] = append(overridingPolicies[policyRuleKey], overriddenBy) continue diff --git a/controllers/test_common.go b/controllers/test_common.go index 0f7c67693..f8e5e909e 100644 --- a/controllers/test_common.go +++ b/controllers/test_common.go @@ -55,7 +55,6 @@ import ( kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" kuadrantv1beta3 "github.com/kuadrant/kuadrant-operator/api/v1beta3" "github.com/kuadrant/kuadrant-operator/pkg/library/fieldindexers" - "github.com/kuadrant/kuadrant-operator/pkg/library/kuadrant" "github.com/kuadrant/kuadrant-operator/pkg/library/reconcilers" ) @@ -73,20 +72,6 @@ func SetupKuadrantOperatorForTest(s *runtime.Scheme, cfg *rest.Config) { ) Expect(err).ToNot(HaveOccurred()) - authPolicyBaseReconciler := reconcilers.NewBaseReconciler( - mgr.GetClient(), - mgr.GetScheme(), - mgr.GetAPIReader(), - log.Log.WithName("authpolicy"), - ) - - err = (&AuthPolicyReconciler{ - BaseReconciler: authPolicyBaseReconciler, - TargetRefReconciler: reconcilers.TargetRefReconciler{Client: mgr.GetClient()}, - AffectedPolicyMap: kuadrant.NewAffectedPolicyMap(), - }).SetupWithManager(mgr) - Expect(err).NotTo(HaveOccurred()) - dnsPolicyBaseReconciler := reconcilers.NewBaseReconciler( mgr.GetClient(), mgr.GetScheme(), @@ -128,19 +113,6 @@ func SetupKuadrantOperatorForTest(s *runtime.Scheme, cfg *rest.Config) { Expect(err).NotTo(HaveOccurred()) - authPolicyIstioAuthorizationPolicyReconciler := reconcilers.NewBaseReconciler( - mgr.GetClient(), - mgr.GetScheme(), - mgr.GetAPIReader(), - log.Log.WithName("authpolicy").WithName("istioauthorizationpolicy"), - ) - - err = (&AuthPolicyIstioAuthorizationPolicyReconciler{ - BaseReconciler: authPolicyIstioAuthorizationPolicyReconciler, - }).SetupWithManager(mgr) - - Expect(err).NotTo(HaveOccurred()) - targetStatusBaseReconciler := reconcilers.NewBaseReconciler( mgr.GetClient(), mgr.GetScheme(), @@ -154,32 +126,6 @@ func SetupKuadrantOperatorForTest(s *runtime.Scheme, cfg *rest.Config) { Expect(err).NotTo(HaveOccurred()) - authPolicyEnvoySecurityPolicyReconciler := reconcilers.NewBaseReconciler( - mgr.GetClient(), - mgr.GetScheme(), - mgr.GetAPIReader(), - log.Log.WithName("authpolicy").WithName("securitypolicy"), - ) - - err = (&AuthPolicyEnvoySecurityPolicyReconciler{ - BaseReconciler: authPolicyEnvoySecurityPolicyReconciler, - }).SetupWithManager(mgr) - - Expect(err).NotTo(HaveOccurred()) - - envoySecurityPolicyReferenceGrantReconciler := reconcilers.NewBaseReconciler( - mgr.GetClient(), - mgr.GetScheme(), - mgr.GetAPIReader(), - log.Log.WithName("authpolicy").WithName("referencegrant"), - ) - - err = (&EnvoySecurityPolicyReferenceGrantReconciler{ - BaseReconciler: envoySecurityPolicyReferenceGrantReconciler, - }).SetupWithManager(mgr) - - Expect(err).NotTo(HaveOccurred()) - dClient, err := dynamic.NewForConfig(mgr.GetConfig()) Expect(err).NotTo(HaveOccurred()) diff --git a/main.go b/main.go index 229af861b..92252403d 100644 --- a/main.go +++ b/main.go @@ -55,7 +55,6 @@ import ( kuadrantv1beta3 "github.com/kuadrant/kuadrant-operator/api/v1beta3" "github.com/kuadrant/kuadrant-operator/controllers" "github.com/kuadrant/kuadrant-operator/pkg/library/fieldindexers" - "github.com/kuadrant/kuadrant-operator/pkg/library/kuadrant" "github.com/kuadrant/kuadrant-operator/pkg/library/reconcilers" "github.com/kuadrant/kuadrant-operator/pkg/log" "github.com/kuadrant/kuadrant-operator/version" @@ -170,20 +169,6 @@ func main() { os.Exit(1) } - authPolicyBaseReconciler := reconcilers.NewBaseReconciler( - mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), - log.Log.WithName("authpolicy"), - ) - - if err = (&controllers.AuthPolicyReconciler{ - TargetRefReconciler: reconcilers.TargetRefReconciler{Client: mgr.GetClient()}, - BaseReconciler: authPolicyBaseReconciler, - AffectedPolicyMap: kuadrant.NewAffectedPolicyMap(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "AuthPolicy") - os.Exit(1) - } - dnsPolicyBaseReconciler := reconcilers.NewBaseReconciler( mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), log.Log.WithName("dnspolicy"), @@ -209,17 +194,6 @@ func main() { os.Exit(1) } - authPolicyIstioAuthorizationPolicyReconciler := reconcilers.NewBaseReconciler( - mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), - log.Log.WithName("authpolicy").WithName("istioauthorizationpolicy"), - ) - if err = (&controllers.AuthPolicyIstioAuthorizationPolicyReconciler{ - BaseReconciler: authPolicyIstioAuthorizationPolicyReconciler, - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "AuthPolicyIstioAuthorizationPolicy") - os.Exit(1) - } - targetStatusBaseReconciler := reconcilers.NewBaseReconciler( mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), log.Log.WithName("targetstatus"), @@ -231,28 +205,6 @@ func main() { os.Exit(1) } - authPolicyEnvoySecurityPolicyReconciler := reconcilers.NewBaseReconciler( - mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), - log.Log.WithName("authpolicy").WithName("securitypolicy"), - ) - if err = (&controllers.AuthPolicyEnvoySecurityPolicyReconciler{ - BaseReconciler: authPolicyEnvoySecurityPolicyReconciler, - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "AuthPolicyEnvoySecurityPolicy") - os.Exit(1) - } - - envoySecurityPolicyReferenceGrantReconciler := reconcilers.NewBaseReconciler( - mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), - log.Log.WithName("authpolicy").WithName("referencegrant"), - ) - if err = (&controllers.EnvoySecurityPolicyReferenceGrantReconciler{ - BaseReconciler: envoySecurityPolicyReferenceGrantReconciler, - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "EnvoySecurityPolicyReferenceGrant") - os.Exit(1) - } - //+kubebuilder:scaffold:builder if err = mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/pkg/library/mappers/httproute.go b/pkg/library/mappers/httproute.go deleted file mode 100644 index 01088531b..000000000 --- a/pkg/library/mappers/httproute.go +++ /dev/null @@ -1,92 +0,0 @@ -package mappers - -import ( - "context" - "fmt" - - "k8s.io/utils/ptr" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" - - "github.com/kuadrant/kuadrant-operator/pkg/library/fieldindexers" - kuadrantgatewayapi "github.com/kuadrant/kuadrant-operator/pkg/library/gatewayapi" - "github.com/kuadrant/kuadrant-operator/pkg/library/kuadrant" - "github.com/kuadrant/kuadrant-operator/pkg/library/utils" -) - -func NewHTTPRouteEventMapper(o ...MapperOption) *HTTPRouteEventMapper { - return &HTTPRouteEventMapper{opts: Apply(o...)} -} - -type HTTPRouteEventMapper struct { - opts MapperOptions -} - -func (m *HTTPRouteEventMapper) MapToPolicy(ctx context.Context, obj client.Object, policyType kuadrantgatewayapi.PolicyType) []reconcile.Request { - logger := m.opts.Logger.WithValues("httproute", client.ObjectKeyFromObject(obj)) - requests := make([]reconcile.Request, 0) - httpRoute, ok := obj.(*gatewayapiv1.HTTPRoute) - if !ok { - logger.Info("cannot map httproute event to kuadrant policy", "error", fmt.Sprintf("%T is not a *gatewayapiv1beta1.HTTPRoute", obj)) - return []reconcile.Request{} - } - - gatewayKeys := kuadrantgatewayapi.GetRouteAcceptedGatewayParentKeys(httpRoute) - - for _, gatewayKey := range gatewayKeys { - gateway := &gatewayapiv1.Gateway{} - err := m.opts.Client.Get(ctx, gatewayKey, gateway) - if err != nil { - logger.Info("cannot get gateway", "error", err) - continue - } - - routeList := &gatewayapiv1.HTTPRouteList{} - fields := client.MatchingFields{fieldindexers.HTTPRouteGatewayParentField: client.ObjectKeyFromObject(gateway).String()} - if err = m.opts.Client.List(ctx, routeList, fields); err != nil { - logger.Info("cannot list httproutes", "error", err) - continue - } - - policies, err := policyType.GetList(ctx, m.opts.Client, client.InNamespace(obj.GetNamespace())) - if err != nil { - logger.Error(err, "unable to list policies") - continue - } - if len(policies) == 0 { - logger.Info("no kuadrant policy possibly affected by the gateway related event") - continue - } - topology, err := kuadrantgatewayapi.NewTopology( - kuadrantgatewayapi.WithGateways([]*gatewayapiv1.Gateway{gateway}), - kuadrantgatewayapi.WithRoutes(utils.Map(routeList.Items, ptr.To[gatewayapiv1.HTTPRoute])), - kuadrantgatewayapi.WithPolicies(policies), - kuadrantgatewayapi.WithLogger(logger), - ) - if err != nil { - logger.Info("unable to build topology for gateway", "error", err) - continue - } - index := kuadrantgatewayapi.NewTopologyIndexes(topology) - data := utils.Map(index.PoliciesFromGateway(gateway), func(p kuadrantgatewayapi.Policy) reconcile.Request { - policyKey := client.ObjectKeyFromObject(p) - logger.V(1).Info("kuadrant policy possibly affected by the gateway related event found") - return reconcile.Request{NamespacedName: policyKey} - }) - requests = append(requests, data...) - } - - if len(requests) != 0 { - return requests - } - - policyKey, err := kuadrant.DirectReferencesFromObject(httpRoute, policyType.DirectReferenceAnnotationName()) - if err != nil { - logger.Info("could not create direct reference from object", "error", err) - return requests - } - requests = append(requests, reconcile.Request{NamespacedName: policyKey}) - - return requests -} diff --git a/pkg/library/mappers/httproute_test.go b/pkg/library/mappers/httproute_test.go deleted file mode 100644 index 58782bb9a..000000000 --- a/pkg/library/mappers/httproute_test.go +++ /dev/null @@ -1,137 +0,0 @@ -//go:build unit - -package mappers - -import ( - "context" - "testing" - - "gotest.tools/assert" - appsv1 "k8s.io/api/apps/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/utils/ptr" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" - gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - - kuadrantv1beta3 "github.com/kuadrant/kuadrant-operator/api/v1beta3" - "github.com/kuadrant/kuadrant-operator/pkg/library/fieldindexers" - kuadrantgatewayapi "github.com/kuadrant/kuadrant-operator/pkg/library/gatewayapi" - "github.com/kuadrant/kuadrant-operator/pkg/library/utils" - "github.com/kuadrant/kuadrant-operator/pkg/log" -) - -func TestNewHTTPRouteEventMapper(t *testing.T) { - testScheme := runtime.NewScheme() - - err := appsv1.AddToScheme(testScheme) - if err != nil { - t.Fatal(err) - } - err = gatewayapiv1.AddToScheme(testScheme) - if err != nil { - t.Fatal(err) - } - err = kuadrantv1beta3.AddToScheme(testScheme) - if err != nil { - t.Fatal(err) - } - - spec := kuadrantv1beta3.AuthPolicySpec{ - TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: "gateway.networking.k8s.io", - Kind: "HTTPRoute", - Name: "test-route", - }, - } - routeList := &gatewayapiv1.HTTPRouteList{Items: make([]gatewayapiv1.HTTPRoute, 0)} - authPolicyList := &kuadrantv1beta3.AuthPolicyList{Items: []kuadrantv1beta3.AuthPolicy{ - { - ObjectMeta: metav1.ObjectMeta{ - Name: "policy-1", - Namespace: "app-ns", - }, - Spec: spec, - }, - }} - gateway := &gatewayapiv1.Gateway{ - ObjectMeta: metav1.ObjectMeta{Name: "test-gw", Namespace: "app-ns"}, - Status: gatewayapiv1.GatewayStatus{ - Conditions: []metav1.Condition{ - { - Type: "Programmed", - Status: "True", - }, - }, - }, - } - objs := []runtime.Object{routeList, authPolicyList, gateway} - cl := fake.NewClientBuilder().WithScheme(testScheme).WithRuntimeObjects(objs...).WithIndex(&gatewayapiv1.HTTPRoute{}, fieldindexers.HTTPRouteGatewayParentField, func(rawObj client.Object) []string { - return nil - }).Build() - em := NewHTTPRouteEventMapper(WithLogger(log.NewLogger()), WithClient(cl)) - - t.Run("not http route related event", func(subT *testing.T) { - requests := em.MapToPolicy(context.Background(), &gatewayapiv1.Gateway{}, kuadrantv1beta3.NewAuthPolicyType()) - assert.DeepEqual(subT, []reconcile.Request{}, requests) - }) - - t.Run("http route related event - no requests", func(subT *testing.T) { - requests := em.MapToPolicy(context.Background(), &gatewayapiv1.HTTPRoute{}, kuadrantv1beta3.NewAuthPolicyType()) - assert.DeepEqual(subT, []reconcile.Request{}, requests) - }) - - t.Run("http related event - requests", func(subT *testing.T) { - httpRoute := &gatewayapiv1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-route", - Namespace: "app-ns", - Annotations: map[string]string{"kuadrant.io/testpolicies": `[{"Namespace":"app-ns","Name":"policy-1"},{"Namespace":"app-ns","Name":"policy-2"}]`}, - }, - Spec: gatewayapiv1.HTTPRouteSpec{ - CommonRouteSpec: gatewayapiv1.CommonRouteSpec{ - ParentRefs: []gatewayapiv1.ParentReference{{Namespace: ptr.To(gatewayapiv1.Namespace("app-ns")), Name: "test-gw"}}, - }, - }, - - Status: gatewayapiv1.HTTPRouteStatus{ - RouteStatus: gatewayapiv1.RouteStatus{ - Parents: []gatewayapiv1.RouteParentStatus{ - { - ParentRef: gatewayapiv1.ParentReference{ - Name: "test-gw", - Namespace: ptr.To(gatewayapiv1.Namespace("app-ns")), - }, - Conditions: []metav1.Condition{ - { - Type: "Accepted", - Status: metav1.ConditionTrue, - }, - }, - }, - }, - }, - }, - } - - objs = []runtime.Object{routeList, authPolicyList, gateway, httpRoute} - cl = fake.NewClientBuilder().WithScheme(testScheme).WithRuntimeObjects(objs...).WithIndex(&gatewayapiv1.HTTPRoute{}, fieldindexers.HTTPRouteGatewayParentField, func(rawObj client.Object) []string { - route, assertionOk := rawObj.(*gatewayapiv1.HTTPRoute) - if !assertionOk { - return nil - } - - return utils.Map(kuadrantgatewayapi.GetRouteAcceptedGatewayParentKeys(route), func(key client.ObjectKey) string { - return key.String() - }) - }).Build() - em = NewHTTPRouteEventMapper(WithLogger(log.NewLogger()), WithClient(cl)) - requests := em.MapToPolicy(context.Background(), httpRoute, kuadrantv1beta3.NewAuthPolicyType()) - expected := []reconcile.Request{{NamespacedName: types.NamespacedName{Namespace: "app-ns", Name: "policy-1"}}} - assert.DeepEqual(subT, expected, requests) - }) -} diff --git a/tests/common/authpolicy/authpolicy_controller_test.go b/tests/common/authpolicy/authpolicy_controller_test.go index dfac1bf66..990abccde 100644 --- a/tests/common/authpolicy/authpolicy_controller_test.go +++ b/tests/common/authpolicy/authpolicy_controller_test.go @@ -94,13 +94,17 @@ var _ = Describe("AuthPolicy controller (Serial)", Serial, func() { Namespace: testNamespace, }, Spec: kuadrantv1beta3.AuthPolicySpec{ - TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: "HTTPRoute", - Name: TestHTTPRouteName, + TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "HTTPRoute", + Name: TestHTTPRouteName, + }, }, - Defaults: &kuadrantv1beta3.AuthPolicyCommonSpec{ - AuthScheme: tests.BuildBasicAuthScheme(), + Defaults: &kuadrantv1beta3.MergeableAuthPolicySpec{ + AuthPolicySpecProper: kuadrantv1beta3.AuthPolicySpecProper{ + AuthScheme: tests.BuildBasicAuthScheme(), + }, }, }, } @@ -182,13 +186,17 @@ var _ = Describe("AuthPolicy controller", func() { Namespace: testNamespace, }, Spec: kuadrantv1beta3.AuthPolicySpec{ - TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: "HTTPRoute", - Name: TestHTTPRouteName, + TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "HTTPRoute", + Name: TestHTTPRouteName, + }, }, - Defaults: &kuadrantv1beta3.AuthPolicyCommonSpec{ - AuthScheme: tests.BuildBasicAuthScheme(), + Defaults: &kuadrantv1beta3.MergeableAuthPolicySpec{ + AuthPolicySpecProper: kuadrantv1beta3.AuthPolicySpecProper{ + AuthScheme: tests.BuildBasicAuthScheme(), + }, }, }, } @@ -230,7 +238,7 @@ var _ = Describe("AuthPolicy controller", func() { policy.Spec.TargetRef.Group = gatewayapiv1.GroupName policy.Spec.TargetRef.Kind = "Gateway" policy.Spec.TargetRef.Name = gatewayapiv1.ObjectName(gwName) - policy.Spec.CommonSpec().AuthScheme.Authentication["apiKey"].ApiKey.Selector.MatchLabels["admin"] = "yes" + policy.Spec.Proper().AuthScheme.Authentication["apiKey"].ApiKey.Selector.MatchLabels["admin"] = "yes" }) err = k8sClient.Create(ctx, policy) @@ -407,101 +415,113 @@ var _ = Describe("AuthPolicy controller", func() { It("Maps to all fields of the AuthConfig", func(ctx SpecContext) { policy := policyFactory(func(policy *kuadrantv1beta3.AuthPolicy) { - policy.Spec.CommonSpec().NamedPatterns = map[string]authorinoapi.PatternExpressions{ - "internal-source": []authorinoapi.PatternExpression{ - { - Selector: "source.ip", - Operator: authorinoapi.PatternExpressionOperator("matches"), - Value: `192\.168\..*`, + policy.Spec.Proper().NamedPatterns = map[string]kuadrantv1beta3.MergeablePatternExpressions{ + "internal-source": { + PatternExpressions: []authorinoapi.PatternExpression{ + { + Selector: "source.ip", + Operator: authorinoapi.PatternExpressionOperator("matches"), + Value: `192\.168\..*`, + }, }, }, - "authz-and-rl-required": []authorinoapi.PatternExpression{ - { - Selector: "source.ip", - Operator: authorinoapi.PatternExpressionOperator("neq"), - Value: "192.168.0.10", + "authz-and-rl-required": { + PatternExpressions: []authorinoapi.PatternExpression{ + { + Selector: "source.ip", + Operator: authorinoapi.PatternExpressionOperator("neq"), + Value: "192.168.0.10", + }, }, }, } - policy.Spec.CommonSpec().Conditions = []authorinoapi.PatternExpressionOrRef{ + policy.Spec.Proper().Conditions = []kuadrantv1beta3.MergeablePatternExpressionOrRef{ { - PatternRef: authorinoapi.PatternRef{ - Name: "internal-source", + PatternExpressionOrRef: authorinoapi.PatternExpressionOrRef{ + PatternRef: authorinoapi.PatternRef{ + Name: "internal-source", + }, }, }, } - policy.Spec.CommonSpec().AuthScheme = &kuadrantv1beta3.AuthSchemeSpec{ - Authentication: map[string]authorinoapi.AuthenticationSpec{ + policy.Spec.Proper().AuthScheme = &kuadrantv1beta3.AuthSchemeSpec{ + Authentication: map[string]kuadrantv1beta3.MergeableAuthenticationSpec{ "jwt": { - CommonEvaluatorSpec: authorinoapi.CommonEvaluatorSpec{ - Conditions: []authorinoapi.PatternExpressionOrRef{ - { - PatternExpression: authorinoapi.PatternExpression{ - Selector: `filter_metadata.envoy\.filters\.http\.jwt_authn|verified_jwt`, - Operator: "neq", - Value: "", + AuthenticationSpec: authorinoapi.AuthenticationSpec{ + CommonEvaluatorSpec: authorinoapi.CommonEvaluatorSpec{ + Conditions: []authorinoapi.PatternExpressionOrRef{ + { + PatternExpression: authorinoapi.PatternExpression{ + Selector: `filter_metadata.envoy\.filters\.http\.jwt_authn|verified_jwt`, + Operator: "neq", + Value: "", + }, }, }, }, - }, - AuthenticationMethodSpec: authorinoapi.AuthenticationMethodSpec{ - Plain: &authorinoapi.PlainIdentitySpec{ - Selector: `filter_metadata.envoy\.filters\.http\.jwt_authn|verified_jwt`, + AuthenticationMethodSpec: authorinoapi.AuthenticationMethodSpec{ + Plain: &authorinoapi.PlainIdentitySpec{ + Selector: `filter_metadata.envoy\.filters\.http\.jwt_authn|verified_jwt`, + }, }, }, }, }, - Metadata: map[string]authorinoapi.MetadataSpec{ + Metadata: map[string]kuadrantv1beta3.MergeableMetadataSpec{ "user-groups": { - CommonEvaluatorSpec: authorinoapi.CommonEvaluatorSpec{ - Conditions: []authorinoapi.PatternExpressionOrRef{ - { - PatternExpression: authorinoapi.PatternExpression{ - Selector: "auth.identity.admin", - Operator: authorinoapi.PatternExpressionOperator("neq"), - Value: "true", + MetadataSpec: authorinoapi.MetadataSpec{ + CommonEvaluatorSpec: authorinoapi.CommonEvaluatorSpec{ + Conditions: []authorinoapi.PatternExpressionOrRef{ + { + PatternExpression: authorinoapi.PatternExpression{ + Selector: "auth.identity.admin", + Operator: authorinoapi.PatternExpressionOperator("neq"), + Value: "true", + }, }, }, }, - }, - MetadataMethodSpec: authorinoapi.MetadataMethodSpec{ - Http: &authorinoapi.HttpEndpointSpec{ - Url: "http://user-groups/username={auth.identity.username}", + MetadataMethodSpec: authorinoapi.MetadataMethodSpec{ + Http: &authorinoapi.HttpEndpointSpec{ + Url: "http://user-groups/username={auth.identity.username}", + }, }, }, }, }, - Authorization: map[string]authorinoapi.AuthorizationSpec{ + Authorization: map[string]kuadrantv1beta3.MergeableAuthorizationSpec{ "admin-or-privileged": { - CommonEvaluatorSpec: authorinoapi.CommonEvaluatorSpec{ - Conditions: []authorinoapi.PatternExpressionOrRef{ - { - PatternRef: authorinoapi.PatternRef{ - Name: "authz-and-rl-required", + AuthorizationSpec: authorinoapi.AuthorizationSpec{ + CommonEvaluatorSpec: authorinoapi.CommonEvaluatorSpec{ + Conditions: []authorinoapi.PatternExpressionOrRef{ + { + PatternRef: authorinoapi.PatternRef{ + Name: "authz-and-rl-required", + }, }, }, }, - }, - AuthorizationMethodSpec: authorinoapi.AuthorizationMethodSpec{ - PatternMatching: &authorinoapi.PatternMatchingAuthorizationSpec{ - Patterns: []authorinoapi.PatternExpressionOrRef{ - { - Any: []authorinoapi.UnstructuredPatternExpressionOrRef{ - { - PatternExpressionOrRef: authorinoapi.PatternExpressionOrRef{ - PatternExpression: authorinoapi.PatternExpression{ - Selector: "auth.identity.admin", - Operator: authorinoapi.PatternExpressionOperator("eq"), - Value: "true", + AuthorizationMethodSpec: authorinoapi.AuthorizationMethodSpec{ + PatternMatching: &authorinoapi.PatternMatchingAuthorizationSpec{ + Patterns: []authorinoapi.PatternExpressionOrRef{ + { + Any: []authorinoapi.UnstructuredPatternExpressionOrRef{ + { + PatternExpressionOrRef: authorinoapi.PatternExpressionOrRef{ + PatternExpression: authorinoapi.PatternExpression{ + Selector: "auth.identity.admin", + Operator: authorinoapi.PatternExpressionOperator("eq"), + Value: "true", + }, }, }, - }, - { - PatternExpressionOrRef: authorinoapi.PatternExpressionOrRef{ - PatternExpression: authorinoapi.PatternExpression{ - Selector: "auth.metadata.user-groups", - Operator: authorinoapi.PatternExpressionOperator("incl"), - Value: "privileged", + { + PatternExpressionOrRef: authorinoapi.PatternExpressionOrRef{ + PatternExpression: authorinoapi.PatternExpression{ + Selector: "auth.metadata.user-groups", + Operator: authorinoapi.PatternExpressionOperator("incl"), + Value: "privileged", + }, }, }, }, @@ -512,59 +532,67 @@ var _ = Describe("AuthPolicy controller", func() { }, }, }, - Response: &kuadrantv1beta3.ResponseSpec{ - Unauthenticated: &authorinoapi.DenyWithSpec{ - Message: &authorinoapi.ValueOrSelector{ - Value: k8sruntime.RawExtension{Raw: []byte(`"Missing verified JWT injected by the gateway"`)}, + Response: &kuadrantv1beta3.MergeableResponseSpec{ + Unauthenticated: &kuadrantv1beta3.MergeableDenyWithSpec{ + DenyWithSpec: authorinoapi.DenyWithSpec{ + Message: &authorinoapi.ValueOrSelector{ + Value: k8sruntime.RawExtension{Raw: []byte(`"Missing verified JWT injected by the gateway"`)}, + }, }, }, - Unauthorized: &authorinoapi.DenyWithSpec{ - Message: &authorinoapi.ValueOrSelector{ - Value: k8sruntime.RawExtension{Raw: []byte(`"User must be admin or member of privileged group"`)}, + Unauthorized: &kuadrantv1beta3.MergeableDenyWithSpec{ + DenyWithSpec: authorinoapi.DenyWithSpec{ + Message: &authorinoapi.ValueOrSelector{ + Value: k8sruntime.RawExtension{Raw: []byte(`"User must be admin or member of privileged group"`)}, + }, }, }, - Success: kuadrantv1beta3.WrappedSuccessResponseSpec{ - Headers: map[string]kuadrantv1beta3.HeaderSuccessResponseSpec{ + Success: kuadrantv1beta3.MergeableWrappedSuccessResponseSpec{ + Headers: map[string]kuadrantv1beta3.MergeableHeaderSuccessResponseSpec{ "x-username": { - SuccessResponseSpec: authorinoapi.SuccessResponseSpec{ - CommonEvaluatorSpec: authorinoapi.CommonEvaluatorSpec{ - Conditions: []authorinoapi.PatternExpressionOrRef{ - { - PatternExpression: authorinoapi.PatternExpression{ - Selector: "request.headers.x-propagate-username.@case:lower", - Operator: authorinoapi.PatternExpressionOperator("matches"), - Value: "1|yes|true", + HeaderSuccessResponseSpec: authorinoapi.HeaderSuccessResponseSpec{ + SuccessResponseSpec: authorinoapi.SuccessResponseSpec{ + CommonEvaluatorSpec: authorinoapi.CommonEvaluatorSpec{ + Conditions: []authorinoapi.PatternExpressionOrRef{ + { + PatternExpression: authorinoapi.PatternExpression{ + Selector: "request.headers.x-propagate-username.@case:lower", + Operator: authorinoapi.PatternExpressionOperator("matches"), + Value: "1|yes|true", + }, }, }, }, - }, - AuthResponseMethodSpec: authorinoapi.AuthResponseMethodSpec{ - Plain: &authorinoapi.PlainAuthResponseSpec{ - Selector: "auth.identity.username", + AuthResponseMethodSpec: authorinoapi.AuthResponseMethodSpec{ + Plain: &authorinoapi.PlainAuthResponseSpec{ + Selector: "auth.identity.username", + }, }, }, }, }, }, - DynamicMetadata: map[string]authorinoapi.SuccessResponseSpec{ + DynamicMetadata: map[string]kuadrantv1beta3.MergeableSuccessResponseSpec{ "x-auth-data": { - CommonEvaluatorSpec: authorinoapi.CommonEvaluatorSpec{ - Conditions: []authorinoapi.PatternExpressionOrRef{ - { - PatternRef: authorinoapi.PatternRef{ - Name: "authz-and-rl-required", + SuccessResponseSpec: authorinoapi.SuccessResponseSpec{ + CommonEvaluatorSpec: authorinoapi.CommonEvaluatorSpec{ + Conditions: []authorinoapi.PatternExpressionOrRef{ + { + PatternRef: authorinoapi.PatternRef{ + Name: "authz-and-rl-required", + }, }, }, }, - }, - AuthResponseMethodSpec: authorinoapi.AuthResponseMethodSpec{ - Json: &authorinoapi.JsonAuthResponseSpec{ - Properties: authorinoapi.NamedValuesOrSelectors{ - "username": { - Selector: "auth.identity.username", - }, - "groups": { - Selector: "auth.metadata.user-groups", + AuthResponseMethodSpec: authorinoapi.AuthResponseMethodSpec{ + Json: &authorinoapi.JsonAuthResponseSpec{ + Properties: authorinoapi.NamedValuesOrSelectors{ + "username": { + Selector: "auth.identity.username", + }, + "groups": { + Selector: "auth.metadata.user-groups", + }, }, }, }, @@ -573,31 +601,33 @@ var _ = Describe("AuthPolicy controller", func() { }, }, }, - Callbacks: map[string]authorinoapi.CallbackSpec{ + Callbacks: map[string]kuadrantv1beta3.MergeableCallbackSpec{ "unauthorized-attempt": { - CommonEvaluatorSpec: authorinoapi.CommonEvaluatorSpec{ - Conditions: []authorinoapi.PatternExpressionOrRef{ - { - PatternRef: authorinoapi.PatternRef{ - Name: "authz-and-rl-required", + CallbackSpec: authorinoapi.CallbackSpec{ + CommonEvaluatorSpec: authorinoapi.CommonEvaluatorSpec{ + Conditions: []authorinoapi.PatternExpressionOrRef{ + { + PatternRef: authorinoapi.PatternRef{ + Name: "authz-and-rl-required", + }, }, - }, - { - PatternExpression: authorinoapi.PatternExpression{ - Selector: "auth.authorization.admin-or-privileged", - Operator: authorinoapi.PatternExpressionOperator("neq"), - Value: "true", + { + PatternExpression: authorinoapi.PatternExpression{ + Selector: "auth.authorization.admin-or-privileged", + Operator: authorinoapi.PatternExpressionOperator("neq"), + Value: "true", + }, }, }, }, - }, - CallbackMethodSpec: authorinoapi.CallbackMethodSpec{ - Http: &authorinoapi.HttpEndpointSpec{ - Url: "http://events/unauthorized", - Method: ptr.To(authorinoapi.HttpMethod("POST")), - ContentType: authorinoapi.HttpContentType("application/json"), - Body: &authorinoapi.ValueOrSelector{ - Selector: `\{"identity":{auth.identity},"request-id":{request.id}\}`, + CallbackMethodSpec: authorinoapi.CallbackMethodSpec{ + Http: &authorinoapi.HttpEndpointSpec{ + Url: "http://events/unauthorized", + Method: ptr.To(authorinoapi.HttpMethod("POST")), + ContentType: authorinoapi.HttpContentType("application/json"), + Body: &authorinoapi.ValueOrSelector{ + Selector: `\{"identity":{auth.identity},"request-id":{request.id}\}`, + }, }, }, }, @@ -622,12 +652,12 @@ var _ = Describe("AuthPolicy controller", func() { return err == nil && authConfig.Status.Ready() }).WithContext(ctx).Should(BeTrue()) authConfigSpecAsJSON, _ := json.Marshal(authConfig.Spec) - Expect(string(authConfigSpecAsJSON)).To(Equal(fmt.Sprintf(`{"hosts":["%s"],"patterns":{"authz-and-rl-required":[{"selector":"source.ip","operator":"neq","value":"192.168.0.10"}],"internal-source":[{"selector":"source.ip","operator":"matches","value":"192\\.168\\..*"}]},"when":[{"patternRef":"internal-source"},{"any":[{"any":[{"all":[{"selector":"request.method","operator":"eq","value":"GET"},{"selector":"request.url_path","operator":"matches","value":"/toy.*"}]}]}]}],"authentication":{"jwt":{"when":[{"selector":"filter_metadata.envoy\\.filters\\.http\\.jwt_authn|verified_jwt","operator":"neq"}],"credentials":{},"plain":{"selector":"filter_metadata.envoy\\.filters\\.http\\.jwt_authn|verified_jwt"}}},"metadata":{"user-groups":{"when":[{"selector":"auth.identity.admin","operator":"neq","value":"true"}],"http":{"url":"http://user-groups/username={auth.identity.username}","method":"GET","contentType":"application/x-www-form-urlencoded","credentials":{}}}},"authorization":{"admin-or-privileged":{"when":[{"patternRef":"authz-and-rl-required"}],"patternMatching":{"patterns":[{"any":[{"selector":"auth.identity.admin","operator":"eq","value":"true"},{"selector":"auth.metadata.user-groups","operator":"incl","value":"privileged"}]}]}}},"response":{"unauthenticated":{"message":{"value":"Missing verified JWT injected by the gateway"}},"unauthorized":{"message":{"value":"User must be admin or member of privileged group"}},"success":{"headers":{"x-username":{"when":[{"selector":"request.headers.x-propagate-username.@case:lower","operator":"matches","value":"1|yes|true"}],"plain":{"value":null,"selector":"auth.identity.username"}}},"dynamicMetadata":{"x-auth-data":{"when":[{"patternRef":"authz-and-rl-required"}],"json":{"properties":{"groups":{"value":null,"selector":"auth.metadata.user-groups"},"username":{"value":null,"selector":"auth.identity.username"}}}}}}},"callbacks":{"unauthorized-attempt":{"when":[{"patternRef":"authz-and-rl-required"},{"selector":"auth.authorization.admin-or-privileged","operator":"neq","value":"true"}],"http":{"url":"http://events/unauthorized","method":"POST","body":{"value":null,"selector":"\\{\"identity\":{auth.identity},\"request-id\":{request.id}\\}"},"contentType":"application/json","credentials":{}}}}}`, routeHost))) + Expect(string(authConfigSpecAsJSON)).To(Equal(fmt.Sprintf(`{"hosts":["%s"],"patterns":{"authz-and-rl-required":{"allOf":[{"selector":"source.ip","operator":"neq","value":"192.168.0.10"}]},"internal-source":{"allOf":[{"selector":"source.ip","operator":"matches","value":"192\\.168\\..*"}]}},"when":[{"patternRef":"internal-source"},{"any":[{"any":[{"all":[{"selector":"request.method","operator":"eq","value":"GET"},{"selector":"request.url_path","operator":"matches","value":"/toy.*"}]}]}]}],"authentication":{"jwt":{"when":[{"selector":"filter_metadata.envoy\\.filters\\.http\\.jwt_authn|verified_jwt","operator":"neq"}],"credentials":{},"plain":{"selector":"filter_metadata.envoy\\.filters\\.http\\.jwt_authn|verified_jwt"}}},"metadata":{"user-groups":{"when":[{"selector":"auth.identity.admin","operator":"neq","value":"true"}],"http":{"url":"http://user-groups/username={auth.identity.username}","method":"GET","contentType":"application/x-www-form-urlencoded","credentials":{}}}},"authorization":{"admin-or-privileged":{"when":[{"patternRef":"authz-and-rl-required"}],"patternMatching":{"patterns":[{"any":[{"selector":"auth.identity.admin","operator":"eq","value":"true"},{"selector":"auth.metadata.user-groups","operator":"incl","value":"privileged"}]}]}}},"response":{"unauthenticated":{"message":{"value":"Missing verified JWT injected by the gateway"}},"unauthorized":{"message":{"value":"User must be admin or member of privileged group"}},"success":{"headers":{"x-username":{"when":[{"selector":"request.headers.x-propagate-username.@case:lower","operator":"matches","value":"1|yes|true"}],"plain":{"value":null,"selector":"auth.identity.username"}}},"filters":{"x-auth-data":{"when":[{"patternRef":"authz-and-rl-required"}],"json":{"properties":{"groups":{"value":null,"selector":"auth.metadata.user-groups"},"username":{"value":null,"selector":"auth.identity.username"}}}}}}},"callbacks":{"unauthorized-attempt":{"when":[{"patternRef":"authz-and-rl-required"},{"selector":"auth.authorization.admin-or-privileged","operator":"neq","value":"true"}],"http":{"url":"http://events/unauthorized","method":"POST","body":{"value":null,"selector":"\\{\"identity\":{auth.identity},\"request-id\":{request.id}\\}"},"contentType":"application/json","credentials":{}}}}}`, routeHost))) }, testTimeOut) It("Succeeds when AuthScheme is not defined", func(ctx SpecContext) { policy := policyFactory(func(policy *kuadrantv1beta3.AuthPolicy) { - policy.Spec.CommonSpec().AuthScheme = nil + policy.Spec.Proper().AuthScheme = nil }) err := k8sClient.Create(ctx, policy) @@ -863,7 +893,7 @@ var _ = Describe("AuthPolicy controller", func() { policy.Spec.TargetRef.Group = gatewayapiv1.GroupName policy.Spec.TargetRef.Kind = "Gateway" policy.Spec.TargetRef.Name = TestGatewayName - policy.Spec.Overrides = &kuadrantv1beta3.AuthPolicyCommonSpec{} + policy.Spec.Overrides = &kuadrantv1beta3.MergeableAuthPolicySpec{} policy.Spec.Defaults = nil policy.Spec.Overrides.AuthScheme = tests.BuildBasicAuthScheme() policy.Spec.Overrides.AuthScheme.Authentication["apiKey"].ApiKey.Selector.MatchLabels["admin"] = "yes" @@ -900,7 +930,7 @@ var _ = Describe("AuthPolicy controller", func() { policy.Spec.TargetRef.Group = gatewayapiv1.GroupName policy.Spec.TargetRef.Kind = "Gateway" policy.Spec.TargetRef.Name = TestGatewayName - policy.Spec.Overrides = &kuadrantv1beta3.AuthPolicyCommonSpec{} + policy.Spec.Overrides = &kuadrantv1beta3.MergeableAuthPolicySpec{} policy.Spec.Defaults = nil policy.Spec.Overrides.AuthScheme = tests.BuildBasicAuthScheme() policy.Spec.Overrides.AuthScheme.Authentication["apiKey"].ApiKey.Selector.MatchLabels["admin"] = "yes" @@ -930,7 +960,7 @@ var _ = Describe("AuthPolicy controller", func() { policy.Spec.TargetRef.Group = gatewayapiv1.GroupName policy.Spec.TargetRef.Kind = "Gateway" policy.Spec.TargetRef.Name = TestGatewayName - policy.Spec.Overrides = &kuadrantv1beta3.AuthPolicyCommonSpec{} + policy.Spec.Overrides = &kuadrantv1beta3.MergeableAuthPolicySpec{} policy.Spec.Defaults = nil policy.Spec.Overrides.AuthScheme = tests.BuildBasicAuthScheme() policy.Spec.Overrides.AuthScheme.Authentication["apiKey"].ApiKey.Selector.MatchLabels["admin"] = "yes" @@ -967,7 +997,7 @@ var _ = Describe("AuthPolicy controller", func() { policy.Spec.TargetRef.Group = gatewayapiv1.GroupName policy.Spec.TargetRef.Kind = "Gateway" policy.Spec.TargetRef.Name = TestGatewayName - policy.Spec.CommonSpec().AuthScheme.Authentication["apiKey"].ApiKey.Selector.MatchLabels["admin"] = "yes" + policy.Spec.Proper().AuthScheme.Authentication["apiKey"].ApiKey.Selector.MatchLabels["admin"] = "yes" }) err = k8sClient.Create(ctx, gatewayPolicy) @@ -984,7 +1014,7 @@ var _ = Describe("AuthPolicy controller", func() { if err != nil { return false } - gatewayPolicy.Spec.Overrides = &kuadrantv1beta3.AuthPolicyCommonSpec{} + gatewayPolicy.Spec.Overrides = &kuadrantv1beta3.MergeableAuthPolicySpec{} gatewayPolicy.Spec.Defaults = nil gatewayPolicy.Spec.Overrides.AuthScheme = tests.BuildBasicAuthScheme() gatewayPolicy.Spec.Overrides.AuthScheme.Authentication["apiKey"].ApiKey.Selector.MatchLabels["admin"] = "yes" @@ -1013,7 +1043,7 @@ var _ = Describe("AuthPolicy controller", func() { policy.Spec.TargetRef.Group = gatewayapiv1.GroupName policy.Spec.TargetRef.Kind = "Gateway" policy.Spec.TargetRef.Name = TestGatewayName - policy.Spec.Overrides = &kuadrantv1beta3.AuthPolicyCommonSpec{} + policy.Spec.Overrides = &kuadrantv1beta3.MergeableAuthPolicySpec{} policy.Spec.Defaults = nil policy.Spec.Overrides.AuthScheme = tests.BuildBasicAuthScheme() policy.Spec.Overrides.AuthScheme.Authentication["apiKey"].ApiKey.Selector.MatchLabels["admin"] = "yes" @@ -1034,8 +1064,8 @@ var _ = Describe("AuthPolicy controller", func() { return false } gatewayPolicy.Spec.Overrides = nil - gatewayPolicy.Spec.CommonSpec().AuthScheme = tests.BuildBasicAuthScheme() - gatewayPolicy.Spec.CommonSpec().AuthScheme.Authentication["apiKey"].ApiKey.Selector.MatchLabels["admin"] = "yes" + gatewayPolicy.Spec.Proper().AuthScheme = tests.BuildBasicAuthScheme() + gatewayPolicy.Spec.Proper().AuthScheme.Authentication["apiKey"].ApiKey.Selector.MatchLabels["admin"] = "yes" err = k8sClient.Update(ctx, gatewayPolicy) logf.Log.V(1).Info("Updating AuthPolicy", "key", client.ObjectKeyFromObject(gatewayPolicy).String(), "error", err) return err == nil @@ -1049,7 +1079,7 @@ var _ = Describe("AuthPolicy controller", func() { It("Blocks creation of AuthPolicies with overrides targeting HTTPRoutes", func(ctx SpecContext) { routePolicy := policyFactory(func(policy *kuadrantv1beta3.AuthPolicy) { - policy.Spec.Overrides = &kuadrantv1beta3.AuthPolicyCommonSpec{} + policy.Spec.Overrides = &kuadrantv1beta3.MergeableAuthPolicySpec{} policy.Spec.Defaults = nil policy.Spec.Overrides.AuthScheme = tests.BuildBasicAuthScheme() }) @@ -1083,10 +1113,12 @@ var _ = Describe("AuthPolicy CEL Validations", func() { Namespace: testNamespace, }, Spec: kuadrantv1beta3.AuthPolicySpec{ - TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: "HTTPRoute", - Name: "my-target", + TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "HTTPRoute", + Name: "my-target", + }, }, }, } @@ -1142,8 +1174,10 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("Valid when only explicit defaults are used", func(ctx SpecContext) { policy := policyFactory(func(policy *kuadrantv1beta3.AuthPolicy) { - policy.Spec.Defaults = &kuadrantv1beta3.AuthPolicyCommonSpec{ - AuthScheme: tests.BuildBasicAuthScheme(), + policy.Spec.Defaults = &kuadrantv1beta3.MergeableAuthPolicySpec{ + AuthPolicySpecProper: kuadrantv1beta3.AuthPolicySpecProper{ + AuthScheme: tests.BuildBasicAuthScheme(), + }, } }) Expect(k8sClient.Create(ctx, policy)).To(Succeed()) @@ -1151,7 +1185,7 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("Invalid when both implicit and explicit defaults are used - authScheme", func(ctx SpecContext) { policy := policyFactory(func(policy *kuadrantv1beta3.AuthPolicy) { - policy.Spec.Defaults = &kuadrantv1beta3.AuthPolicyCommonSpec{} + policy.Spec.Defaults = &kuadrantv1beta3.MergeableAuthPolicySpec{} policy.Spec.AuthScheme = tests.BuildBasicAuthScheme() }) err := k8sClient.Create(ctx, policy) @@ -1161,13 +1195,15 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("Invalid when both implicit and explicit defaults are used - namedPatterns", func(ctx SpecContext) { policy := policyFactory(func(policy *kuadrantv1beta3.AuthPolicy) { - policy.Spec.Defaults = &kuadrantv1beta3.AuthPolicyCommonSpec{} - policy.Spec.NamedPatterns = map[string]authorinoapi.PatternExpressions{ - "internal-source": []authorinoapi.PatternExpression{ - { - Selector: "source.ip", - Operator: authorinoapi.PatternExpressionOperator("matches"), - Value: `192\.168\..*`, + policy.Spec.Defaults = &kuadrantv1beta3.MergeableAuthPolicySpec{} + policy.Spec.NamedPatterns = map[string]kuadrantv1beta3.MergeablePatternExpressions{ + "internal-source": { + PatternExpressions: []authorinoapi.PatternExpression{ + { + Selector: "source.ip", + Operator: authorinoapi.PatternExpressionOperator("matches"), + Value: `192\.168\..*`, + }, }, }, } @@ -1179,11 +1215,13 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("Invalid when both implicit and explicit defaults are used - conditions", func(ctx SpecContext) { policy := policyFactory(func(policy *kuadrantv1beta3.AuthPolicy) { - policy.Spec.Defaults = &kuadrantv1beta3.AuthPolicyCommonSpec{} - policy.Spec.Conditions = []authorinoapi.PatternExpressionOrRef{ + policy.Spec.Defaults = &kuadrantv1beta3.MergeableAuthPolicySpec{} + policy.Spec.Conditions = []kuadrantv1beta3.MergeablePatternExpressionOrRef{ { - PatternRef: authorinoapi.PatternRef{ - Name: "internal-source", + PatternExpressionOrRef: authorinoapi.PatternExpressionOrRef{ + PatternRef: authorinoapi.PatternRef{ + Name: "internal-source", + }, }, }, } diff --git a/tests/common/targetstatus/target_status_controller_test.go b/tests/common/targetstatus/target_status_controller_test.go index 6635a017b..36047107d 100644 --- a/tests/common/targetstatus/target_status_controller_test.go +++ b/tests/common/targetstatus/target_status_controller_test.go @@ -132,17 +132,23 @@ var _ = Describe("Target status reconciler", func() { Namespace: testNamespace, }, Spec: kuadrantv1beta3.AuthPolicySpec{ - TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: "HTTPRoute", - Name: TestHTTPRouteName, + TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "HTTPRoute", + Name: TestHTTPRouteName, + }, }, - Defaults: &kuadrantv1beta3.AuthPolicyCommonSpec{ - AuthScheme: &kuadrantv1beta3.AuthSchemeSpec{ - Authentication: map[string]authorinoapi.AuthenticationSpec{ - "anonymous": { - AuthenticationMethodSpec: authorinoapi.AuthenticationMethodSpec{ - AnonymousAccess: &authorinoapi.AnonymousAccessSpec{}, + Defaults: &kuadrantv1beta3.MergeableAuthPolicySpec{ + AuthPolicySpecProper: kuadrantv1beta3.AuthPolicySpecProper{ + AuthScheme: &kuadrantv1beta3.AuthSchemeSpec{ + Authentication: map[string]kuadrantv1beta3.MergeableAuthenticationSpec{ + "anonymous": { + AuthenticationSpec: authorinoapi.AuthenticationSpec{ + AuthenticationMethodSpec: authorinoapi.AuthenticationMethodSpec{ + AnonymousAccess: &authorinoapi.AnonymousAccessSpec{}, + }, + }, }, }, }, @@ -163,7 +169,7 @@ var _ = Describe("Target status reconciler", func() { if !tests.IsAuthPolicyAccepted(ctx, testClient(), policy)() { return false } - return targetsAffected(ctx, client.ObjectKeyFromObject(policy), policyAffectedCondition, policy.Spec.TargetRef, routeNames...) + return targetsAffected(ctx, client.ObjectKeyFromObject(policy), policyAffectedCondition, policy.GetTargetRef(), routeNames...) } } @@ -214,10 +220,12 @@ var _ = Describe("Target status reconciler", func() { It("adds PolicyAffected status condition to the targeted gateway and routes", func(ctx SpecContext) { policy := policyFactory(func(policy *kuadrantv1beta3.AuthPolicy) { policy.Name = "gateway-auth" - policy.Spec.TargetRef = gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: "Gateway", - Name: TestGatewayName, + policy.Spec.TargetRef = gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "Gateway", + Name: TestGatewayName, + }, } }) Expect(k8sClient.Create(ctx, policy)).To(Succeed()) @@ -227,10 +235,12 @@ var _ = Describe("Target status reconciler", func() { It("removes PolicyAffected status condition from the targeted gateway and routes when the policy is deleted", func(ctx SpecContext) { policy := policyFactory(func(policy *kuadrantv1beta3.AuthPolicy) { policy.Name = "gateway-auth" - policy.Spec.TargetRef = gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: "Gateway", - Name: TestGatewayName, + policy.Spec.TargetRef = gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "Gateway", + Name: TestGatewayName, + }, } }) Expect(k8sClient.Create(ctx, policy)).To(Succeed()) @@ -266,10 +276,12 @@ var _ = Describe("Target status reconciler", func() { gatewayPolicy := policyFactory(func(policy *kuadrantv1beta3.AuthPolicy) { policy.Name = "gateway-auth" - policy.Spec.TargetRef = gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: "Gateway", - Name: TestGatewayName, + policy.Spec.TargetRef = gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "Gateway", + Name: TestGatewayName, + }, } }) Expect(k8sClient.Create(ctx, gatewayPolicy)).To(Succeed()) diff --git a/tests/commons.go b/tests/commons.go index d9fd8f26f..aa6495ee8 100644 --- a/tests/commons.go +++ b/tests/commons.go @@ -655,20 +655,22 @@ func KuadrantIsReady(ctx context.Context, cl client.Client, key client.ObjectKey func BuildBasicAuthScheme() *kuadrantv1beta3.AuthSchemeSpec { return &kuadrantv1beta3.AuthSchemeSpec{ - Authentication: map[string]authorinoapi.AuthenticationSpec{ + Authentication: map[string]kuadrantv1beta3.MergeableAuthenticationSpec{ "apiKey": { - AuthenticationMethodSpec: authorinoapi.AuthenticationMethodSpec{ - ApiKey: &authorinoapi.ApiKeyAuthenticationSpec{ - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "app": "toystore", + AuthenticationSpec: authorinoapi.AuthenticationSpec{ + AuthenticationMethodSpec: authorinoapi.AuthenticationMethodSpec{ + ApiKey: &authorinoapi.ApiKeyAuthenticationSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "toystore", + }, }, }, }, - }, - Credentials: authorinoapi.Credentials{ - AuthorizationHeader: &authorinoapi.Prefixed{ - Prefix: "APIKEY", + Credentials: authorinoapi.Credentials{ + AuthorizationHeader: &authorinoapi.Prefixed{ + Prefix: "APIKEY", + }, }, }, }, diff --git a/tests/envoygateway/authpolicy_envoysecuritypolicy_controller_test.go b/tests/envoygateway/authpolicy_envoysecuritypolicy_controller_test.go deleted file mode 100644 index c08c1b2c2..000000000 --- a/tests/envoygateway/authpolicy_envoysecuritypolicy_controller_test.go +++ /dev/null @@ -1,276 +0,0 @@ -//go:build integration - -package envoygateway_test - -import ( - "fmt" - "strings" - "time" - - egv1alpha1 "github.com/envoyproxy/gateway/api/v1alpha1" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/onsi/gomega/gstruct" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/rand" - "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" - - kuadrantv1beta3 "github.com/kuadrant/kuadrant-operator/api/v1beta3" - "github.com/kuadrant/kuadrant-operator/controllers" - "github.com/kuadrant/kuadrant-operator/pkg/library/kuadrant" - "github.com/kuadrant/kuadrant-operator/tests" -) - -var _ = Describe("Auth Envoy SecurityPolicy controller", func() { - const ( - testTimeOut = SpecTimeout(2 * time.Minute) - afterEachTimeOut = NodeTimeout(3 * time.Minute) - ) - var ( - testNamespace string - gwHost = fmt.Sprintf("*.toystore-%s.com", rand.String(4)) - gateway *gatewayapiv1.Gateway - ) - - BeforeEach(func(ctx SpecContext) { - testNamespace = tests.CreateNamespace(ctx, testClient()) - gateway = tests.NewGatewayBuilder(TestGatewayName, tests.GatewayClassName, testNamespace). - WithHTTPListener("test-listener", gwHost). - Gateway - err := k8sClient.Create(ctx, gateway) - Expect(err).ToNot(HaveOccurred()) - - Eventually(tests.GatewayIsReady(ctx, testClient(), gateway)).WithContext(ctx).Should(BeTrue()) - }) - - AfterEach(func(ctx SpecContext) { - tests.DeleteNamespace(ctx, testClient(), testNamespace) - }, afterEachTimeOut) - - policyFactory := func(mutateFns ...func(policy *kuadrantv1beta3.AuthPolicy)) *kuadrantv1beta3.AuthPolicy { - policy := &kuadrantv1beta3.AuthPolicy{ - TypeMeta: metav1.TypeMeta{ - Kind: "AuthPolicy", - APIVersion: kuadrantv1beta3.GroupVersion.String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "toystore", - Namespace: testNamespace, - }, - Spec: kuadrantv1beta3.AuthPolicySpec{ - TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: "HTTPRoute", - Name: TestHTTPRouteName, - }, - Defaults: &kuadrantv1beta3.AuthPolicyCommonSpec{ - AuthScheme: tests.BuildBasicAuthScheme(), - }, - }, - } - for _, mutateFn := range mutateFns { - mutateFn(policy) - } - return policy - } - - randomHostFromGWHost := func() string { - return strings.Replace(gwHost, "*", rand.String(4), 1) - } - - Context("Auth Policy attached to the gateway", func() { - - var ( - gwPolicy *kuadrantv1beta3.AuthPolicy - ) - - BeforeEach(func(ctx SpecContext) { - gwRoute := tests.BuildBasicHttpRoute(TestHTTPRouteName, TestGatewayName, testNamespace, []string{randomHostFromGWHost()}) - err := k8sClient.Create(ctx, gwRoute) - Expect(err).ToNot(HaveOccurred()) - Eventually(tests.RouteIsAccepted(ctx, testClient(), client.ObjectKeyFromObject(gwRoute))).WithContext(ctx).Should(BeTrue()) - - gwPolicy = policyFactory(func(policy *kuadrantv1beta3.AuthPolicy) { - policy.Name = "gw-auth" - policy.Spec.TargetRef.Group = gatewayapiv1.GroupName - policy.Spec.TargetRef.Kind = "Gateway" - policy.Spec.TargetRef.Name = TestGatewayName - policy.Spec.CommonSpec().AuthScheme.Authentication["apiKey"].ApiKey.Selector.MatchLabels["admin"] = "yes" - }) - - err = k8sClient.Create(ctx, gwPolicy) - logf.Log.V(1).Info("Creating AuthPolicy", "key", client.ObjectKeyFromObject(gwPolicy).String(), "error", err) - Expect(err).ToNot(HaveOccurred()) - - // check policy status - Eventually(tests.IsAuthPolicyAcceptedAndEnforced(ctx, testClient(), gwPolicy)).WithContext(ctx).Should(BeTrue()) - }) - - It("Creates security policy", func(ctx SpecContext) { - spKey := types.NamespacedName{Name: controllers.EnvoySecurityPolicyName(gwPolicy.GetName()), Namespace: testNamespace} - sp := &egv1alpha1.SecurityPolicy{} - Eventually(func() bool { - err := k8sClient.Get(ctx, spKey, sp) - logf.Log.V(1).Info("Fetching envoy SecurityPolicy", "key", spKey.String(), "error", err) - return err == nil - }).WithContext(ctx).Should(BeTrue()) - - //has correct configuration - Expect(*sp).To( - MatchFields(IgnoreExtras, Fields{ - "Spec": MatchFields(IgnoreExtras, Fields{ - "PolicyTargetReferences": MatchFields(IgnoreExtras, Fields{ - "TargetRefs": ConsistOf(MatchFields(IgnoreExtras, Fields{ - "LocalPolicyTargetReference": MatchFields(IgnoreExtras, Fields{ - "Group": Equal(gatewayapiv1.Group(gatewayapiv1.GroupName)), - "Kind": Equal(gatewayapiv1.Kind("Gateway")), - "Name": Equal(gatewayapiv1.ObjectName(TestGatewayName)), - }), - })), - }), - "ExtAuth": PointTo(MatchFields(IgnoreExtras, Fields{ - "GRPC": PointTo(MatchFields(IgnoreExtras, Fields{ - "BackendRefs": ConsistOf(MatchFields(IgnoreExtras, Fields{ - "BackendObjectReference": MatchFields(IgnoreExtras, Fields{ - "Group": PointTo(Equal(gatewayapiv1.Group(""))), - "Kind": PointTo(Equal(gatewayapiv1.Kind("Service"))), - "Name": Equal(gatewayapiv1.ObjectName(kuadrant.AuthorinoServiceName)), - "Namespace": PointTo(Equal(gatewayapiv1.Namespace(kuadrantInstallationNS))), - "Port": PointTo(Equal(gatewayapiv1.PortNumber(50051))), - }), - })), - })), - })), - }), - })) - }, testTimeOut) - - It("Deletes security policy when auth policy is deleted", func(ctx SpecContext) { - err := k8sClient.Delete(ctx, gwPolicy) - logf.Log.V(1).Info("Deleting AuthPolicy", "key", client.ObjectKeyFromObject(gwPolicy).String(), "error", err) - Expect(err).ToNot(HaveOccurred()) - - spKey := types.NamespacedName{Name: controllers.EnvoySecurityPolicyName(TestGatewayName), Namespace: testNamespace} - sp := &egv1alpha1.SecurityPolicy{} - Eventually(func() bool { - err := k8sClient.Get(ctx, spKey, sp) - logf.Log.V(1).Info("Fetching envoy SecurityPolicy", "key", spKey.String(), "error", err) - return apierrors.IsNotFound(err) - }).WithContext(ctx).Should(BeTrue()) - }, testTimeOut) - - It("Deletes security policy if gateway is deleted", func(ctx SpecContext) { - err := k8sClient.Delete(ctx, gateway) - logf.Log.V(1).Info("Deleting Gateway", "key", client.ObjectKeyFromObject(gateway).String(), "error", err) - Expect(err).ToNot(HaveOccurred()) - - spKey := types.NamespacedName{Name: controllers.EnvoySecurityPolicyName(TestGatewayName), Namespace: testNamespace} - sp := &egv1alpha1.SecurityPolicy{} - Eventually(func() bool { - err := k8sClient.Get(ctx, spKey, sp) - logf.Log.V(1).Info("Fetching envoy SecurityPolicy", "key", spKey.String(), "error", err) - return apierrors.IsNotFound(err) - }).WithContext(ctx).Should(BeTrue()) - }, testTimeOut) - }) - - Context("Auth Policy attached to the route", func() { - - var ( - routePolicy *kuadrantv1beta3.AuthPolicy - gwRoute *gatewayapiv1.HTTPRoute - ) - - BeforeEach(func(ctx SpecContext) { - gwRoute = tests.BuildBasicHttpRoute(TestHTTPRouteName, TestGatewayName, testNamespace, []string{randomHostFromGWHost()}) - err := k8sClient.Create(ctx, gwRoute) - Expect(err).ToNot(HaveOccurred()) - Eventually(tests.RouteIsAccepted(ctx, testClient(), client.ObjectKeyFromObject(gwRoute))).WithContext(ctx).Should(BeTrue()) - - routePolicy = policyFactory(func(policy *kuadrantv1beta3.AuthPolicy) { - policy.Spec.TargetRef.Group = gatewayapiv1.GroupName - policy.Spec.TargetRef.Kind = "HTTPRoute" - policy.Spec.TargetRef.Name = TestHTTPRouteName - }) - - err = k8sClient.Create(ctx, routePolicy) - logf.Log.V(1).Info("Creating AuthPolicy", "key", client.ObjectKeyFromObject(routePolicy).String(), "error", err) - Expect(err).ToNot(HaveOccurred()) - - // check policy status - Eventually(tests.IsAuthPolicyAcceptedAndEnforced(ctx, testClient(), routePolicy)).WithContext(ctx).Should(BeTrue()) - }) - - It("Creates security policy", func(ctx SpecContext) { - spKey := types.NamespacedName{Name: controllers.EnvoySecurityPolicyName(routePolicy.GetName()), Namespace: testNamespace} - sp := &egv1alpha1.SecurityPolicy{} - Eventually(func() bool { - err := k8sClient.Get(ctx, spKey, sp) - logf.Log.V(1).Info("Fetching envoy SecurityPolicy", "key", spKey.String(), "error", err) - return err == nil - }).WithContext(ctx).Should(BeTrue()) - - //has correct configuration - Expect(*sp).To( - MatchFields(IgnoreExtras, Fields{ - "Spec": MatchFields(IgnoreExtras, Fields{ - "PolicyTargetReferences": MatchFields(IgnoreExtras, Fields{ - "TargetRefs": ConsistOf(MatchFields(IgnoreExtras, Fields{ - "LocalPolicyTargetReference": MatchFields(IgnoreExtras, Fields{ - "Group": Equal(gatewayapiv1.Group(gatewayapiv1.GroupName)), - "Kind": Equal(gatewayapiv1.Kind("HTTPRoute")), - "Name": Equal(gatewayapiv1.ObjectName(TestHTTPRouteName)), - }), - })), - }), - "ExtAuth": PointTo(MatchFields(IgnoreExtras, Fields{ - "GRPC": PointTo(MatchFields(IgnoreExtras, Fields{ - "BackendRefs": ConsistOf(MatchFields(IgnoreExtras, Fields{ - "BackendObjectReference": MatchFields(IgnoreExtras, Fields{ - "Group": PointTo(Equal(gatewayapiv1.Group(""))), - "Kind": PointTo(Equal(gatewayapiv1.Kind("Service"))), - "Name": Equal(gatewayapiv1.ObjectName(kuadrant.AuthorinoServiceName)), - "Namespace": PointTo(Equal(gatewayapiv1.Namespace(kuadrantInstallationNS))), - "Port": PointTo(Equal(gatewayapiv1.PortNumber(50051))), - }), - })), - })), - })), - }), - })) - }, testTimeOut) - - It("Security policy deleted when auth policy is deleted", func(ctx SpecContext) { - err := k8sClient.Delete(ctx, routePolicy) - logf.Log.V(1).Info("Deleting AuthPolicy", "key", client.ObjectKeyFromObject(routePolicy).String(), "error", err) - Expect(err).ToNot(HaveOccurred()) - - spKey := types.NamespacedName{Name: controllers.EnvoySecurityPolicyName(TestHTTPRouteName), Namespace: testNamespace} - sp := &egv1alpha1.SecurityPolicy{} - Eventually(func() bool { - err := k8sClient.Get(ctx, spKey, sp) - logf.Log.V(1).Info("Fetching envoy SecurityPolicy", "key", spKey.String(), "error", err) - return apierrors.IsNotFound(err) - }).WithContext(ctx).Should(BeTrue()) - }, testTimeOut) - - It("Deletes security policy if route is deleted", func(ctx SpecContext) { - err := k8sClient.Delete(ctx, gwRoute) - logf.Log.V(1).Info("Deleting AuthPolicy", "key", client.ObjectKeyFromObject(routePolicy).String(), "error", err) - Expect(err).ToNot(HaveOccurred()) - - spKey := types.NamespacedName{Name: controllers.EnvoySecurityPolicyName(TestHTTPRouteName), Namespace: testNamespace} - sp := &egv1alpha1.SecurityPolicy{} - Eventually(func() bool { - err := k8sClient.Get(ctx, spKey, sp) - logf.Log.V(1).Info("Fetching envoy SecurityPolicy", "key", spKey.String(), "error", err) - return apierrors.IsNotFound(err) - }).WithContext(ctx).Should(BeTrue()) - }, testTimeOut) - }) -}) diff --git a/tests/envoygateway/envoysecuritypolicy_referencegrant_controller_test.go b/tests/envoygateway/envoysecuritypolicy_referencegrant_controller_test.go deleted file mode 100644 index 20cb50737..000000000 --- a/tests/envoygateway/envoysecuritypolicy_referencegrant_controller_test.go +++ /dev/null @@ -1,271 +0,0 @@ -//go:build integration - -package envoygateway_test - -import ( - "time" - - egv1alpha1 "github.com/envoyproxy/gateway/api/v1alpha1" - "github.com/kuadrant/kuadrant-operator/controllers" - "github.com/kuadrant/kuadrant-operator/pkg/library/kuadrant" - "github.com/kuadrant/kuadrant-operator/tests" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/onsi/gomega/gstruct" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "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" - gatewayapiv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - - kuadrantv1beta3 "github.com/kuadrant/kuadrant-operator/api/v1beta3" -) - -var _ = Describe("Envoy SecurityPolicy ReferenceGrant controller", func() { - const ( - testTimeOut = SpecTimeout(2 * time.Minute) - afterEachTimeOut = NodeTimeout(3 * time.Minute) - ) - var ( - routePolicyOne *kuadrantv1beta3.AuthPolicy - gateway *gatewayapiv1.Gateway - route *gatewayapiv1.HTTPRoute - ) - - initGatewayRoutePolicy := func(ctx SpecContext, testNamespace string, policy *kuadrantv1beta3.AuthPolicy) { - gateway = tests.BuildBasicGateway(TestGatewayName, testNamespace) - err := k8sClient.Create(ctx, gateway) - Expect(err).ToNot(HaveOccurred()) - Eventually(tests.GatewayIsReady(ctx, testClient(), gateway)).WithContext(ctx).Should(BeTrue()) - - route = tests.BuildBasicHttpRoute(TestHTTPRouteName, TestGatewayName, testNamespace, []string{"*.example.com"}) - err = k8sClient.Create(ctx, route) - Expect(err).ToNot(HaveOccurred()) - Eventually(tests.RouteIsAccepted(ctx, testClient(), client.ObjectKeyFromObject(route))).WithContext(ctx).Should(BeTrue()) - - err = k8sClient.Create(ctx, policy) - logf.Log.V(1).Info("Creating AuthPolicy", "key", client.ObjectKeyFromObject(policy).String(), "error", err) - Expect(err).ToNot(HaveOccurred()) - } - - policyFactory := func(testNamespace string, mutateFns ...func(policy *kuadrantv1beta3.AuthPolicy)) *kuadrantv1beta3.AuthPolicy { - policy := &kuadrantv1beta3.AuthPolicy{ - TypeMeta: metav1.TypeMeta{ - Kind: "AuthPolicy", - APIVersion: kuadrantv1beta3.GroupVersion.String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "toystore", - Namespace: testNamespace, - }, - Spec: kuadrantv1beta3.AuthPolicySpec{ - TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: "HTTPRoute", - Name: TestHTTPRouteName, - }, - Defaults: &kuadrantv1beta3.AuthPolicyCommonSpec{ - AuthScheme: tests.BuildBasicAuthScheme(), - }, - }, - } - for _, mutateFn := range mutateFns { - mutateFn(policy) - } - return policy - } - - Context("Single auth policy namespace", func() { - - var ( - testNamespaceOne string - ) - - BeforeEach(func(ctx SpecContext) { - testNamespaceOne = tests.CreateNamespace(ctx, testClient()) - routePolicyOne = policyFactory(testNamespaceOne) - initGatewayRoutePolicy(ctx, testNamespaceOne, routePolicyOne) - }) - - AfterEach(func(ctx SpecContext) { - tests.DeleteNamespace(ctx, testClient(), testNamespaceOne) - }, afterEachTimeOut) - - It("Creates reference grant", func(ctx SpecContext) { - rgKey := types.NamespacedName{Name: controllers.KuadrantReferenceGrantName, Namespace: kuadrantInstallationNS} - - Eventually(func() *gatewayapiv1beta1.ReferenceGrant { - rg := &gatewayapiv1beta1.ReferenceGrant{} - err := k8sClient.Get(ctx, rgKey, rg) - logf.Log.V(1).Info("Fetching ReferenceGrant", "key", rgKey.String(), "error", err) - if err != nil { - return nil - } - return rg - }).WithContext(ctx).Should(PointTo(MatchFields(IgnoreExtras, Fields{ - "ObjectMeta": MatchFields(IgnoreExtras, Fields{ - "Name": Equal(controllers.KuadrantReferenceGrantName), - "Namespace": Equal(kuadrantInstallationNS), - }), - "Spec": MatchFields(IgnoreExtras, Fields{ - "To": ConsistOf(MatchFields(IgnoreExtras, Fields{ - "Group": Equal(gatewayapiv1.Group("")), - "Kind": Equal(gatewayapiv1.Kind("Service")), - "Name": PointTo(Equal(gatewayapiv1.ObjectName(kuadrant.AuthorinoServiceName))), - })), - "From": ContainElement(MatchFields(IgnoreExtras, Fields{ - "Group": Equal(gatewayapiv1.Group(egv1alpha1.GroupName)), - "Kind": Equal(gatewayapiv1.Kind(egv1alpha1.KindSecurityPolicy)), - "Namespace": Equal(gatewayapiv1.Namespace(testNamespaceOne)), - })), - }), - }))) - }, testTimeOut) - - It("Deleting auth policy removes reference grant", func(ctx SpecContext) { - err := k8sClient.Delete(ctx, routePolicyOne) - logf.Log.V(1).Info("Deleting AuthPolicy", "key", client.ObjectKeyFromObject(routePolicyOne).String(), "error", err) - Expect(err).ToNot(HaveOccurred()) - - rgKey := types.NamespacedName{Name: controllers.KuadrantReferenceGrantName, Namespace: kuadrantInstallationNS} - rg := &gatewayapiv1beta1.ReferenceGrant{} - Eventually(func() bool { - err := k8sClient.Get(ctx, rgKey, rg) - logf.Log.V(1).Info("Fetching ReferenceGrant", "key", rgKey.String(), "error", err) - return apierrors.IsNotFound(err) - }).WithContext(ctx).Should(BeTrue()) - }, testTimeOut) - }) - - Context("Single auth policy in kuadrant installation namespace", func() { - - BeforeEach(func(ctx SpecContext) { - routePolicyOne = policyFactory(kuadrantInstallationNS) - initGatewayRoutePolicy(ctx, kuadrantInstallationNS, routePolicyOne) - }) - - AfterEach(func(ctx SpecContext) { - err := k8sClient.Delete(ctx, routePolicyOne) - logf.Log.V(1).Info("Deleting AuthPolicy", "key", client.ObjectKeyFromObject(routePolicyOne).String(), "error", err) - Expect(err).ToNot(HaveOccurred()) - - err = k8sClient.Delete(ctx, route) - logf.Log.V(1).Info("Deleting HTTPRoute", "key", client.ObjectKeyFromObject(route).String(), "error", err) - Expect(err).ToNot(HaveOccurred()) - - err = k8sClient.Delete(ctx, gateway) - logf.Log.V(1).Info("Deleting Gateway", "key", client.ObjectKeyFromObject(route).String(), "error", err) - Expect(err).ToNot(HaveOccurred()) - - }, afterEachTimeOut) - - It("Does not create reference grant", func(ctx SpecContext) { - rgKey := types.NamespacedName{Name: controllers.KuadrantReferenceGrantName, Namespace: kuadrantInstallationNS} - rg := &gatewayapiv1beta1.ReferenceGrant{} - Eventually(func() bool { - err := k8sClient.Get(ctx, rgKey, rg) - logf.Log.V(1).Info("Fetching ReferenceGrant", "key", rgKey.String(), "error", err) - return apierrors.IsNotFound(err) - }).WithContext(ctx).Should(BeTrue()) - }) - - }) - - Context("Multiple auth policy namespaces", func() { - - var ( - testNamespaceOne string - testNamespaceTwo string - routePolicyTwo *kuadrantv1beta3.AuthPolicy - ) - - BeforeEach(func(ctx SpecContext) { - testNamespaceOne = tests.CreateNamespace(ctx, testClient()) - routePolicyOne = policyFactory(testNamespaceOne) - initGatewayRoutePolicy(ctx, testNamespaceOne, routePolicyOne) - testNamespaceTwo = tests.CreateNamespace(ctx, testClient()) - routePolicyTwo = policyFactory(testNamespaceTwo) - initGatewayRoutePolicy(ctx, testNamespaceTwo, routePolicyTwo) - }) - - AfterEach(func(ctx SpecContext) { - tests.DeleteNamespace(ctx, testClient(), testNamespaceOne) - tests.DeleteNamespace(ctx, testClient(), testNamespaceTwo) - }, afterEachTimeOut) - - It("Creates reference grant", func(ctx SpecContext) { - rgKey := types.NamespacedName{Name: controllers.KuadrantReferenceGrantName, Namespace: kuadrantInstallationNS} - - Eventually(func() *gatewayapiv1beta1.ReferenceGrant { - rg := &gatewayapiv1beta1.ReferenceGrant{} - err := k8sClient.Get(ctx, rgKey, rg) - logf.Log.V(1).Info("Fetching ReferenceGrant", "key", rgKey.String(), "error", err) - if err != nil { - return nil - } - return rg - }).WithContext(ctx).Should(PointTo(MatchFields(IgnoreExtras, Fields{ - "ObjectMeta": MatchFields(IgnoreExtras, Fields{ - "Name": Equal(controllers.KuadrantReferenceGrantName), - "Namespace": Equal(kuadrantInstallationNS), - }), - "Spec": MatchFields(IgnoreExtras, Fields{ - "To": ConsistOf(MatchFields(IgnoreExtras, Fields{ - "Group": Equal(gatewayapiv1.Group("")), - "Kind": Equal(gatewayapiv1.Kind("Service")), - "Name": PointTo(Equal(gatewayapiv1.ObjectName(kuadrant.AuthorinoServiceName))), - })), - "From": ContainElements( - MatchFields(IgnoreExtras, Fields{ - "Group": Equal(gatewayapiv1.Group(egv1alpha1.GroupName)), - "Kind": Equal(gatewayapiv1.Kind(egv1alpha1.KindSecurityPolicy)), - "Namespace": Equal(gatewayapiv1.Namespace(testNamespaceOne)), - }), - MatchFields(IgnoreExtras, Fields{ - "Group": Equal(gatewayapiv1.Group(egv1alpha1.GroupName)), - "Kind": Equal(gatewayapiv1.Kind(egv1alpha1.KindSecurityPolicy)), - "Namespace": Equal(gatewayapiv1.Namespace(testNamespaceTwo)), - })), - }), - }))) - }, testTimeOut) - - It("Deleting policy updates reference grant", func(ctx SpecContext) { - err := k8sClient.Delete(ctx, routePolicyTwo) - logf.Log.V(1).Info("Deleting AuthPolicy", "key", client.ObjectKeyFromObject(routePolicyTwo).String(), "error", err) - Expect(err).ToNot(HaveOccurred()) - - rgKey := types.NamespacedName{Name: controllers.KuadrantReferenceGrantName, Namespace: kuadrantInstallationNS} - - Eventually(func() *gatewayapiv1beta1.ReferenceGrant { - rg := &gatewayapiv1beta1.ReferenceGrant{} - err := k8sClient.Get(ctx, rgKey, rg) - logf.Log.V(1).Info("Fetching ReferenceGrant", "key", rgKey.String(), "error", err) - if err != nil { - return nil - } - return rg - }).WithContext(ctx).Should(PointTo(MatchFields(IgnoreExtras, Fields{ - "ObjectMeta": MatchFields(IgnoreExtras, Fields{ - "Name": Equal(controllers.KuadrantReferenceGrantName), - "Namespace": Equal(kuadrantInstallationNS), - }), - "Spec": MatchFields(IgnoreExtras, Fields{ - "To": ConsistOf(MatchFields(IgnoreExtras, Fields{ - "Group": Equal(gatewayapiv1.Group("")), - "Kind": Equal(gatewayapiv1.Kind("Service")), - "Name": PointTo(Equal(gatewayapiv1.ObjectName(kuadrant.AuthorinoServiceName))), - })), - "From": ContainElement(MatchFields(IgnoreExtras, Fields{ - "Group": Equal(gatewayapiv1.Group(egv1alpha1.GroupName)), - "Kind": Equal(gatewayapiv1.Kind(egv1alpha1.KindSecurityPolicy)), - "Namespace": Equal(gatewayapiv1.Namespace(testNamespaceOne)), - })), - }), - }))) - }, testTimeOut) - }) -}) diff --git a/tests/istio/authpolicy_controller_authorizationpolicy_test.go b/tests/istio/authpolicy_controller_authorizationpolicy_test.go deleted file mode 100644 index 546de197f..000000000 --- a/tests/istio/authpolicy_controller_authorizationpolicy_test.go +++ /dev/null @@ -1,328 +0,0 @@ -//go:build integration - -package istio_test - -import ( - "fmt" - "strings" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - secv1beta1resources "istio.io/client-go/pkg/apis/security/v1beta1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/rand" - "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" - - kuadrantv1beta3 "github.com/kuadrant/kuadrant-operator/api/v1beta3" - "github.com/kuadrant/kuadrant-operator/controllers" - "github.com/kuadrant/kuadrant-operator/tests" -) - -var _ = Describe("AuthPolicy controller managing authorization policy", func() { - const ( - testTimeOut = SpecTimeout(2 * time.Minute) - afterEachTimeOut = NodeTimeout(3 * time.Minute) - ) - var ( - testNamespace string - gwHost = fmt.Sprintf("*.toystore-%s.com", rand.String(4)) - ) - - BeforeEach(func(ctx SpecContext) { - testNamespace = tests.CreateNamespace(ctx, testClient()) - - gateway := tests.BuildBasicGateway(TestGatewayName, testNamespace, func(gateway *gatewayapiv1.Gateway) { - gateway.Spec.Listeners[0].Hostname = ptr.To(gatewayapiv1.Hostname(gwHost)) - }) - err := k8sClient.Create(ctx, gateway) - Expect(err).ToNot(HaveOccurred()) - - Eventually(tests.GatewayIsReady(ctx, testClient(), gateway)).WithContext(ctx).Should(BeTrue()) - }) - - AfterEach(func(ctx SpecContext) { - tests.DeleteNamespace(ctx, testClient(), testNamespace) - }, afterEachTimeOut) - - policyFactory := func(mutateFns ...func(policy *kuadrantv1beta3.AuthPolicy)) *kuadrantv1beta3.AuthPolicy { - policy := &kuadrantv1beta3.AuthPolicy{ - TypeMeta: metav1.TypeMeta{ - Kind: "AuthPolicy", - APIVersion: kuadrantv1beta3.GroupVersion.String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "toystore", - Namespace: testNamespace, - }, - Spec: kuadrantv1beta3.AuthPolicySpec{ - TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReference{ - Group: gatewayapiv1.GroupName, - Kind: "HTTPRoute", - Name: TestHTTPRouteName, - }, - Defaults: &kuadrantv1beta3.AuthPolicyCommonSpec{ - AuthScheme: tests.BuildBasicAuthScheme(), - }, - }, - } - for _, mutateFn := range mutateFns { - mutateFn(policy) - } - return policy - } - - randomHostFromGWHost := func() string { - return strings.Replace(gwHost, "*", rand.String(4), 1) - } - - Context("policy attached to the gateway", func() { - - var ( - gwPolicy *kuadrantv1beta3.AuthPolicy - ) - - BeforeEach(func(ctx SpecContext) { - route := tests.BuildBasicHttpRoute(TestHTTPRouteName, TestGatewayName, testNamespace, []string{randomHostFromGWHost()}) - err := k8sClient.Create(ctx, route) - Expect(err).ToNot(HaveOccurred()) - Eventually(tests.RouteIsAccepted(ctx, testClient(), client.ObjectKeyFromObject(route))).WithContext(ctx).Should(BeTrue()) - - gwPolicy = policyFactory(func(policy *kuadrantv1beta3.AuthPolicy) { - policy.Name = "gw-auth" - policy.Spec.TargetRef.Group = gatewayapiv1.GroupName - policy.Spec.TargetRef.Kind = "Gateway" - policy.Spec.TargetRef.Name = TestGatewayName - policy.Spec.CommonSpec().AuthScheme.Authentication["apiKey"].ApiKey.Selector.MatchLabels["admin"] = "yes" - }) - - err = k8sClient.Create(ctx, gwPolicy) - logf.Log.V(1).Info("Creating AuthPolicy", "key", client.ObjectKeyFromObject(gwPolicy).String(), "error", err) - Expect(err).ToNot(HaveOccurred()) - - // check policy status - Eventually(tests.IsAuthPolicyAcceptedAndEnforced(ctx, testClient(), gwPolicy)).WithContext(ctx).Should(BeTrue()) - }) - - It("authpolicy has rules added", func(ctx SpecContext) { - // check istio authorizationpolicy - iapKey := types.NamespacedName{Name: controllers.IstioAuthorizationPolicyName(TestGatewayName, gwPolicy.Spec.TargetRef), Namespace: testNamespace} - iap := &secv1beta1resources.AuthorizationPolicy{} - Eventually(func() bool { - err := k8sClient.Get(ctx, iapKey, iap) - logf.Log.V(1).Info("Fetching Istio's AuthorizationPolicy", "key", iapKey.String(), "error", err) - return err == nil - }).WithContext(ctx).Should(BeTrue()) - - // has the correct target ref - Expect(iap.Spec.TargetRef).To(Not(BeNil())) - Expect(iap.Spec.TargetRef.Group).To(Equal("gateway.networking.k8s.io")) - Expect(iap.Spec.TargetRef.Kind).To(Equal("Gateway")) - Expect(iap.Spec.TargetRef.Name).To(Equal(TestGatewayName)) - Expect(iap.Spec.Rules).To(HaveLen(1)) - Expect(iap.Spec.Rules[0].To).To(HaveLen(1)) - Expect(iap.Spec.Rules[0].To[0].Operation).ShouldNot(BeNil()) - Expect(iap.Spec.Rules[0].To[0].Operation.Hosts).To(Equal([]string{gwHost})) - Expect(iap.Spec.Rules[0].To[0].Operation.Methods).To(Equal([]string{"GET"})) - Expect(iap.Spec.Rules[0].To[0].Operation.Paths).To(Equal([]string{"/toy*"})) - }, testTimeOut) - }) - - Context("policy attached to the route", func() { - var ( - routePolicy *kuadrantv1beta3.AuthPolicy - routeHost = randomHostFromGWHost() - ) - - BeforeEach(func(ctx SpecContext) { - route := tests.BuildBasicHttpRoute(TestHTTPRouteName, TestGatewayName, testNamespace, []string{routeHost}) - err := k8sClient.Create(ctx, route) - Expect(err).ToNot(HaveOccurred()) - Eventually(tests.RouteIsAccepted(ctx, testClient(), client.ObjectKeyFromObject(route))).WithContext(ctx).Should(BeTrue()) - - routePolicy = policyFactory(func(policy *kuadrantv1beta3.AuthPolicy) { - policy.Spec.TargetRef.Group = gatewayapiv1.GroupName - policy.Spec.TargetRef.Kind = "HTTPRoute" - policy.Spec.TargetRef.Name = TestHTTPRouteName - }) - - err = k8sClient.Create(ctx, routePolicy) - logf.Log.V(1).Info("Creating AuthPolicy", "key", client.ObjectKeyFromObject(routePolicy).String(), "error", err) - Expect(err).ToNot(HaveOccurred()) - - // check policy status - Eventually(tests.IsAuthPolicyAcceptedAndEnforced(ctx, testClient(), routePolicy)).WithContext(ctx).Should(BeTrue()) - }) - - It("authorization policy has rules added", func(ctx SpecContext) { - // check istio authorizationpolicy - iapKey := types.NamespacedName{Name: controllers.IstioAuthorizationPolicyName(TestGatewayName, routePolicy.Spec.TargetRef), Namespace: testNamespace} - iap := &secv1beta1resources.AuthorizationPolicy{} - Eventually(func() bool { - err := k8sClient.Get(ctx, iapKey, iap) - logf.Log.V(1).Info("Fetching Istio's AuthorizationPolicy", "key", iapKey.String(), "error", err) - return err == nil - }).WithContext(ctx).Should(BeTrue()) - - // has the correct target ref - Expect(iap.Spec.TargetRef).To(Not(BeNil())) - Expect(iap.Spec.TargetRef.Group).To(Equal("gateway.networking.k8s.io")) - Expect(iap.Spec.TargetRef.Kind).To(Equal("Gateway")) - Expect(iap.Spec.TargetRef.Name).To(Equal(TestGatewayName)) - Expect(iap.Spec.Rules).To(HaveLen(1)) - Expect(iap.Spec.Rules[0].To).To(HaveLen(1)) - Expect(iap.Spec.Rules[0].To[0].Operation).ShouldNot(BeNil()) - Expect(iap.Spec.Rules[0].To[0].Operation.Hosts).To(Equal([]string{routeHost})) - Expect(iap.Spec.Rules[0].To[0].Operation.Methods).To(Equal([]string{"GET"})) - Expect(iap.Spec.Rules[0].To[0].Operation.Paths).To(Equal([]string{"/toy*"})) - }, testTimeOut) - - It("Deletes authorizationpolicy when the policy is deleted", func(ctx SpecContext) { - // delete policy - err := k8sClient.Delete(ctx, routePolicy) - logf.Log.V(1).Info("Deleting AuthPolicy", "key", client.ObjectKeyFromObject(routePolicy).String(), "error", err) - Expect(err).ToNot(HaveOccurred()) - - // check istio authorizationpolicy - iapKey := types.NamespacedName{Name: controllers.IstioAuthorizationPolicyName(TestGatewayName, routePolicy.Spec.TargetRef), Namespace: testNamespace} - Eventually(func() bool { - err := k8sClient.Get(ctx, iapKey, &secv1beta1resources.AuthorizationPolicy{}) - logf.Log.V(1).Info("Fetching Istio's AuthorizationPolicy", "key", iapKey.String(), "error", err) - return apierrors.IsNotFound(err) - }).WithContext(ctx).Should(BeTrue()) - }, testTimeOut) - }) - - Context("Attaches policy to the Gateway while having other policies attached to some HTTPRoutes", func() { - // Gw A - // Route A -> Gw A - // Route B -> Gw A - // RLP 1 -> Gw A - // RLP 2 -> Route A - var ( - gwPolicy *kuadrantv1beta3.AuthPolicy - routeHost = randomHostFromGWHost() - ) - BeforeEach(func(ctx SpecContext) { - route := tests.BuildBasicHttpRoute(TestHTTPRouteName, TestGatewayName, testNamespace, []string{routeHost}) - err := k8sClient.Create(ctx, route) - Expect(err).ToNot(HaveOccurred()) - Eventually(tests.RouteIsAccepted(ctx, testClient(), client.ObjectKeyFromObject(route))).WithContext(ctx).Should(BeTrue()) - - gwPolicy = policyFactory(func(policy *kuadrantv1beta3.AuthPolicy) { - policy.Name = "gw-auth" - policy.Spec.TargetRef.Group = gatewayapiv1.GroupName - policy.Spec.TargetRef.Kind = "Gateway" - policy.Spec.TargetRef.Name = TestGatewayName - policy.Spec.CommonSpec().AuthScheme.Authentication["apiKey"].ApiKey.Selector.MatchLabels["admin"] = "yes" - }) - - err = k8sClient.Create(ctx, gwPolicy) - logf.Log.V(1).Info("Creating AuthPolicy", "key", client.ObjectKeyFromObject(gwPolicy).String(), "error", err) - Expect(err).ToNot(HaveOccurred()) - - // check policy status - Eventually(tests.IsAuthPolicyAcceptedAndEnforced(ctx, testClient(), gwPolicy)).WithContext(ctx).Should(BeTrue()) - - routePolicy := policyFactory(func(policy *kuadrantv1beta3.AuthPolicy) { - policy.Spec.TargetRef.Group = gatewayapiv1.GroupName - policy.Spec.TargetRef.Kind = "HTTPRoute" - policy.Spec.TargetRef.Name = TestHTTPRouteName - }) - - err = k8sClient.Create(ctx, routePolicy) - logf.Log.V(1).Info("Creating AuthPolicy", "key", client.ObjectKeyFromObject(routePolicy).String(), "error", err) - Expect(err).ToNot(HaveOccurred()) - - // check policy status - Eventually(tests.IsAuthPolicyAcceptedAndEnforced(ctx, testClient(), routePolicy)).WithContext(ctx).Should(BeTrue()) - - // create second (policyless) httproute - otherRoute := tests.BuildBasicHttpRoute("policyless-route", TestGatewayName, testNamespace, []string{randomHostFromGWHost()}) - otherRoute.Spec.Rules = []gatewayapiv1.HTTPRouteRule{ - { - Matches: []gatewayapiv1.HTTPRouteMatch{ - { - Method: ptr.To(gatewayapiv1.HTTPMethod("POST")), - }, - }, - }, - } - err = k8sClient.Create(ctx, otherRoute) - Expect(err).ToNot(HaveOccurred()) - Eventually(tests.RouteIsAccepted(ctx, testClient(), client.ObjectKeyFromObject(otherRoute))).WithContext(ctx).Should(BeTrue()) - }) - - It("check istio authorizationpolicy", func(ctx SpecContext) { - iapKey := types.NamespacedName{Name: controllers.IstioAuthorizationPolicyName(TestGatewayName, gwPolicy.Spec.TargetRef), Namespace: testNamespace} - iap := &secv1beta1resources.AuthorizationPolicy{} - Eventually(func() bool { - err := k8sClient.Get(ctx, iapKey, iap) - logf.Log.V(1).Info("Fetching Istio's AuthorizationPolicy", "key", iapKey.String(), "error", err) - return err == nil - }).WithContext(ctx).Should(BeTrue()) - Expect(iap.Spec.Rules).To(HaveLen(1)) - Expect(iap.Spec.Rules[0].To).To(HaveLen(1)) - Expect(iap.Spec.Rules[0].To[0].Operation).ShouldNot(BeNil()) - Expect(iap.Spec.Rules[0].To[0].Operation.Hosts).To(Equal([]string{gwHost})) - Expect(iap.Spec.Rules[0].To[0].Operation.Methods).To(Equal([]string{"POST"})) - Expect(iap.Spec.Rules[0].To[0].Operation.Paths).To(Equal([]string{"/*"})) - }, testTimeOut) - }) - - Context("Complex HTTPRoute with multiple rules and hostnames", func() { - - var ( - routeHost1 = randomHostFromGWHost() - routeHost2 = randomHostFromGWHost() - ) - - BeforeEach(func(ctx SpecContext) { - route := tests.BuildMultipleRulesHttpRoute(TestHTTPRouteName, TestGatewayName, testNamespace, []string{routeHost1, routeHost2}) - err := k8sClient.Create(ctx, route) - Expect(err).ToNot(HaveOccurred()) - Eventually(tests.RouteIsAccepted(ctx, testClient(), client.ObjectKeyFromObject(route))).WithContext(ctx).Should(BeTrue()) - }) - - It("Attaches simple policy to the HTTPRoute", func(ctx SpecContext) { - policy := policyFactory() - - err := k8sClient.Create(ctx, policy) - Expect(err).ToNot(HaveOccurred()) - - // check policy status - Eventually(tests.IsAuthPolicyAcceptedAndEnforced(ctx, testClient(), policy)).WithContext(ctx).Should(BeTrue()) - - // check istio authorizationpolicy - iapKey := types.NamespacedName{Name: controllers.IstioAuthorizationPolicyName(TestGatewayName, policy.Spec.TargetRef), Namespace: testNamespace} - iap := &secv1beta1resources.AuthorizationPolicy{} - Eventually(func() bool { - err := k8sClient.Get(ctx, iapKey, iap) - logf.Log.V(1).Info("Fetching Istio's AuthorizationPolicy", "key", iapKey.String(), "error", err) - return err == nil - }).WithContext(ctx).Should(BeTrue()) - Expect(iap.Spec.Rules).To(HaveLen(3)) - Expect(iap.Spec.Rules[0].To).To(HaveLen(1)) - Expect(iap.Spec.Rules[0].To[0].Operation).ShouldNot(BeNil()) - Expect(iap.Spec.Rules[0].To[0].Operation.Hosts).To(Equal([]string{routeHost1, routeHost2})) - Expect(iap.Spec.Rules[0].To[0].Operation.Methods).To(Equal([]string{"POST"})) - Expect(iap.Spec.Rules[0].To[0].Operation.Paths).To(Equal([]string{"/admin*"})) - Expect(iap.Spec.Rules[1].To).To(HaveLen(1)) - Expect(iap.Spec.Rules[1].To[0].Operation).ShouldNot(BeNil()) - Expect(iap.Spec.Rules[1].To[0].Operation.Hosts).To(Equal([]string{routeHost1, routeHost2})) - Expect(iap.Spec.Rules[1].To[0].Operation.Methods).To(Equal([]string{"DELETE"})) - Expect(iap.Spec.Rules[1].To[0].Operation.Paths).To(Equal([]string{"/admin*"})) - Expect(iap.Spec.Rules[2].To).To(HaveLen(1)) - Expect(iap.Spec.Rules[2].To[0].Operation).ShouldNot(BeNil()) - Expect(iap.Spec.Rules[2].To[0].Operation.Hosts).To(Equal([]string{routeHost1, routeHost2})) - Expect(iap.Spec.Rules[2].To[0].Operation.Methods).To(Equal([]string{"GET"})) - Expect(iap.Spec.Rules[2].To[0].Operation.Paths).To(Equal([]string{"/private*"})) - }, testTimeOut) - }) -})