Skip to content

Commit

Permalink
RLP atomic override (#523)
Browse files Browse the repository at this point in the history
* feat: apply RLP gateway overrides

* feat: RLP CEL implicit default and override mutual exclusivity

* refactor: use DAG for applying RLP overrides

* refactor: RLP CEL for override - only allowed for gateways and mutually exclusive with defaults

* docs: RLP overrides

* feat: rlp enforced condition

* refactor: event handler for limitador status changes only

* refactor: overridden policy map

* refactor: override logic and integration tests

* refactor: overridden to affected policy map

* tests: add testing enforced condition to other integration tests

* fix: wasm plugin config not accounting for limit overrides

* tests: AuthPolicy enforced condition message

* fix: invalid reason not deleting second ns after test & fix missing comments
  • Loading branch information
KevFan authored Apr 22, 2024
1 parent 11840b8 commit 0eb260b
Show file tree
Hide file tree
Showing 27 changed files with 2,150 additions and 321 deletions.
12 changes: 12 additions & 0 deletions api/v1beta2/ratelimitpolicy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ func (l Limit) CountersAsStringList() []string {
// RateLimitPolicySpec defines the desired state of RateLimitPolicy
// +kubebuilder:validation:XValidation:rule="self.targetRef.kind != 'Gateway' || !has(self.limits) || !self.limits.exists(x, has(self.limits[x].routeSelectors))",message="route selectors not supported when targeting a Gateway"
// +kubebuilder:validation:XValidation:rule="!(has(self.defaults) && has(self.limits))",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.limits))",message="Overrides and implicit defaults are mutually exclusive"
// +kubebuilder:validation:XValidation:rule="!(has(self.overrides) && self.targetRef.kind != 'Gateway')",message="Overrides are only allowed for policies targeting a Gateway resource"
type RateLimitPolicySpec struct {
// TargetRef identifies an API object to apply policy to.
// +kubebuilder:validation:XValidation:rule="self.group == 'gateway.networking.k8s.io'",message="Invalid targetRef.group. The only supported value is 'gateway.networking.k8s.io'"
Expand All @@ -133,6 +136,11 @@ type RateLimitPolicySpec struct {
// +optional
Defaults *RateLimitPolicyCommonSpec `json:"defaults,omitempty"`

// Overrides define override values for this policy and for policies inheriting this policy.
// Overrides are mutually exclusive with implicit defaults and explicit Defaults defined by RateLimitPolicyCommonSpec.
// +optional
Overrides *RateLimitPolicyCommonSpec `json:"overrides,omitempty"`

// RateLimitPolicyCommonSpec defines implicit default values for this policy and for policies inheriting this policy.
// RateLimitPolicyCommonSpec is mutually exclusive with explicit defaults defined by Defaults.
RateLimitPolicyCommonSpec `json:""`
Expand Down Expand Up @@ -286,6 +294,10 @@ func (r *RateLimitPolicySpec) CommonSpec() *RateLimitPolicyCommonSpec {
return r.Defaults
}

if r.Overrides != nil {
return r.Overrides
}

return &r.RateLimitPolicyCommonSpec
}

Expand Down
5 changes: 5 additions & 0 deletions api/v1beta2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

401 changes: 401 additions & 0 deletions bundle/manifests/kuadrant.io_ratelimitpolicies.yaml

Large diffs are not rendered by default.

401 changes: 401 additions & 0 deletions config/crd/bases/kuadrant.io_ratelimitpolicies.yaml

Large diffs are not rendered by default.

77 changes: 49 additions & 28 deletions controllers/authpolicy_authconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ import (
"errors"
"fmt"
"reflect"
"slices"
"strings"

"k8s.io/utils/ptr"

"github.com/go-logr/logr"
authorinoapi "github.com/kuadrant/authorino/api/v1beta2"
apierrors "k8s.io/apimachinery/pkg/api/errors"
Expand All @@ -18,6 +17,7 @@ import (

api "github.com/kuadrant/kuadrant-operator/api/v1beta2"
"github.com/kuadrant/kuadrant-operator/pkg/common"
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"
)
Expand Down Expand Up @@ -67,14 +67,17 @@ func (r *AuthPolicyReconciler) desiredAuthConfig(ctx context.Context, ap *api.Au

switch obj := targetNetworkObject.(type) {
case *gatewayapiv1.HTTPRoute:
ok, err := routeGatewayHasAuthOverrides(ctx, obj, r.Client())
t, err := r.generateTopology(ctx)
if err != nil {
logger.V(1).Info("Failed to generate topology", "error", err)
return nil, err
}
if ok {

overrides := routeGatewayAuthOverrides(t, ap)
if len(overrides) != 0 {
logger.V(1).Info("targeted gateway has authpolicy with atomic overrides, skipping authorino authconfig for the HTTPRoute authpolicy")
utils.TagObjectToDelete(authConfig)
r.OverriddenPolicyMap.SetOverriddenPolicy(ap)
r.AffectedPolicyMap.SetAffectedPolicy(ap, overrides)
return authConfig, nil
}
route = obj
Expand Down Expand Up @@ -105,7 +108,14 @@ func (r *AuthPolicyReconciler) desiredAuthConfig(ctx context.Context, ap *api.Au
if len(rules) == 0 {
logger.V(1).Info("no httproutes attached to the targeted gateway, skipping authorino authconfig for the gateway authpolicy")
utils.TagObjectToDelete(authConfig)
r.OverriddenPolicyMap.SetOverriddenPolicy(ap)
obj := targetNetworkObject.(*gatewayapiv1.Gateway)
gatewayWrapper := kuadrant.GatewayWrapper{Gateway: obj, Referrer: ap}
refs := gatewayWrapper.PolicyRefs()
filteredRef := utils.Filter(refs, func(key client.ObjectKey) bool {
return key != client.ObjectKeyFromObject(ap)
})

r.AffectedPolicyMap.SetAffectedPolicy(ap, filteredRef)
return authConfig, nil
}
route = &gatewayapiv1.HTTPRoute{
Expand All @@ -116,8 +126,8 @@ func (r *AuthPolicyReconciler) desiredAuthConfig(ctx context.Context, ap *api.Au
}
}

// AuthPolicy is not overridden if we still need to create an AuthConfig for it
r.OverriddenPolicyMap.RemoveOverriddenPolicy(ap)
// AuthPolicy is not Affected if we still need to create an AuthConfig for it
r.AffectedPolicyMap.RemoveAffectedPolicy(ap)

// hosts
authConfig.Spec.Hosts = hosts
Expand Down Expand Up @@ -185,32 +195,43 @@ func (r *AuthPolicyReconciler) desiredAuthConfig(ctx context.Context, ap *api.Au
return mergeConditionsFromRouteSelectorsIntoConfigs(ap, route, authConfig)
}

// routeGatewayHasAuthOverrides return true when the gateway which a route is attached to has an attached authPolicy that defines atomic overrides
func routeGatewayHasAuthOverrides(ctx context.Context, route *gatewayapiv1.HTTPRoute, c client.Client) (bool, error) {
for idx := range route.Spec.ParentRefs {
parentRef := route.Spec.ParentRefs[idx]
gw := &gatewayapiv1.Gateway{}
namespace := ptr.Deref(parentRef.Namespace, gatewayapiv1.Namespace(route.GetNamespace()))
err := c.Get(ctx, client.ObjectKey{Name: string(parentRef.Name), Namespace: string(namespace)}, gw)
if err != nil {
return false, err
}
// routeGatewayAuthOverrides returns the GW auth policies that has an override field set
func routeGatewayAuthOverrides(t *kuadrantgatewayapi.Topology, ap *api.AuthPolicy) []client.ObjectKey {
affectedPolicies := getAffectedPolicies(t, ap)

annotation, ok := gw.GetAnnotations()[common.AuthPolicyBackRefAnnotation]
// Filter the policies where:
// 1. targets a gateway
// 2. is not the current AP that is being assessed
// 3. is an overriding policy
affectedPolicies = utils.Filter(affectedPolicies, func(policy kuadrantgatewayapi.Policy) bool {
p, ok := policy.(*api.AuthPolicy)
if !ok {
continue
}
otherAP := &api.AuthPolicy{}
err = c.Get(ctx, utils.NamespacedNameToObjectKey(annotation, gw.Namespace), otherAP)
if err != nil {
return false, err
return false
}
return kuadrantgatewayapi.IsTargetRefGateway(policy.GetTargetRef()) &&
ap.GetUID() != policy.GetUID() && p.IsAtomicOverride()
})

if otherAP.IsAtomicOverride() {
return true, nil
return utils.Map(affectedPolicies, func(policy kuadrantgatewayapi.Policy) client.ObjectKey {
return client.ObjectKeyFromObject(policy)
})
}

func getAffectedPolicies(t *kuadrantgatewayapi.Topology, ap *api.AuthPolicy) []kuadrantgatewayapi.Policy {
topologyIndexes := kuadrantgatewayapi.NewTopologyIndexes(t)
var affectedPolicies []kuadrantgatewayapi.Policy

// If AP is listed within the policies from gateway, it potentially can be overridden by it
for _, gw := range t.Gateways() {
policyList := topologyIndexes.PoliciesFromGateway(gw.Gateway)
if slices.Contains(utils.Map(policyList, func(p kuadrantgatewayapi.Policy) client.ObjectKey {
return client.ObjectKeyFromObject(p)
}), client.ObjectKeyFromObject(ap)) {
affectedPolicies = append(affectedPolicies, policyList...)
}
}
return false, nil

return affectedPolicies
}

// authConfigName returns the name of Authorino AuthConfig CR.
Expand Down
8 changes: 4 additions & 4 deletions controllers/authpolicy_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ const authPolicyFinalizer = "authpolicy.kuadrant.io/finalizer"
type AuthPolicyReconciler struct {
*reconcilers.BaseReconciler
TargetRefReconciler reconcilers.TargetRefReconciler
// OverriddenPolicyMap tracks the overridden policies to report their status.
OverriddenPolicyMap *kuadrant.OverriddenPolicyMap
// AffectedPolicyMap tracks the affected policies to report their status.
AffectedPolicyMap *kuadrant.AffectedPolicyMap
}

//+kubebuilder:rbac:groups=kuadrant.io,resources=authpolicies,verbs=get;list;watch;create;update;patch;delete
Expand Down Expand Up @@ -72,7 +72,7 @@ func (r *AuthPolicyReconciler) Reconcile(eventCtx context.Context, req ctrl.Requ
if delResErr == nil {
delResErr = err
}
return r.reconcileStatus(ctx, ap, targetNetworkObject, kuadrant.NewErrTargetNotFound(ap.Kind(), ap.GetTargetRef(), delResErr))
return r.reconcileStatus(ctx, ap, kuadrant.NewErrTargetNotFound(ap.Kind(), ap.GetTargetRef(), delResErr))
}
return ctrl.Result{}, err
}
Expand Down Expand Up @@ -108,7 +108,7 @@ func (r *AuthPolicyReconciler) Reconcile(eventCtx context.Context, req ctrl.Requ
specErr := r.reconcileResources(ctx, ap, targetNetworkObject)

// reconcile authpolicy status
statusResult, statusErr := r.reconcileStatus(ctx, ap, targetNetworkObject, specErr)
statusResult, statusErr := r.reconcileStatus(ctx, ap, specErr)

if specErr != nil {
return ctrl.Result{}, specErr
Expand Down
Loading

0 comments on commit 0eb260b

Please sign in to comment.