diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index fffa4e32c..632f1db89 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -50,15 +50,16 @@ jobs: verbose: true controllers-integration-tests: - name: Integration Tests for github.com/kuadrant/kuadrant-operator/controllers + name: Integration Tests for kuadrant-operator/controllers strategy: matrix: - gatewayapi-provider: [istio] include: - # - istio-type: sail - # gatewayapi-provider: istio - - istio-type: istioctl - gatewayapi-provider: istio + - gatewayapi-provider: istio + istio-type: istioctl +# - gatewayapi-provider: istio +# istio-type: sail + - gatewayapi-provider: envoygateway + fail-fast: false runs-on: ubuntu-latest env: KIND_CLUSTER_NAME: kuadrant-test @@ -89,7 +90,7 @@ jobs: make env-setup GATEWAYAPI_PROVIDER=${{ matrix.gatewayapi-provider }} ISTIO_INSTALL_SAIL=${{ matrix.istio-type == 'sail' && true || false }} - name: Run integration tests run: | - make test-integration + make test-integration GATEWAYAPI_PROVIDER=${{ matrix.gatewayapi-provider }} - name: Upload integration-test coverage reports to CodeCov # more at https://github.com/codecov/codecov-action # Only run if the feature branch is in your repo (not in a fork) @@ -103,7 +104,7 @@ jobs: verbose: true bare-k8s-integration-tests: - name: Integration Tests for github.com/kuadrant/kuadrant-operator/tests/bare_k8s + name: Integration Tests for kuadrant-operator/tests/bare_k8s runs-on: ubuntu-latest env: KIND_CLUSTER_NAME: kuadrant-test @@ -148,7 +149,7 @@ jobs: verbose: true gatewayapi-integration-tests: - name: Integration Tests for github.com/kuadrant/kuadrant-operator/tests/gatewayapi + name: Integration Tests for kuadrant-operator/tests/gatewayapi runs-on: ubuntu-latest env: KIND_CLUSTER_NAME: kuadrant-test @@ -192,8 +193,12 @@ jobs: fail_ci_if_error: false verbose: true - istio-integration-tests: - name: Integration Tests for github.com/kuadrant/kuadrant-operator/tests/istio + gatewayapi-provider-integration-tests: + name: Integration Tests for kuadrant-operator/tests/[gatewayapi-provider] + strategy: + matrix: + gatewayapi-provider: [istio, envoygateway] + fail-fast: false runs-on: ubuntu-latest env: KIND_CLUSTER_NAME: kuadrant-test @@ -219,12 +224,12 @@ jobs: - name: Check cluster info run: | kubectl cluster-info dump - - name: Run make istio-env-setup + - name: Run make ${{ matrix.gatewayapi-provider }}-env-setup run: | - make istio-env-setup + make ${{ matrix.gatewayapi-provider }}-env-setup - name: Run integration tests run: | - make test-istio-env-integration + make test-${{ matrix.gatewayapi-provider }}-env-integration - name: Upload integration-test coverage reports to CodeCov # more at https://github.com/codecov/codecov-action # Only run if the feature branch is in your repo (not in a fork) @@ -233,7 +238,7 @@ jobs: uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} - flags: istio-integration + flags: ${{ matrix.gatewayapi-provider }}-integration fail_ci_if_error: false verbose: true diff --git a/Makefile b/Makefile index 9022a8504..22c4f87be 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,8 @@ SHELL = /usr/bin/env bash -o pipefail MKFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST))) PROJECT_PATH := $(patsubst %/,%,$(dir $(MKFILE_PATH))) +OS = $(shell uname -s | tr '[:upper:]' '[:lower:]') +ARCH := $(shell uname -m | tr '[:upper:]' '[:lower:]') # Container Engine to be used for building image and with kind CONTAINER_ENGINE ?= docker @@ -168,6 +170,9 @@ else RELATED_IMAGE_WASMSHIM ?= oci://quay.io/kuadrant/wasm-shim:$(WASM_SHIM_VERSION) endif +## gatewayapi-provider +GATEWAYAPI_PROVIDER ?= istio + all: build ##@ General @@ -258,7 +263,7 @@ $(GINKGO): .PHONY: ginkgo ginkgo: $(GINKGO) ## Download ginkgo locally if necessary. -HELM = ./bin/helm +HELM = $(PROJECT_PATH)/bin/helm HELM_VERSION = v3.15.0 $(HELM): @{ \ diff --git a/README.md b/README.md index eaaf0d6c7..b18b3c190 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,10 @@ Kuadrant is a system of cloud-native k8s components that grows as users’ needs ## Architecture -Kuadrant relies on [Istio](https://istio.io/) and the [Gateway API](https://gateway-api.sigs.k8s.io/) -to operate the cluster (Istio's) ingress gateway to provide API management with **authentication** (authN), +Kuadrant relies on the [Gateway API](https://gateway-api.sigs.k8s.io/) and one Gateway API provider +being installed on the cluster. Currently only [Istio](https://istio.io/) and +[EnvoyGateway](https://gateway.envoyproxy.io/) are supported +to operate the cluster ingress gateway to provide API management with **authentication** (authN), **authorization** (authZ) and **rate limiting** capabilities. ### Kuadrant components @@ -67,11 +69,11 @@ Additionally, Kuadrant provides the following CRDs ### Pre-requisites -* Istio is installed in the cluster. Otherwise, refer to the - [Istio getting started guide](https://istio.io/latest/docs/setup/getting-started/). -* Kubernetes Gateway API is installed in the cluster. Otherwise, - [configure Istio to expose a service using the Kubernetes Gateway API](https://istio.io/latest/docs/tasks/traffic-management/ingress/gateway-api/). -* cert-manager is installed in the cluster. Otherwise, refer to the +* Istio or Envoy Gateway is installed in the cluster. Otherwise, refer to the + [Istio getting started guide](https://istio.io/latest/docs/setup/getting-started/) + or [EnvoyGateway getting started guide](https://gateway.envoyproxy.io/docs/). +* Kubernetes Gateway API is installed in the cluster. +* cert-manager is installed in the cluster. Otherwise, refer to the [cert-manager installation guide](https://cert-manager.io/docs/installation/). ### Installing Kuadrant @@ -139,7 +141,7 @@ EOF #### If you are a *Cluster Operator* -* (Optionally) deploy istio ingress gateway using the +* (Optionally) deploy ingress gateway using the [Gateway](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.Gateway) resource. * Write and apply the Kuadrant's [RateLimitPolicy](doc/rate-limiting.md) and/or [AuthPolicy](doc/auth.md) custom resources targeting the Gateway resource @@ -175,4 +177,4 @@ This software is licensed under the [Apache 2.0 license](https://www.apache.org/ See the LICENSE and NOTICE files that should have been provided along with this software for details. -[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FKuadrant%2Fkuadrant-operator.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2FKuadrant%2Fkuadrant-operator?ref=badge_large) \ No newline at end of file +[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FKuadrant%2Fkuadrant-operator.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2FKuadrant%2Fkuadrant-operator?ref=badge_large) diff --git a/bundle/manifests/kuadrant-operator.clusterserviceversion.yaml b/bundle/manifests/kuadrant-operator.clusterserviceversion.yaml index 954a86b16..81d5ead52 100644 --- a/bundle/manifests/kuadrant-operator.clusterserviceversion.yaml +++ b/bundle/manifests/kuadrant-operator.clusterserviceversion.yaml @@ -282,6 +282,42 @@ spec: - patch - update - watch + - apiGroups: + - gateway.envoyproxy.io + resources: + - envoyextensionpolicies + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - gateway.envoyproxy.io + resources: + - envoypatchpolicies + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - gateway.envoyproxy.io + resources: + - securitypolicies + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - gateway.networking.k8s.io resources: @@ -333,6 +369,18 @@ spec: - get - patch - update + - apiGroups: + - gateway.networking.k8s.io + resources: + - referencegrants + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - install.istio.io resources: diff --git a/config/dependencies/envoy-gateway/gateway/gateway-class.yaml b/config/dependencies/envoy-gateway/gateway/gateway-class.yaml new file mode 100644 index 000000000..d2b456504 --- /dev/null +++ b/config/dependencies/envoy-gateway/gateway/gateway-class.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: GatewayClass +metadata: + name: envoygateway +spec: + controllerName: gateway.envoyproxy.io/gatewayclass-controller diff --git a/config/dependencies/envoy-gateway/gateway/gateway.yaml b/config/dependencies/envoy-gateway/gateway/gateway.yaml new file mode 100644 index 000000000..798383087 --- /dev/null +++ b/config/dependencies/envoy-gateway/gateway/gateway.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: kuadrant-ingressgateway +spec: + gatewayClassName: envoygateway + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All diff --git a/config/dependencies/envoy-gateway/gateway/kustomization.yaml b/config/dependencies/envoy-gateway/gateway/kustomization.yaml new file mode 100644 index 000000000..fdfd9e6dc --- /dev/null +++ b/config/dependencies/envoy-gateway/gateway/kustomization.yaml @@ -0,0 +1,7 @@ +--- +# Adds namespace to all resources. +namespace: gateway-system +resources: +- namespace.yaml +- gateway-class.yaml +- gateway.yaml diff --git a/config/dependencies/envoy-gateway/gateway/namespace.yaml b/config/dependencies/envoy-gateway/gateway/namespace.yaml new file mode 100644 index 000000000..99f749f52 --- /dev/null +++ b/config/dependencies/envoy-gateway/gateway/namespace.yaml @@ -0,0 +1,5 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: gateway-system diff --git a/config/dependencies/istio/gateway/gateway.yaml b/config/dependencies/istio/gateway/gateway.yaml index 22be12636..45c141eb6 100644 --- a/config/dependencies/istio/gateway/gateway.yaml +++ b/config/dependencies/istio/gateway/gateway.yaml @@ -4,7 +4,7 @@ kind: Gateway metadata: labels: istio: ingressgateway - name: istio-ingressgateway + name: kuadrant-ingressgateway spec: gatewayClassName: istio listeners: diff --git a/config/dependencies/istio/gateway/kustomization.yaml b/config/dependencies/istio/gateway/kustomization.yaml index b489ae5d3..6a519b4df 100644 --- a/config/dependencies/istio/gateway/kustomization.yaml +++ b/config/dependencies/istio/gateway/kustomization.yaml @@ -1,5 +1,6 @@ --- # Adds namespace to all resources. -namespace: istio-system +namespace: gateway-system resources: +- namespace.yaml - gateway.yaml diff --git a/config/dependencies/istio/gateway/namespace.yaml b/config/dependencies/istio/gateway/namespace.yaml new file mode 100644 index 000000000..99f749f52 --- /dev/null +++ b/config/dependencies/istio/gateway/namespace.yaml @@ -0,0 +1,5 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: gateway-system diff --git a/config/observability/openshift/telemetry.yaml b/config/observability/openshift/telemetry.yaml index c3a7509b2..7b6c6f11f 100644 --- a/config/observability/openshift/telemetry.yaml +++ b/config/observability/openshift/telemetry.yaml @@ -2,7 +2,7 @@ apiVersion: telemetry.istio.io/v1alpha1 kind: Telemetry metadata: name: namespace-metrics - namespace: istio-system + namespace: gateway-system spec: metrics: - providers: diff --git a/config/observability/prometheus/monitors/pod-monitor-envoy.yaml b/config/observability/prometheus/monitors/pod-monitor-envoy.yaml index 1d98a8f8a..e10c5c54d 100644 --- a/config/observability/prometheus/monitors/pod-monitor-envoy.yaml +++ b/config/observability/prometheus/monitors/pod-monitor-envoy.yaml @@ -5,10 +5,10 @@ metadata: spec: namespaceSelector: matchNames: - - istio-system + - gateway-system selector: matchLabels: - app: istio-ingressgateway + app: kuadrant-ingressgateway podMetricsEndpoints: - port: http-envoy-prom path: /stats/prometheus diff --git a/config/observability/prometheus/monitors/service-monitor-istiod.yaml b/config/observability/prometheus/monitors/service-monitor-istiod.yaml index 656cd440b..54dcd4257 100644 --- a/config/observability/prometheus/monitors/service-monitor-istiod.yaml +++ b/config/observability/prometheus/monitors/service-monitor-istiod.yaml @@ -5,7 +5,7 @@ metadata: spec: namespaceSelector: matchNames: - - istio-system + - gateway-system selector: matchLabels: app: istiod diff --git a/config/observability/prometheus/telemetry.yaml b/config/observability/prometheus/telemetry.yaml index ac6796fce..df37e0aab 100644 --- a/config/observability/prometheus/telemetry.yaml +++ b/config/observability/prometheus/telemetry.yaml @@ -2,7 +2,7 @@ apiVersion: telemetry.istio.io/v1alpha1 kind: Telemetry metadata: name: namespace-metrics - namespace: istio-system + namespace: gateway-system spec: metrics: - providers: diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 0a9fcd949..602db0f6d 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -130,6 +130,42 @@ rules: - patch - update - watch +- apiGroups: + - gateway.envoyproxy.io + resources: + - envoyextensionpolicies + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - gateway.envoyproxy.io + resources: + - envoypatchpolicies + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - gateway.envoyproxy.io + resources: + - securitypolicies + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - gateway.networking.k8s.io resources: @@ -181,6 +217,18 @@ rules: - get - patch - update +- apiGroups: + - gateway.networking.k8s.io + resources: + - referencegrants + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - install.istio.io resources: diff --git a/controllers/authpolicy_controller.go b/controllers/authpolicy_controller.go index e5bc41b80..2ce57e6f3 100644 --- a/controllers/authpolicy_controller.go +++ b/controllers/authpolicy_controller.go @@ -35,7 +35,6 @@ type AuthPolicyReconciler struct { //+kubebuilder:rbac:groups=kuadrant.io,resources=authpolicies,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=kuadrant.io,resources=authpolicies/finalizers,verbs=update //+kubebuilder:rbac:groups=kuadrant.io,resources=authpolicies/status,verbs=get;update;patch -//+kubebuilder:rbac:groups=security.istio.io,resources=authorizationpolicies,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=authorino.kuadrant.io,resources=authconfigs,verbs=get;list;watch;create;update;patch;delete func (r *AuthPolicyReconciler) Reconcile(eventCtx context.Context, req ctrl.Request) (ctrl.Result, error) { @@ -157,10 +156,6 @@ func (r *AuthPolicyReconciler) reconcileResources(ctx context.Context, ap *api.A return err } - if err := r.reconcileIstioAuthorizationPolicies(ctx, ap, targetNetworkObject, gatewayDiffObj); err != nil { - return fmt.Errorf("reconcile AuthorizationPolicy error %w", err) - } - if err := r.reconcileAuthConfigs(ctx, ap, targetNetworkObject); err != nil { return fmt.Errorf("reconcile AuthConfig error %w", err) } @@ -213,10 +208,6 @@ func (r *AuthPolicyReconciler) deleteResources(ctx context.Context, ap *api.Auth return err } - if err := r.deleteIstioAuthorizationPolicies(ctx, ap, gatewayDiffObj); err != nil { - return err - } - // remove direct back ref if targetNetworkObject != nil { if err := r.deleteNetworkResourceDirectBackReference(ctx, targetNetworkObject, ap); err != nil { diff --git a/controllers/authpolicy_envoysecuritypolicy_controller.go b/controllers/authpolicy_envoysecuritypolicy_controller.go new file mode 100644 index 000000000..ba4a9282a --- /dev/null +++ b/controllers/authpolicy_envoysecuritypolicy_controller.go @@ -0,0 +1,209 @@ +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" + kuadrantv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2" + 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(), kuadrantv1beta2.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( + &kuadrantv1beta2.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.go b/controllers/authpolicy_istio_authorizationpolicy_controller.go similarity index 68% rename from controllers/authpolicy_istio_authorizationpolicy.go rename to controllers/authpolicy_istio_authorizationpolicy_controller.go index 14ff12b16..11adcc461 100644 --- a/controllers/authpolicy_istio_authorizationpolicy.go +++ b/controllers/authpolicy_istio_authorizationpolicy_controller.go @@ -2,92 +2,108 @@ package controllers import ( "context" + "encoding/json" "errors" "fmt" - "reflect" "github.com/go-logr/logr" + "github.com/google/uuid" + "github.com/samber/lo" istiosecurity "istio.io/api/security/v1beta1" - istio "istio.io/client-go/pkg/apis/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/apimachinery/pkg/labels" "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" - api "github.com/kuadrant/kuadrant-operator/api/v1beta2" + kuadrantv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2" "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") -// reconcileIstioAuthorizationPolicies translates and reconciles `AuthRules` into an Istio AuthorizationPoilcy containing them. -func (r *AuthPolicyReconciler) reconcileIstioAuthorizationPolicies(ctx context.Context, ap *api.AuthPolicy, targetNetworkObject client.Object, gwDiffObj *reconcilers.GatewayDiffs) error { - if err := r.deleteIstioAuthorizationPolicies(ctx, ap, gwDiffObj); err != nil { - return err - } +// AuthPolicyIstioAuthorizationPolicyReconciler reconciles IstioAuthorizationPolicy objects for auth +type AuthPolicyIstioAuthorizationPolicyReconciler struct { + *reconcilers.BaseReconciler +} - logger, err := logr.FromContext(ctx) - if err != nil { - return err +//+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 } - // Create IstioAuthorizationPolicy for each gateway directly or indirectly referred by the policy (existing and new) - for _, gw := range append(gwDiffObj.GatewaysWithValidPolicyRef, gwDiffObj.GatewaysMissingPolicyRef...) { - iap, err := r.istioAuthorizationPolicy(ctx, ap, targetNetworkObject, gw) + if logger.V(1).Enabled() { + jsonData, err := json.MarshalIndent(gw, "", " ") if err != nil { - return err - } - if err := r.ReconcileResource(ctx, &istio.AuthorizationPolicy{}, iap, alwaysUpdateAuthPolicy); err != nil && !apierrors.IsAlreadyExists(err) { - logger.Error(err, "failed to reconcile IstioAuthorizationPolicy resource") - return err + return ctrl.Result{}, err } + logger.V(1).Info(string(jsonData)) } - return nil -} + if !kuadrant.IsKuadrantManaged(gw) { + return ctrl.Result{}, nil + } -// deleteIstioAuthorizationPolicies deletes IstioAuthorizationPolicies previously created for gateways no longer targeted by the policy (directly or indirectly) -func (r *AuthPolicyReconciler) deleteIstioAuthorizationPolicies(ctx context.Context, ap *api.AuthPolicy, gwDiffObj *reconcilers.GatewayDiffs) error { - logger, err := logr.FromContext(ctx) + topology, err := kuadranttools.TopologyFromGateway(ctx, r.Client(), gw, kuadrantv1beta2.NewAuthPolicyType()) if err != nil { - return err + return ctrl.Result{}, err } + topologyIndex := kuadrantgatewayapi.NewTopologyIndexes(topology) + policies := lo.FilterMap(topologyIndex.PoliciesFromGateway(gw), func(policy kuadrantgatewayapi.Policy, _ int) (*kuadrantv1beta2.AuthPolicy, bool) { + ap, ok := policy.(*kuadrantv1beta2.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 + } - for _, gw := range gwDiffObj.GatewaysWithInvalidPolicyRef { - listOptions := &client.ListOptions{LabelSelector: labels.SelectorFromSet(istioAuthorizationPolicyLabels(client.ObjectKeyFromObject(gw.Gateway), client.ObjectKeyFromObject(ap)))} - iapList := &istio.AuthorizationPolicyList{} - if err := r.Client().List(ctx, iapList, listOptions); err != nil { - return err + if policy.GetDeletionTimestamp() != nil { + utils.TagObjectToDelete(iap) } - for _, iap := range iapList.Items { - // it's OK to just go ahead and delete because we only create one IAP per target network object, - // and a network object can be targeted by no more than one AuthPolicy - if err := r.DeleteResource(ctx, iap); err != nil && !apierrors.IsNotFound(err) { - logger.Error(err, "failed to delete IstioAuthorizationPolicy") - return err - } + 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 nil + return ctrl.Result{}, nil } -func (r *AuthPolicyReconciler) istioAuthorizationPolicy(ctx context.Context, ap *api.AuthPolicy, targetNetworkObject client.Object, gw kuadrant.GatewayWrapper) (*istio.AuthorizationPolicy, error) { +func (r *AuthPolicyIstioAuthorizationPolicyReconciler) istioAuthorizationPolicy(ctx context.Context, gateway *gatewayapiv1.Gateway, ap *kuadrantv1beta2.AuthPolicy, topologyIndex *kuadrantgatewayapi.TopologyIndexes, topology *kuadrantgatewayapi.Topology) (*istiov1beta1.AuthorizationPolicy, error) { logger, _ := logr.FromContext(ctx) logger = logger.WithName("istioAuthorizationPolicy") - gateway := gw.Gateway - - iap := &istio.AuthorizationPolicy{ + iap := &istiov1beta1.AuthorizationPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: IstioAuthorizationPolicyName(gateway.Name, ap.GetTargetRef()), Namespace: gateway.Namespace, @@ -104,13 +120,14 @@ func (r *AuthPolicyReconciler) istioAuthorizationPolicy(ctx context.Context, ap }, } - var route *gatewayapiv1.HTTPRoute - - gwHostnames := gw.Hostnames() + 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: @@ -124,9 +141,9 @@ func (r *AuthPolicyReconciler) istioAuthorizationPolicy(ctx context.Context, ap // 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 := r.TargetRefReconciler.FetchAcceptedGatewayHTTPRoutes(ctx, obj) + routes := topology.Routes() for idx := range routes { - route := routes[idx] + route := routes[idx].Route() // skip routes that have an authpolicy of its own if route.GetAnnotations()[common.AuthPolicyBackRefAnnotation] != "" { continue @@ -166,9 +183,55 @@ func (r *AuthPolicyReconciler) istioAuthorizationPolicy(ctx context.Context, ap 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( + &kuadrantv1beta2.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 { @@ -193,7 +256,7 @@ func istioAuthorizationPolicyLabels(gwKey, apKey client.ObjectKey) map[string]st // These rules are the conditions that, when matched, will make the gateway to call external authorization. // If no rules are specified, the gateway will call external authorization for all requests. // If the route selectors specified in the policy do not match any route rules, an error is returned. -func istioAuthorizationPolicyRules(ap *api.AuthPolicy, route *gatewayapiv1.HTTPRoute) ([]*istiosecurity.Rule, error) { +func istioAuthorizationPolicyRules(ap *kuadrantv1beta2.AuthPolicy, route *gatewayapiv1.HTTPRoute) ([]*istiosecurity.Rule, error) { commonSpec := ap.Spec.CommonSpec() // use only the top level route selectors if defined if topLevelRouteSelectors := commonSpec.RouteSelectors; len(topLevelRouteSelectors) > 0 { @@ -204,7 +267,7 @@ func istioAuthorizationPolicyRules(ap *api.AuthPolicy, route *gatewayapiv1.HTTPR // istioAuthorizationPolicyRulesFromRouteSelectors builds a list of Istio AuthorizationPolicy rules from an HTTPRoute, // filtered to the HTTPRouteRules and hostnames selected by the route selectors. -func istioAuthorizationPolicyRulesFromRouteSelectors(route *gatewayapiv1.HTTPRoute, routeSelectors []api.RouteSelector) ([]*istiosecurity.Rule, error) { +func istioAuthorizationPolicyRulesFromRouteSelectors(route *gatewayapiv1.HTTPRoute, routeSelectors []kuadrantv1beta2.RouteSelector) ([]*istiosecurity.Rule, error) { istioRules := []*istiosecurity.Rule{} if len(routeSelectors) > 0 { @@ -230,7 +293,7 @@ func istioAuthorizationPolicyRulesFromRouteSelectors(route *gatewayapiv1.HTTPRou func istioAuthorizationPolicyRulesFromHTTPRoute(route *gatewayapiv1.HTTPRoute) []*istiosecurity.Rule { istioRules := []*istiosecurity.Rule{} - hostnamesForConditions := (&api.RouteSelector{}).HostnamesForConditions(route) + hostnamesForConditions := (&kuadrantv1beta2.RouteSelector{}).HostnamesForConditions(route) for _, rule := range route.Spec.Rules { istioRules = append(istioRules, istioAuthorizationPolicyRulesFromHTTPRouteRule(rule, hostnamesForConditions)...) } @@ -344,43 +407,3 @@ func istioAuthorizationPolicyRulesFromHTTPRouteRule(rule gatewayapiv1.HTTPRouteR } return } - -func alwaysUpdateAuthPolicy(existingObj, desiredObj client.Object) (bool, error) { - existing, ok := existingObj.(*istio.AuthorizationPolicy) - if !ok { - return false, fmt.Errorf("%T is not an *istio.AuthorizationPolicy", existingObj) - } - desired, ok := desiredObj.(*istio.AuthorizationPolicy) - if !ok { - return false, fmt.Errorf("%T is not an *istio.AuthorizationPolicy", desiredObj) - } - - var update bool - - if !reflect.DeepEqual(existing.Spec.Action, desired.Spec.Action) { - update = true - existing.Spec.Action = desired.Spec.Action - } - - if !reflect.DeepEqual(existing.Spec.ActionDetail, desired.Spec.ActionDetail) { - update = true - existing.Spec.ActionDetail = desired.Spec.ActionDetail - } - - if !reflect.DeepEqual(existing.Spec.Rules, desired.Spec.Rules) { - update = true - existing.Spec.Rules = desired.Spec.Rules - } - - if !reflect.DeepEqual(existing.Spec.Selector, desired.Spec.Selector) { - update = true - existing.Spec.Selector = desired.Spec.Selector - } - - if !reflect.DeepEqual(existing.Annotations, desired.Annotations) { - update = true - existing.Annotations = desired.Annotations - } - - return update, nil -} diff --git a/controllers/envoygateway_limitador_cluster_controller.go b/controllers/envoygateway_limitador_cluster_controller.go new file mode 100644 index 000000000..9d9eb7637 --- /dev/null +++ b/controllers/envoygateway_limitador_cluster_controller.go @@ -0,0 +1,224 @@ +package controllers + +import ( + "context" + "encoding/json" + "fmt" + + egv1alpha1 "github.com/envoyproxy/gateway/api/v1alpha1" + "github.com/go-logr/logr" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + limitadorv1alpha1 "github.com/kuadrant/limitador-operator/api/v1alpha1" + + kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" + "github.com/kuadrant/kuadrant-operator/pkg/common" + kuadrantenvoygateway "github.com/kuadrant/kuadrant-operator/pkg/envoygateway" + kuadrantgatewayapi "github.com/kuadrant/kuadrant-operator/pkg/library/gatewayapi" + "github.com/kuadrant/kuadrant-operator/pkg/library/reconcilers" +) + +// EnvoyGatewayLimitadorClusterReconciler reconciles an EnvoyGateway EnvoyPatchPolicy object +// to setup limitador's cluster on the gateway. It is a requirement for the wasm module to work. +// https://gateway.envoyproxy.io/latest/api/extension_types/#envoypatchpolicy +type EnvoyGatewayLimitadorClusterReconciler struct { + *reconcilers.BaseReconciler +} + +//+kubebuilder:rbac:groups=gateway.envoyproxy.io,resources=envoypatchpolicies,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=gateway.envoyproxy.io,resources=envoyextensionpolicies,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=gateways,verbs=get;list;watch;update;patch + +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.10.0/pkg/reconcile +func (r *EnvoyGatewayLimitadorClusterReconciler) Reconcile(eventCtx context.Context, req ctrl.Request) (ctrl.Result, error) { + logger := r.Logger().WithValues("envoyExtensionPolicy", req.NamespacedName) + logger.V(1).Info("Reconciling limitador cluster") + ctx := logr.NewContext(eventCtx, logger) + + extPolicy := &egv1alpha1.EnvoyExtensionPolicy{} + if err := r.Client().Get(ctx, req.NamespacedName, extPolicy); err != nil { + if apierrors.IsNotFound(err) { + logger.Info("no envoygateway extension policy object found") + return ctrl.Result{}, nil + } + logger.Error(err, "failed to get envoygateway extension policy object") + return ctrl.Result{}, err + } + + if logger.V(1).Enabled() { + jsonData, err := json.MarshalIndent(extPolicy.Spec.PolicyTargetReferences, "", " ") + if err != nil { + return ctrl.Result{}, err + } + logger.V(1).Info(string(jsonData)) + } + + if extPolicy.DeletionTimestamp != nil { + // no need to handle deletion + // ownerrefs will do the job + return ctrl.Result{}, nil + } + + // + // Get kuadrant + // + kuadrantList := &kuadrantv1beta1.KuadrantList{} + err := r.Client().List(ctx, kuadrantList) + if err != nil { + return ctrl.Result{}, err + } + if len(kuadrantList.Items) == 0 { + logger.Info("kuadrant object not found. Nothing to do") + return ctrl.Result{}, nil + } + + kObj := kuadrantList.Items[0] + + // + // Get limitador + // + limitadorKey := client.ObjectKey{Name: common.LimitadorName, Namespace: kObj.Namespace} + limitador := &limitadorv1alpha1.Limitador{} + err = r.Client().Get(ctx, limitadorKey, limitador) + logger.V(1).Info("read limitador", "key", limitadorKey, "err", err) + if err != nil { + if apierrors.IsNotFound(err) { + logger.Info("limitador object not found. Nothing to do") + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + + if !meta.IsStatusConditionTrue(limitador.Status.Conditions, "Ready") { + logger.Info("limitador status reports not ready. Retrying") + return ctrl.Result{Requeue: true}, nil + } + + limitadorClusterPatchPolicy, err := r.desiredLimitadorClusterPatchPolicy(extPolicy, limitador) + if err != nil { + return ctrl.Result{}, err + } + err = r.ReconcileResource(ctx, &egv1alpha1.EnvoyPatchPolicy{}, limitadorClusterPatchPolicy, reconcilers.CreateOnlyMutator) + if err != nil { + return ctrl.Result{}, err + } + + logger.V(1).Info("Envoygateway limitador cluster reconciled successfully") + + return ctrl.Result{}, nil +} + +func (r *EnvoyGatewayLimitadorClusterReconciler) desiredLimitadorClusterPatchPolicy( + extPolicy *egv1alpha1.EnvoyExtensionPolicy, + limitador *limitadorv1alpha1.Limitador) (*egv1alpha1.EnvoyPatchPolicy, error) { + patchPolicy := &egv1alpha1.EnvoyPatchPolicy{ + TypeMeta: metav1.TypeMeta{ + Kind: egv1alpha1.KindEnvoyPatchPolicy, + APIVersion: egv1alpha1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: LimitadorClusterEnvoyPatchPolicyName(extPolicy.GetName()), + Namespace: extPolicy.Namespace, + }, + Spec: egv1alpha1.EnvoyPatchPolicySpec{ + // Same target ref as the associated extension policy + TargetRef: extPolicy.Spec.PolicyTargetReferences.TargetRefs[0].LocalPolicyTargetReference, + Type: egv1alpha1.JSONPatchEnvoyPatchType, + JSONPatches: []egv1alpha1.EnvoyJSONPatchConfig{ + limitadorClusterPatch( + limitador.Status.Service.Host, + int(limitador.Status.Service.Ports.GRPC), + ), + }, + }, + } + + // controller reference + // patchPolicy has ownerref to extension policy + if err := r.SetOwnerReference(extPolicy, patchPolicy); err != nil { + return nil, err + } + + return patchPolicy, nil +} + +func LimitadorClusterEnvoyPatchPolicyName(targetName string) string { + return fmt.Sprintf("patch-for-%s", targetName) +} + +func limitadorClusterPatch(limitadorSvcHost string, limitadorGRPCPort int) egv1alpha1.EnvoyJSONPatchConfig { + // The patch defines the rate_limit_cluster, which provides the endpoint location of the external rate limit service. + // TODO(eguzki): Istio EnvoyFilter uses almost the same structure. DRY + patchUnstructured := map[string]any{ + "name": common.KuadrantRateLimitClusterName, + "type": "STRICT_DNS", + "connect_timeout": "1s", + "lb_policy": "ROUND_ROBIN", + "http2_protocol_options": map[string]any{}, + "load_assignment": map[string]any{ + "cluster_name": common.KuadrantRateLimitClusterName, + "endpoints": []map[string]any{ + { + "lb_endpoints": []map[string]any{ + { + "endpoint": map[string]any{ + "address": map[string]any{ + "socket_address": map[string]any{ + "address": limitadorSvcHost, + "port_value": limitadorGRPCPort, + }, + }, + }, + }, + }, + }, + }, + }, + } + + patchRaw, _ := json.Marshal(patchUnstructured) + value := &apiextensionsv1.JSON{} + value.UnmarshalJSON(patchRaw) + + return egv1alpha1.EnvoyJSONPatchConfig{ + Type: egv1alpha1.ClusterEnvoyResourceType, + Name: common.KuadrantRateLimitClusterName, + Operation: egv1alpha1.JSONPatchOperation{ + Op: egv1alpha1.JSONPatchOperationType("add"), + Path: "", + Value: value, + }, + } +} + +// SetupWithManager sets up the controller with the Manager. +func (r *EnvoyGatewayLimitadorClusterReconciler) SetupWithManager(mgr ctrl.Manager) error { + ok, err := kuadrantenvoygateway.IsEnvoyGatewayInstalled(mgr.GetRESTMapper()) + if err != nil { + return err + } + if !ok { + r.Logger().Info("EnvoyGateway limitador cluster 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 limitador cluster disabled. GatewayAPI was not found") + return nil + } + + return ctrl.NewControllerManagedBy(mgr). + For(&egv1alpha1.EnvoyExtensionPolicy{}). + Owns(&egv1alpha1.EnvoyPatchPolicy{}). + Complete(r) +} diff --git a/controllers/envoygateway_wasm_controller.go b/controllers/envoygateway_wasm_controller.go new file mode 100644 index 000000000..6c9a0133f --- /dev/null +++ b/controllers/envoygateway_wasm_controller.go @@ -0,0 +1,221 @@ +package controllers + +import ( + "context" + "fmt" + + egv1alpha1 "github.com/envoyproxy/gateway/api/v1alpha1" + "github.com/go-logr/logr" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "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" + gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + + kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" + kuadrantv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2" + 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" + "github.com/kuadrant/kuadrant-operator/pkg/rlptools" + "github.com/kuadrant/kuadrant-operator/pkg/rlptools/wasm" +) + +// EnvoyGatewayWasmReconciler reconciles an EnvoyGateway EnvoyExtensionPolicy object for the kuadrant's wasm module +type EnvoyGatewayWasmReconciler struct { + *reconcilers.BaseReconciler +} + +//+kubebuilder:rbac:groups=gateway.envoyproxy.io,resources=envoyextensionpolicies,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=gateways,verbs=get;list;watch;update;patch +//+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=httproutes,verbs=get;list;watch;update;patch +//+kubebuilder:rbac:groups=kuadrant.io,resources=ratelimitpolicies,verbs=get;list;watch;update;patch + +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.10.0/pkg/reconcile +func (r *EnvoyGatewayWasmReconciler) Reconcile(eventCtx context.Context, req ctrl.Request) (ctrl.Result, error) { + logger := r.Logger().WithValues("kuadrant", req.NamespacedName) + logger.V(1).Info("Reconciling envoygateway wasm attachment") + 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 + } + + rawTopology, err := kuadranttools.TopologyForPolicies(ctx, r.Client(), kuadrantv1beta2.NewRateLimitPolicyType()) + if err != nil { + return ctrl.Result{}, err + } + + for _, gw := range rawTopology.Gateways() { + topology, err := rlptools.ApplyOverrides(rawTopology, gw.GetGateway()) + if err != nil { + return ctrl.Result{}, err + } + envoyPolicy, err := r.desiredEnvoyExtensionPolicy(ctx, gw, kObj, topology) + if err != nil { + return ctrl.Result{}, err + } + err = r.ReconcileResource(ctx, &egv1alpha1.EnvoyExtensionPolicy{}, envoyPolicy, kuadrantenvoygateway.EnvoyExtensionPolicyMutator) + if err != nil { + return ctrl.Result{}, err + } + } + + logger.V(1).Info("Envoygateway wasm attachment reconciled successfully") + return ctrl.Result{}, nil +} + +func (r *EnvoyGatewayWasmReconciler) desiredEnvoyExtensionPolicy( + ctx context.Context, gw kuadrantgatewayapi.GatewayNode, + kObj *kuadrantv1beta1.Kuadrant, + topology *kuadrantgatewayapi.Topology) (*egv1alpha1.EnvoyExtensionPolicy, error) { + baseLogger, err := logr.FromContext(ctx) + if err != nil { + return nil, err + } + envoyPolicy := &egv1alpha1.EnvoyExtensionPolicy{ + TypeMeta: metav1.TypeMeta{ + Kind: egv1alpha1.KindEnvoyExtensionPolicy, + APIVersion: egv1alpha1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: EnvoyExtensionPolicyName(gw.GetName()), + Namespace: gw.GetNamespace(), + }, + Spec: egv1alpha1.EnvoyExtensionPolicySpec{ + PolicyTargetReferences: egv1alpha1.PolicyTargetReferences{ + TargetRefs: []gatewayapiv1alpha2.LocalPolicyTargetReferenceWithSectionName{ + { + LocalPolicyTargetReference: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.Group(gatewayapiv1.GroupVersion.Group), + Kind: gatewayapiv1.Kind("Gateway"), + Name: gatewayapiv1.ObjectName(gw.GetName()), + }, + }, + }, + }, + Wasm: []egv1alpha1.Wasm{ + { + Name: ptr.To("kuadrant-wasm-shim"), + RootID: ptr.To("kuadrant_wasm_shim"), + Code: egv1alpha1.WasmCodeSource{ + Type: egv1alpha1.ImageWasmCodeSourceType, + Image: &egv1alpha1.ImageWasmCodeSource{ + URL: WASMFilterImageURL, + }, + }, + Config: nil, + // When a fatal error accurs during the initialization or the execution of the + // Wasm extension, if FailOpen is set to false the system blocks the traffic and returns + // an HTTP 5xx error. + FailOpen: ptr.To(false), + }, + }, + }, + } + + logger := baseLogger.WithValues("envoyextensionpolicy", client.ObjectKeyFromObject(envoyPolicy)) + + config, err := wasm.ConfigForGateway(ctx, gw.GetGateway(), topology) + if err != nil { + return nil, err + } + + if config == nil || len(config.RateLimitPolicies) == 0 { + logger.V(1).Info("config is empty. EnvoyExtensionPolicy will be deleted if it exists") + utils.TagObjectToDelete(envoyPolicy) + return envoyPolicy, nil + } + + configJSON, err := config.ToJSON() + if err != nil { + return nil, err + } + + envoyPolicy.Spec.Wasm[0].Config = configJSON + + kuadrant.AnnotateObject(envoyPolicy, kObj.GetNamespace()) + + // controller reference + if err := r.SetOwnerReference(gw.GetGateway(), envoyPolicy); err != nil { + return nil, err + } + + return envoyPolicy, nil +} + +func EnvoyExtensionPolicyName(targetName string) string { + return fmt.Sprintf("kuadrant-wasm-for-%s", targetName) +} + +// SetupWithManager sets up the controller with the Manager. +func (r *EnvoyGatewayWasmReconciler) SetupWithManager(mgr ctrl.Manager) error { + ok, err := kuadrantenvoygateway.IsEnvoyGatewayInstalled(mgr.GetRESTMapper()) + if err != nil { + return err + } + if !ok { + r.Logger().Info("EnvoyGateway Wasm 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 Wasm controller disabled. GatewayAPI was not found") + return nil + } + + kuadrantListEventMapper := mappers.NewKuadrantListEventMapper( + mappers.WithLogger(r.Logger().WithName("envoyExtensionPolicyToKuadrantEventMapper")), + 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.EnvoyExtensionPolicy{}, + handler.EnqueueRequestsFromMapFunc(kuadrantListEventMapper.Map), + ). + Watches( + &kuadrantv1beta2.RateLimitPolicy{}, + handler.EnqueueRequestsFromMapFunc(policyToKuadrantEventMapper.Map), + ). + Watches( + &gatewayapiv1.Gateway{}, + handler.EnqueueRequestsFromMapFunc(gatewayToKuadrantEventMapper.Map), + ). + Watches( + &gatewayapiv1.HTTPRoute{}, + handler.EnqueueRequestsFromMapFunc(httpRouteToKuadrantEventMapper.Map), + ). + Complete(r) +} diff --git a/controllers/envoysecuritypolicy_referencegrant_controller.go b/controllers/envoysecuritypolicy_referencegrant_controller.go new file mode 100644 index 000000000..e0bcd3d01 --- /dev/null +++ b/controllers/envoysecuritypolicy_referencegrant_controller.go @@ -0,0 +1,166 @@ +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/kuadrant_status.go b/controllers/kuadrant_status.go index 7df6b4832..12dd42342 100644 --- a/controllers/kuadrant_status.go +++ b/controllers/kuadrant_status.go @@ -20,6 +20,7 @@ import ( kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" "github.com/kuadrant/kuadrant-operator/pkg/common" + kuadrantenvoygateway "github.com/kuadrant/kuadrant-operator/pkg/envoygateway" kuadrantistioutils "github.com/kuadrant/kuadrant-operator/pkg/istio" ) @@ -203,6 +204,7 @@ func (r *KuadrantReconciler) checkGatewayProviders() (*string, error) { anyProvider, err := anyProviderFunc([]func(restMapper meta.RESTMapper) (bool, error){ kuadrantistioutils.IsIstioInstalled, + kuadrantenvoygateway.IsEnvoyGatewayInstalled, }) if err != nil { diff --git a/controllers/rate_limiting_istio_wasmplugin_controller.go b/controllers/rate_limiting_istio_wasmplugin_controller.go index e4815d153..5c43f8738 100644 --- a/controllers/rate_limiting_istio_wasmplugin_controller.go +++ b/controllers/rate_limiting_istio_wasmplugin_controller.go @@ -20,7 +20,6 @@ import ( "context" "encoding/json" "fmt" - "sort" "github.com/go-logr/logr" "github.com/google/uuid" @@ -35,8 +34,8 @@ import ( gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" kuadrantv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2" - "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/mappers" "github.com/kuadrant/kuadrant-operator/pkg/library/reconcilers" @@ -155,131 +154,22 @@ func (r *RateLimitingIstioWASMPluginReconciler) desiredRateLimitingWASMPlugin(ct } func (r *RateLimitingIstioWASMPluginReconciler) wasmPluginConfig(ctx context.Context, gw *gatewayapiv1.Gateway) (*wasm.Config, error) { - logger, err := logr.FromContext(ctx) + rawTopology, err := kuadranttools.TopologyFromGateway(ctx, r.Client(), gw, kuadrantv1beta2.NewRateLimitPolicyType()) if err != nil { return nil, err } - config := &wasm.Config{ - FailureMode: wasm.FailureModeDeny, - RateLimitPolicies: make([]wasm.RateLimitPolicy, 0), - } - - t, err := rlptools.TopologyIndexesFromGateway(ctx, r.Client(), gw) + topology, err := rlptools.ApplyOverrides(rawTopology, gw) if err != nil { return nil, err } - rateLimitPolicies := t.PoliciesFromGateway(gw) - - logger.V(1).Info("wasmPluginConfig", "#RLPS", len(rateLimitPolicies)) - - // Sort RLPs for consistent comparison with existing objects - sort.Sort(kuadrantgatewayapi.PolicyByTargetRefKindAndCreationTimeStamp(rateLimitPolicies)) - - for _, policy := range rateLimitPolicies { - rlp := policy.(*kuadrantv1beta2.RateLimitPolicy) - wasmRLP, err := r.wasmRateLimitPolicy(ctx, t, rlp, gw) - if err != nil { - return nil, err - } - - if wasmRLP == nil { - // skip this RLP - continue - } - - config.RateLimitPolicies = append(config.RateLimitPolicies, *wasmRLP) - } - - return config, nil -} - -func (r *RateLimitingIstioWASMPluginReconciler) wasmRateLimitPolicy(ctx context.Context, t *kuadrantgatewayapi.TopologyIndexes, rlp *kuadrantv1beta2.RateLimitPolicy, gw *gatewayapiv1.Gateway) (*wasm.RateLimitPolicy, error) { - route, err := r.routeFromRLP(ctx, t, rlp, gw) + config, err := wasm.ConfigForGateway(ctx, gw, topology) if err != nil { return nil, err } - if route == nil { - // no need to add the policy if there are no routes; - // a rlp can return no rules if all its limits fail to match any route rule - // or targeting a gateway with no "free" routes. "free" meaning no route with policies targeting it - return nil, nil - } - - // narrow the list of hostnames specified in the route so we don't generate wasm rules that only apply to other gateways - // this is a no-op for the gateway rlp - gwHostnames := kuadrantgatewayapi.GatewayHostnames(gw) - if len(gwHostnames) == 0 { - gwHostnames = []gatewayapiv1.Hostname{"*"} - } - hostnames := kuadrantgatewayapi.FilterValidSubdomains(gwHostnames, route.Spec.Hostnames) - if len(hostnames) == 0 { // it should only happen when the route specifies no hostnames - hostnames = gwHostnames - } - // - // The route selectors logic rely on the "hostnames" field of the route object. - // However, routes effective hostname can be inherited from parent gateway, - // hence it depends on the context as multiple gateways can be targeted by a route - // The route selectors logic needs to be refactored - // or just deleted as soon as the HTTPRoute has name in the route object - // - routeWithEffectiveHostnames := route.DeepCopy() - routeWithEffectiveHostnames.Spec.Hostnames = hostnames - - rules := wasm.Rules(rlp, routeWithEffectiveHostnames) - if len(rules) == 0 { - // no need to add the policy if there are no rules; a rlp can return no rules if all its limits fail to match any route rule - return nil, nil - } - - return &wasm.RateLimitPolicy{ - Name: client.ObjectKeyFromObject(rlp).String(), - Domain: rlptools.LimitsNamespaceFromRLP(rlp), - Hostnames: utils.HostnamesToStrings(hostnames), // we might be listing more hostnames than needed due to route selectors hostnames possibly being more restrictive - Service: common.KuadrantRateLimitClusterName, - Rules: rules, - }, nil -} - -func (r *RateLimitingIstioWASMPluginReconciler) routeFromRLP(ctx context.Context, t *kuadrantgatewayapi.TopologyIndexes, rlp *kuadrantv1beta2.RateLimitPolicy, gw *gatewayapiv1.Gateway) (*gatewayapiv1.HTTPRoute, error) { - logger, err := logr.FromContext(ctx) - if err != nil { - return nil, err - } - - route := t.GetPolicyHTTPRoute(rlp) - - if route == nil { - // The policy is targeting a gateway - // This gateway policy will be enforced into all HTTPRoutes that do not have a policy attached to it - - // Build imaginary route with all the routes not having a RLP targeting it - untargetedRoutes := t.GetUntargetedRoutes(gw) - - if len(untargetedRoutes) == 0 { - // For policies targeting a gateway, when no httproutes is attached to the gateway, skip wasm config - // test wasm config when no http routes attached to the gateway - logger.V(1).Info("no untargeted httproutes attached to the targeted gateway, skipping wasm config for the gateway rlp", "ratelimitpolicy", client.ObjectKeyFromObject(rlp)) - return nil, nil - } - - untargetedRules := make([]gatewayapiv1.HTTPRouteRule, 0) - for idx := range untargetedRoutes { - untargetedRules = append(untargetedRules, untargetedRoutes[idx].Spec.Rules...) - } - gwHostnamesTmp := kuadrantgatewayapi.TargetHostnames(gw) - gwHostnames := utils.Map(gwHostnamesTmp, func(str string) gatewayapiv1.Hostname { return gatewayapiv1.Hostname(str) }) - route = &gatewayapiv1.HTTPRoute{ - Spec: gatewayapiv1.HTTPRouteSpec{ - Hostnames: gwHostnames, - Rules: untargetedRules, - }, - } - } - - return route, nil + return config, nil } // SetupWithManager sets up the controller with the Manager. diff --git a/controllers/test_common.go b/controllers/test_common.go index 5c04667cd..0819b2067 100644 --- a/controllers/test_common.go +++ b/controllers/test_common.go @@ -26,6 +26,7 @@ import ( "encoding/json" certmanv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + egv1alpha1 "github.com/envoyproxy/gateway/api/v1alpha1" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" istioclientgoextensionv1alpha1 "istio.io/client-go/pkg/apis/extensions/v1alpha1" @@ -40,6 +41,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" + gatewayapiv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" authorinoopapi "github.com/kuadrant/authorino-operator/api/v1beta1" authorinoapi "github.com/kuadrant/authorino/api/v1beta2" @@ -171,6 +173,18 @@ 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"), + mgr.GetEventRecorderFor("AuthPolicyIstioAuthorizationPolicy"), + ) + + err = (&AuthPolicyIstioAuthorizationPolicyReconciler{ + BaseReconciler: authPolicyIstioAuthorizationPolicyReconciler, + }).SetupWithManager(mgr) + + Expect(err).NotTo(HaveOccurred()) + targetStatusBaseReconciler := reconcilers.NewBaseReconciler( mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), log.Log.WithName("targetstatus"), @@ -194,6 +208,54 @@ 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"), + mgr.GetEventRecorderFor("AuthPolicyEnvoySecurityPolicy"), + ) + + 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"), + mgr.GetEventRecorderFor("EnvoySecurityPolicyReferenceGrant"), + ) + + err = (&EnvoySecurityPolicyReferenceGrantReconciler{ + BaseReconciler: envoySecurityPolicyReferenceGrantReconciler, + }).SetupWithManager(mgr) + + Expect(err).NotTo(HaveOccurred()) + + envoyGatewayWasmReconciler := reconcilers.NewBaseReconciler( + mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), + log.Log.WithName("envoyGatewayWasmReconciler"), + mgr.GetEventRecorderFor("EnvoyGatewayWasmReconciler"), + ) + + err = (&EnvoyGatewayWasmReconciler{ + BaseReconciler: envoyGatewayWasmReconciler, + }).SetupWithManager(mgr) + + Expect(err).NotTo(HaveOccurred()) + + envoyGatewayLimitadorClusterReconciler := reconcilers.NewBaseReconciler( + mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), + log.Log.WithName("envoyGatewayLimitadorClusterReconciler"), + mgr.GetEventRecorderFor("EnvoyGatewayLimitadorClusterReconciler"), + ) + + err = (&EnvoyGatewayLimitadorClusterReconciler{ + BaseReconciler: envoyGatewayLimitadorClusterReconciler, + }).SetupWithManager(mgr) + + Expect(err).NotTo(HaveOccurred()) + go func() { defer GinkgoRecover() err = mgr.Start(ctrl.SetupSignalHandler()) @@ -226,6 +288,7 @@ func BootstrapScheme() *runtime.Scheme { kuadrantv1beta1.AddToScheme, kuadrantv1beta2.AddToScheme, gatewayapiv1.Install, + gatewayapiv1beta1.Install, authorinoopapi.AddToScheme, authorinoapi.AddToScheme, istioapis.AddToScheme, @@ -236,6 +299,7 @@ func BootstrapScheme() *runtime.Scheme { istioclientgoextensionv1alpha1.AddToScheme, certmanv1.AddToScheme, maistraapis.AddToScheme, + egv1alpha1.AddToScheme, ) err := sb.AddToScheme(s) diff --git a/doc/development.md b/doc/development.md index 5d819ca54..229bf20c5 100644 --- a/doc/development.md +++ b/doc/development.md @@ -27,7 +27,7 @@ The `make local-setup` target accepts the following variables: | **Makefile Variable** | **Description** | **Default value** | | --- | --- |--- | -| `GATEWAYAPI_PROVIDER` | GatewayAPI provider name. Accepted values: [*istio*] | *istio* | +| `GATEWAYAPI_PROVIDER` | GatewayAPI provider name. Accepted values: [*istio* \| *envoygateway*] | *istio* | ## Run as a local process @@ -41,7 +41,7 @@ The `make local-env-setup` target accepts the following variables: | **Makefile Variable** | **Description** | **Default value** | | --- | --- |--- | -| `GATEWAYAPI_PROVIDER` | GatewayAPI provider name. Accepted values: [*istio*] | *istio* | +| `GATEWAYAPI_PROVIDER` | GatewayAPI provider name. Accepted values: [*istio* \| *envoygateway*] | *istio* | Then, run the operator locally @@ -54,12 +54,12 @@ make run **Requirements**: * Active session open to the kubernetes cluster. * GatewayAPI installed -* GatewayAPI provider installed. Currently only Istio supported. +* GatewayAPI provider installed. Currently only [Istio](https://istio.io/) and [EnvoyGateway](https://gateway.envoyproxy.io/) supported. * [Cert Manager](https://cert-manager.io/) installed Before running the kuadrant operator, some dependencies needs to be deployed. -``` +```sh make install make deploy-dependencies ``` @@ -246,7 +246,7 @@ Multiple controller integration tests are defined | --- | --- | --- | --- | | `github.com/kuadrant/kuadrant-operator/tests/bare_k8s` | no gateway provider, no GatewayAPI CRDs. Just Kuadrant API and Kuadrant dependencies. | `make local-k8s-env-setup` | `make test-bare-k8s-integration` | | `github.com/kuadrant/kuadrant-operator/tests/gatewayapi` | no gateway provider. GatewayAPI CRDs, Kuadrant API and Kuadrant dependencies. | `make local-gatewayapi-env-setup` | `make test-gatewayapi-env-integration` | -| `github.com/kuadrant/kuadrant-operator/controllers` | at least one gatewayapi provider. It can be any: istio, envoygateway, ... | `make local-env-setup GATEWAYAPI_PROVIDER=[istio]` (Default *istio*) | `make test-integration` | +| `github.com/kuadrant/kuadrant-operator/controllers` | at least one gatewayapi provider. It can be any: istio, envoygateway, ... | `make local-env-setup GATEWAYAPI_PROVIDER=[istio \| envoygateway]` (Default *istio*) | `make test-integration GATEWAYAPI_PROVIDER=[istio \| envoygateway]` (Default *istio*) | | `github.com/kuadrant/kuadrant-operator/tests/istio` | GatewayAPI CRDs, Istio, Kuadrant API and Kuadrant dependencies. | `make local-env-setup GATEWAYAPI_PROVIDER=istio` | `make test-istio-env-integration` | ### Lint tests diff --git a/doc/install/install-kubernetes.md b/doc/install/install-kubernetes.md index b8eda28c6..bc971eb06 100644 --- a/doc/install/install-kubernetes.md +++ b/doc/install/install-kubernetes.md @@ -31,7 +31,11 @@ kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/downloa curl -sL https://github.com/operator-framework/operator-lifecycle-manager/releases/download/v0.28.0/install.sh | bash -s v0.28.0 ``` -### Install Istio as a Gateway API provider +### (Optional) Install Istio as a Gateway API provider + +!!! note + + Skip this step if planing to use [Envoy Gateway](https://gateway.envoyproxy.io/) as Gateway API provider !!! note @@ -44,6 +48,36 @@ curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.21.4 sh - kubectl apply -f https://raw.githubusercontent.com/Kuadrant/kuadrant-operator/main/config/dependencies/istio/istio-operator.yaml ``` +### (Optional) Install Envoy Gateway as a Gateway API provider + +!!! note + + Skip this step if planing to use [Istio](https://istio.io/) as Gateway API provider + +!!! note + + There are several ways to install Envoy Gateway (via `egctl`, Helm chart or Kubernetes yaml) - this is just an example for starting from a bare Kubernetes cluster. + +```bash +helm install eg oci://docker.io/envoyproxy/gateway-helm --version v1.1.0 -n envoy-gateway-system --create-namespace +``` + +Kuadrant relies on the Envoy Gateway patch policy feature to function correctly - enable the *EnvoyPatchPolicy* feature like so: + +```bash +TMP=$(mktemp -d) +kubectl get configmap -n envoy-gateway-system envoy-gateway-config -o jsonpath='{.data.envoy-gateway\.yaml}' > ${TMP}/envoy-gateway.yaml +yq e '.extensionApis.enableEnvoyPatchPolicy = true' -i ${TMP}/envoy-gateway.yaml +kubectl create configmap -n envoy-gateway-system envoy-gateway-config --from-file=envoy-gateway.yaml=${TMP}/envoy-gateway.yaml -o yaml --dry-run=client | kubectl replace -f - +kubectl rollout restart deployment envoy-gateway -n envoy-gateway-system +``` + +Wait for Envoy Gateway to become available: + +```bash +kubectl wait --timeout=5m -n envoy-gateway-system deployment/envoy-gateway --for=condition=Available +``` + ### Install Kuadrant ```bash @@ -106,7 +140,7 @@ Follow these steps to create the necessary secret: ```bash # Replace this with an accessible Redis cluster URL export REDIS_URL=redis://user:xxxxxx@some-redis.com:6379 - + kubectl -n kuadrant-system create secret generic redis-config \ --from-literal=URL=$REDIS_URL ``` @@ -128,7 +162,7 @@ spec: storage: redis-cached: configSecretRef: - name: redis-config + name: redis-config EOF ``` diff --git a/doc/install/install-openshift.md b/doc/install/install-openshift.md index 966870546..7117f0462 100644 --- a/doc/install/install-openshift.md +++ b/doc/install/install-openshift.md @@ -48,7 +48,11 @@ More installation options at [cert-manager.io](https://cert-manager.io/docs/inst You can install the [cert-manager Operator for Red Hat OpenShift](https://docs.openshift.com/container-platform/4.16/security/cert_manager_operator/cert-manager-operator-install.html) by using the web console. -### Step 4 - Install and configure Istio with the Sail Operator +### Step 4 - (Optional) Install and configure Istio with the Sail Operator + +!!! note + + Skip this step if planing to use [Envoy Gateway](https://gateway.envoyproxy.io/) as Gateway API provider Kuadrant integrates with Istio as a Gateway API provider. You can set up an Istio-based Gateway API provider by using the Sail Operator. @@ -57,7 +61,7 @@ Kuadrant integrates with Istio as a Gateway API provider. You can set up an Isti To install the Istio Gateway provider, run the following commands: ```bash -kubectl create ns istio-system +kubectl create ns gateway-system ``` ```bash @@ -66,7 +70,7 @@ kind: OperatorGroup apiVersion: operators.coreos.com/v1 metadata: name: sail - namespace: istio-system + namespace: gateway-system spec: upgradeStrategy: Default --- @@ -74,7 +78,7 @@ apiVersion: operators.coreos.com/v1alpha1 kind: Subscription metadata: name: sailoperator - namespace: istio-system + namespace: gateway-system spec: channel: 3.0-dp1 installPlanApproval: Automatic @@ -87,7 +91,7 @@ EOF Check the status of the installation as follows: ```bash -kubectl get installplan -n istio-system -o=jsonpath='{.items[0].status.phase}' +kubectl get installplan -n gateway-system -o=jsonpath='{.items[0].status.phase}' ``` When ready, the status will change from `installing` to `complete`. @@ -104,7 +108,7 @@ metadata: name: default spec: version: v1.21.0 - namespace: istio-system + namespace: gateway-system # Disable autoscaling to reduce dev resources values: pilot: @@ -115,10 +119,40 @@ EOF Wait for Istio to be ready as follows: ```bash -kubectl wait istio/default -n istio-system --for="condition=Ready=true" +kubectl wait istio/default -n gateway-system --for="condition=Ready=true" +``` + +### Step 5 - (Optional) Install Envoy Gateway as a Gateway API provider + +!!! note + + Skip this step if planing to use [Istio](https://istio.io/) as Gateway API provider + +!!! note + + There are several ways to install Envoy Gateway (via `egctl`, Helm chart or Kubernetes yaml) - this is just an example for starting from a bare Kubernetes cluster. + +```bash +helm install eg oci://docker.io/envoyproxy/gateway-helm --version v1.1.0 -n envoy-gateway-system --create-namespace +``` + +Enable *EnvoyPatchPolicy* feature: + +```bash +TMP=$(mktemp -d) +kubectl get configmap -n envoy-gateway-system envoy-gateway-config -o jsonpath='{.data.envoy-gateway\.yaml}' > ${TMP}/envoy-gateway.yaml +yq e '.extensionApis.enableEnvoyPatchPolicy = true' -i ${TMP}/envoy-gateway.yaml +kubectl create configmap -n envoy-gateway-system envoy-gateway-config --from-file=envoy-gateway.yaml=${TMP}/envoy-gateway.yaml -o yaml --dry-run=client | kubectl replace -f - +kubectl rollout restart deployment envoy-gateway -n envoy-gateway-system +``` + +Wait for Envoy Gateway to become available:: + +```bash +kubectl wait --timeout=5m -n envoy-gateway-system deployment/envoy-gateway --for=condition=Available ``` -### Step 5 - Optional: Configure observability and metrics +### Step 6 - Optional: Configure observability and metrics Kuadrant provides a set of example dashboards that use known metrics exported by Kuadrant and Gateway components to provide insight into different components of your APIs and Gateways. While not essential, it is best to set up an OpenShift monitoring stack. This section provides links to OpenShift and Thanos documentation on configuring monitoring and metrics storage. @@ -129,7 +163,7 @@ If you have user workload monitoring enabled, it is best to configure remote wri - [OpenShift remote write configuration](https://docs.openshift.com/container-platform/latest/observability/monitoring/configuring-the-monitoring-stack.html#configuring_remote_write_storage_configuring-the-monitoring-stack) - [Kube Thanos](https://github.com/thanos-io/kube-thanos) -The [example dashboards and alerts](https://docs.kuadrant.io/latest/kuadrant-operator/doc/observability/examples/) for observing Kuadrant functionality use low-level CPU metrics and network metrics available from the user monitoring stack in OpenShift. They also use resource state metrics from Gateway API and Kuadrant resources. +The [example dashboards and alerts](https://docs.kuadrant.io/latest/kuadrant-operator/doc/observability/examples/) for observing Kuadrant functionality use low-level CPU metrics and network metrics available from the user monitoring stack in OpenShift. They also use resource state metrics from Gateway API and Kuadrant resources. To scrape these additional metrics, you can install a `kube-state-metrics instance`, with a custom resource configuration as follows: @@ -149,7 +183,7 @@ If you have Grafana installed in your cluster, you can import the [example dashb For example installation details, see [installing Grafana on OpenShift](https://cloud.redhat.com/experts/o11y/ocp-grafana/). When installed, you must add your Thanos instance as a data source to Grafana. Alternatively, if you are using only the user workload monitoring stack in your OpenShift cluster, and not writing metrics to an external Thanos instance, you can [set up a data source to the thanos-querier route in the OpenShift cluster](https://docs.openshift.com/container-platform/4.15/observability/monitoring/accessing-third-party-monitoring-apis.html#accessing-metrics-from-outside-cluster_accessing-monitoring-apis-by-using-the-cli). -### Step 6 - Create secrets for your credentials +### Step 7 - Create secrets for your credentials Before installing the Kuadrant Operator, you must enter the following commands to set up secrets that you will use later: @@ -212,7 +246,7 @@ kubectl -n ingress-gateway create secret generic aws-credentials \ --from-literal=AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY ``` -### Step 7 - Install the Kuadrant Operator +### Step 8 - Install the Kuadrant Operator To install the Kuadrant Operator, enter the following command: @@ -248,7 +282,7 @@ kubectl get installplan -n kuadrant-system -o=jsonpath='{.items[0].status.phase} After some time, this command should return `complete`. -### Step 8 - Configure Kuadrant +### Step 9 - Configure Kuadrant To configure your Kuadrant deployment, enter the following command: diff --git a/doc/observability/metrics.md b/doc/observability/metrics.md index d054896c8..d2075eaab 100644 --- a/doc/observability/metrics.md +++ b/doc/observability/metrics.md @@ -47,7 +47,7 @@ apiVersion: telemetry.istio.io/v1alpha1 kind: Telemetry metadata: name: namespace-metrics - namespace: istio-system + namespace: gateway-system spec: metrics: - providers: diff --git a/doc/observability/tracing.md b/doc/observability/tracing.md index 16ecd1184..c04a82c45 100644 --- a/doc/observability/tracing.md +++ b/doc/observability/tracing.md @@ -22,7 +22,7 @@ apiVersion: telemetry.istio.io/v1alpha1 kind: Telemetry metadata: name: mesh-default - namespace: istio-system + namespace: gateway-system spec: tracing: - providers: @@ -34,7 +34,7 @@ kind: Istio metadata: name: default spec: - namespace: istio-system + namespace: gateway-system values: meshConfig: defaultConfig: diff --git a/doc/rate-limiting.md b/doc/rate-limiting.md index 04a8074f6..5e8432219 100644 --- a/doc/rate-limiting.md +++ b/doc/rate-limiting.md @@ -391,19 +391,19 @@ A Kuadrant wasm-shim configuration for 2 RateLimitPolicy custom resources (a Gat apiVersion: extensions.istio.io/v1alpha1 kind: WasmPlugin metadata: - name: kuadrant-istio-ingressgateway - namespace: istio-system + name: kuadrant-kuadrant-ingressgateway + namespace: gateway-system … spec: phase: STATS pluginConfig: failureMode: deny rateLimitPolicies: - - domain: istio-system/gw-rlp # allows isolating policy rules and improve performance of the rate limit service + - domain: gateway-system/gw-rlp # allows isolating policy rules and improve performance of the rate limit service hostnames: - '*.website' - '*.io' - name: istio-system/gw-rlp + name: gateway-system/gw-rlp rules: # match rules from the gateway and according to conditions specified in the policy - conditions: - allOf: @@ -478,6 +478,6 @@ spec: service: kuadrant-rate-limiting-service selector: matchLabels: - istio.io/gateway-name: istio-ingressgateway + istio.io/gateway-name: kuadrant-ingressgateway url: oci://quay.io/kuadrant/wasm-shim:v0.3.0 ``` diff --git a/doc/user-guides/auth-for-app-devs-and-platform-engineers.md b/doc/user-guides/auth-for-app-devs-and-platform-engineers.md index 8fdbf2b4b..a8e582acf 100644 --- a/doc/user-guides/auth-for-app-devs-and-platform-engineers.md +++ b/doc/user-guides/auth-for-app-devs-and-platform-engineers.md @@ -7,7 +7,7 @@ Two AuthPolicies will be declared: | Use case | AuthPolicy | |--------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **App developer** | 1 AuthPolicy targeting a HTTPRoute that routes traffic to a sample Toy Store application, and enforces API key authentication to all requests in this route, as well as requires API key owners to be mapped to `groups:admins` metadata to access a specific HTTPRouteRule of the route. | -| **Platform engineer use-case** | 1 AuthPolicy targeting the `istio-ingressgateway` Gateway that enforces a trivial "deny-all" policy that locks down any other HTTPRoute attached to the Gateway. | +| **Platform engineer use-case** | 1 AuthPolicy targeting the `kuadrant-ingressgateway` Gateway that enforces a trivial "deny-all" policy that locks down any other HTTPRoute attached to the Gateway. | Topology: @@ -18,19 +18,19 @@ Topology: └───────┬───────┘ │ ▼ - ┌──────────────────────┐ - │ (Gateway) │ - │ istio-ingressgateway │ - ┌────►│ │◄───┐ - │ │ * │ │ - │ └──────────────────────┘ │ - │ │ - ┌────────┴─────────┐ ┌────────┴─────────┐ - │ (HTTPRoute) │ │ (HTTPRoute) │ - │ toystore │ │ other │ - │ │ │ │ - │ api.toystore.com │ │ *.other-apps.com │ - └──────────────────┘ └──────────────────┘ + ┌─────────────────────────┐ + │ (Gateway) │ + │ kuadrant-ingressgateway │ + ┌────►│ │◄───┐ + │ │ * │ │ + │ └─────────────────────────┘ │ + │ │ + ┌────────┴─────────┐ ┌────────┴─────────┐ + │ (HTTPRoute) │ │ (HTTPRoute) │ + │ toystore │ │ other │ + │ │ │ │ + │ api.toystore.com │ │ *.other-apps.com │ + └──────────────────┘ └──────────────────┘ ▲ │ ┌───────┴───────┐ @@ -83,8 +83,8 @@ metadata: name: toystore spec: parentRefs: - - name: istio-ingressgateway - namespace: istio-system + - name: kuadrant-ingressgateway + namespace: gateway-system hostnames: - api.toystore.com rules: @@ -113,8 +113,8 @@ EOF Export the gateway hostname and port: ```sh -export INGRESS_HOST=$(kubectl get gtw istio-ingressgateway -n istio-system -o jsonpath='{.status.addresses[0].value}') -export INGRESS_PORT=$(kubectl get gtw istio-ingressgateway -n istio-system -o jsonpath='{.spec.listeners[?(@.name=="http")].port}') +export INGRESS_HOST=$(kubectl get gtw kuadrant-ingressgateway -n gateway-system -o jsonpath='{.status.addresses[0].value}') +export INGRESS_PORT=$(kubectl get gtw kuadrant-ingressgateway -n gateway-system -o jsonpath='{.spec.listeners[?(@.name=="http")].port}') export GATEWAY_URL=$INGRESS_HOST:$INGRESS_PORT ``` @@ -231,7 +231,7 @@ curl -H 'Host: api.toystore.com' -H 'Authorization: APIKEY iamanadmin' http://$G Create the policy: ```sh -kubectl -n istio-system apply -f - < **Note**: If the command above fails to hit the Toy Store API on your environment, try forwarding requests to the service and accessing over localhost: > > ```sh -> kubectl port-forward -n istio-system service/istio-ingressgateway-istio 9080:80 >/dev/null 2>&1 & +> kubectl port-forward -n gateway-system service/kuadrant-ingressgateway-istio 9080:80 >/dev/null 2>&1 & > export GATEWAY_URL=localhost:9080 > ``` > ```sh diff --git a/doc/user-guides/authenticated-rl-with-jwt-and-k8s-authnz.md b/doc/user-guides/authenticated-rl-with-jwt-and-k8s-authnz.md index 9c39c508f..7646cfed1 100644 --- a/doc/user-guides/authenticated-rl-with-jwt-and-k8s-authnz.md +++ b/doc/user-guides/authenticated-rl-with-jwt-and-k8s-authnz.md @@ -85,8 +85,8 @@ kubectl apply -f examples/toystore/httproute.yaml Export the gateway hostname and port: ```sh -export INGRESS_HOST=$(kubectl get gtw istio-ingressgateway -n istio-system -o jsonpath='{.status.addresses[0].value}') -export INGRESS_PORT=$(kubectl get gtw istio-ingressgateway -n istio-system -o jsonpath='{.spec.listeners[?(@.name=="http")].port}') +export INGRESS_HOST=$(kubectl get gtw kuadrant-ingressgateway -n gateway-system -o jsonpath='{.status.addresses[0].value}') +export INGRESS_PORT=$(kubectl get gtw kuadrant-ingressgateway -n gateway-system -o jsonpath='{.spec.listeners[?(@.name=="http")].port}') export GATEWAY_URL=$INGRESS_HOST:$INGRESS_PORT ``` @@ -100,7 +100,7 @@ It should return `200 OK`. > **Note**: If the command above fails to hit the Toy Store API on your environment, try forwarding requests to the service and accessing over localhost: > > ```sh -> kubectl port-forward -n istio-system service/istio-ingressgateway-istio 9080:80 >/dev/null 2>&1 & +> kubectl port-forward -n gateway-system service/kuadrant-ingressgateway-istio 9080:80 >/dev/null 2>&1 & > export GATEWAY_URL=localhost:9080 > ``` > ```sh diff --git a/doc/user-guides/gateway-rl-for-cluster-operators.md b/doc/user-guides/gateway-rl-for-cluster-operators.md index c3edc398a..e112fa10d 100644 --- a/doc/user-guides/gateway-rl-for-cluster-operators.md +++ b/doc/user-guides/gateway-rl-for-cluster-operators.md @@ -40,7 +40,7 @@ EOF ### ② Create the ingress gateways ```sh -kubectl -n istio-system apply -f - </dev/null 2>&1 & -kubectl port-forward -n istio-system service/internal-istio 9082:80 >/dev/null 2>&1 & +kubectl port-forward -n gateway-system service/external-istio 9081:80 >/dev/null 2>&1 & +kubectl port-forward -n gateway-system service/internal-istio 9082:80 >/dev/null 2>&1 & ``` Up to 5 successful (`200 OK`) requests every 10 seconds through the `external` ingress gateway (`*.io`), then `429 Too Many Requests`: diff --git a/doc/user-guides/simple-rl-for-app-developers.md b/doc/user-guides/simple-rl-for-app-developers.md index 4f151f238..a978320fb 100644 --- a/doc/user-guides/simple-rl-for-app-developers.md +++ b/doc/user-guides/simple-rl-for-app-developers.md @@ -4,7 +4,7 @@ This user guide walks you through an example of how to configure rate limiting f
-In this guide, we will rate limit a sample REST API called **Toy Store**. In reality, this API is just an echo service that echoes back to the user whatever attributes it gets in the request. The API listens to requests at the hostname `api.toystore.com`, where it exposes the endpoints `GET /toys*` and `POST /toys`, respectively, to mimic a operations of reading and writing toy records. +In this guide, we will rate limit a sample REST API called **Toy Store**. In reality, this API is just an echo service that echoes back to the user whatever attributes it gets in the request. The API listens to requests at the hostname `api.toystore.com`, where it exposes the endpoints `GET /toys*` and `POST /toys`, respectively, to mimic operations of reading and writing toy records. We will rate limit the `POST /toys` endpoint to a maximum of 5rp10s ("5 requests every 10 seconds"). @@ -63,8 +63,8 @@ metadata: name: toystore spec: parentRefs: - - name: istio-ingressgateway - namespace: istio-system + - name: kuadrant-ingressgateway + namespace: gateway-system hostnames: - api.toystore.com rules: @@ -90,8 +90,8 @@ EOF Export the gateway hostname and port: ```sh -export INGRESS_HOST=$(kubectl get gtw istio-ingressgateway -n istio-system -o jsonpath='{.status.addresses[0].value}') -export INGRESS_PORT=$(kubectl get gtw istio-ingressgateway -n istio-system -o jsonpath='{.spec.listeners[?(@.name=="http")].port}') +export INGRESS_HOST=$(kubectl get gtw kuadrant-ingressgateway -n gateway-system -o jsonpath='{.status.addresses[0].value}') +export INGRESS_PORT=$(kubectl get gtw kuadrant-ingressgateway -n gateway-system -o jsonpath='{.spec.listeners[?(@.name=="http")].port}') export GATEWAY_URL=$INGRESS_HOST:$INGRESS_PORT ``` @@ -105,7 +105,7 @@ curl -H 'Host: api.toystore.com' http://$GATEWAY_URL/toys -i > **Note**: If the command above fails to hit the Toy Store API on your environment, try forwarding requests to the service and accessing over localhost: > > ```sh -> kubectl port-forward -n istio-system service/istio-ingressgateway-istio 9080:80 >/dev/null 2>&1 & +> kubectl port-forward -n gateway-system service/kuadrant-ingressgateway-istio 9080:80 >/dev/null 2>&1 & > export GATEWAY_URL=localhost:9080 > ``` > ```sh diff --git a/examples/toystore/authpolicy.yaml b/examples/toystore/authpolicy.yaml index a7a404b71..cf646b48d 100644 --- a/examples/toystore/authpolicy.yaml +++ b/examples/toystore/authpolicy.yaml @@ -37,12 +37,12 @@ apiVersion: kuadrant.io/v1beta1 kind: AuthPolicy metadata: name: toystore - namespace: istio-system + namespace: gateway-system spec: targetRef: group: gateway.networking.k8s.io kind: Gateway - name: istio-ingressgateway + name: kuadrant-ingressgateway rules: authentication: "apikey": diff --git a/examples/toystore/httproute.yaml b/examples/toystore/httproute.yaml index f44f9f9e5..941e2db4b 100644 --- a/examples/toystore/httproute.yaml +++ b/examples/toystore/httproute.yaml @@ -7,8 +7,8 @@ metadata: app: toystore spec: parentRefs: - - name: istio-ingressgateway - namespace: istio-system + - name: kuadrant-ingressgateway + namespace: gateway-system hostnames: ["*.toystore.com"] rules: - matches: diff --git a/examples/toystore/ratelimitpolicy_gateway.yaml b/examples/toystore/ratelimitpolicy_gateway.yaml index 57b53002b..b7cabca1d 100644 --- a/examples/toystore/ratelimitpolicy_gateway.yaml +++ b/examples/toystore/ratelimitpolicy_gateway.yaml @@ -2,12 +2,12 @@ apiVersion: kuadrant.io/v1beta2 kind: RateLimitPolicy metadata: name: toystore-gw - namespace: istio-system + namespace: gateway-system spec: targetRef: group: gateway.networking.k8s.io kind: Gateway - name: istio-ingressgateway + name: kuadrant-ingressgateway limits: "expensive-operation": rates: diff --git a/go.mod b/go.mod index 666b5c5dd..09d5c0fac 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.22.5 require ( github.com/cert-manager/cert-manager v1.12.1 github.com/elliotchance/orderedmap/v2 v2.2.0 + github.com/envoyproxy/gateway v1.1.0 github.com/go-logr/logr v1.4.2 github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.6.0 @@ -18,17 +19,17 @@ require ( github.com/onsi/gomega v1.33.1 github.com/prometheus/client_golang v1.19.1 github.com/samber/lo v1.39.0 - go.uber.org/zap v1.26.0 + go.uber.org/zap v1.27.0 google.golang.org/protobuf v1.34.2 gotest.tools v2.2.0+incompatible - istio.io/api v1.20.0 - istio.io/client-go v1.20.0 - istio.io/istio v0.0.0-20240208010324-ffed2074bd92 + istio.io/api v1.22.3-0.20240703105953-437a88321a16 + istio.io/client-go v1.22.3-0.20240703110620-5f69a1e4c030 + istio.io/istio v0.0.0-20240709015522-1e0dc8dd8809 k8s.io/api v0.30.2 - k8s.io/apiextensions-apiserver v0.30.1 + k8s.io/apiextensions-apiserver v0.30.2 k8s.io/apimachinery v0.30.2 k8s.io/client-go v0.30.2 - k8s.io/klog/v2 v2.120.1 + k8s.io/klog/v2 v2.130.1 k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 maistra.io/istio-operator v0.0.0-20240217080932-98753cb28cd7 sigs.k8s.io/controller-runtime v0.18.4 @@ -39,35 +40,35 @@ require ( require ( github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect - github.com/BurntSushi/toml v1.3.2 // indirect + github.com/BurntSushi/toml v1.4.0 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/Masterminds/squirrel v1.5.4 // indirect - github.com/Microsoft/hcsshim v0.11.4 // indirect + github.com/Microsoft/hcsshim v0.12.3 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/chai2010/gettext-go v1.0.2 // indirect - github.com/containerd/containerd v1.7.12 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/chai2010/gettext-go v1.0.3 // indirect + github.com/containerd/containerd v1.7.17 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/cyphar/filepath-securejoin v0.2.4 // indirect + github.com/cyphar/filepath-securejoin v0.2.5 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/cli v25.0.1+incompatible // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/cli v27.0.3+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect - github.com/docker/docker v24.0.9+incompatible // indirect - github.com/docker/docker-credential-helpers v0.8.1 // indirect - github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/docker v27.0.3+incompatible // indirect + github.com/docker/docker-credential-helpers v0.8.2 // indirect + github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect - github.com/docker/go-units v0.5.0 // indirect github.com/emicklei/dot v1.6.2 // indirect github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/evanphx/json-patch v5.9.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect - github.com/fatih/color v1.16.0 // indirect + github.com/fatih/color v1.17.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-errors/errors v1.5.1 // indirect @@ -93,13 +94,14 @@ require ( github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/golang-lru/arc/v2 v2.0.7 // indirect github.com/huandu/xstrings v1.4.0 // indirect github.com/imdario/mergo v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmoiron/sqlx v1.3.5 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.17.4 // indirect + github.com/klauspost/compress v1.17.8 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/lib/pq v1.10.9 // indirect @@ -117,23 +119,24 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect - github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0-rc5 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.48.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect - github.com/rivo/uniseg v0.4.6 // indirect - github.com/rubenv/sql-migrate v1.5.2 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/redis/go-redis/extra/redisotel/v9 v9.5.3 // indirect + github.com/redis/go-redis/v9 v9.6.1 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/rubenv/sql-migrate v1.6.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/shopspring/decimal v1.3.1 // indirect + github.com/shopspring/decimal v1.4.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/cast v1.6.0 // indirect - github.com/spf13/cobra v1.8.0 // indirect + github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/telepresenceio/watchable v0.0.0-20220726211108-9bb86f92afa7 // indirect github.com/tidwall/gjson v1.14.0 // indirect @@ -143,41 +146,42 @@ require ( github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xlab/treeprint v1.2.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 // indirect - go.opentelemetry.io/otel v1.22.0 // indirect - go.opentelemetry.io/otel/metric v1.22.0 // indirect - go.opentelemetry.io/otel/trace v1.22.0 // indirect - go.starlark.net v0.0.0-20231121155337-90ade8b19d09 // indirect + go.opentelemetry.io/contrib/exporters/autoexport v0.53.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect + go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel/metric v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect + go.starlark.net v0.0.0-20240520160348-046347dcd104 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.24.0 // indirect + golang.org/x/crypto v0.25.0 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect - golang.org/x/net v0.26.0 // indirect + golang.org/x/net v0.27.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.22.0 // indirect - golang.org/x/term v0.21.0 // indirect + golang.org/x/term v0.22.0 // indirect golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.22.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf // indirect - google.golang.org/grpc v1.63.2 // indirect + google.golang.org/grpc v1.65.0 // indirect + gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - helm.sh/helm/v3 v3.14.3 // indirect - k8s.io/apiserver v0.30.1 // indirect - k8s.io/cli-runtime v0.29.1 // indirect - k8s.io/component-base v0.30.1 // indirect + helm.sh/helm/v3 v3.15.3 // indirect + k8s.io/apiserver v0.30.2 // indirect + k8s.io/cli-runtime v0.30.2 // indirect + k8s.io/component-base v0.30.2 // indirect k8s.io/kube-openapi v0.0.0-20240521193020-835d969ad83a // indirect - k8s.io/kubectl v0.29.1 // indirect - oras.land/oras-go v1.2.4 // indirect + k8s.io/kubectl v0.30.2 // indirect + oras.land/oras-go v1.2.5 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect - sigs.k8s.io/kustomize/kyaml v0.16.0 // indirect + sigs.k8s.io/kustomize/api v0.17.2 // indirect + sigs.k8s.io/kustomize/kyaml v0.17.1 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index 8e0b3b85f..df6e89a15 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,15 @@ +cel.dev/expr v0.15.0 h1:O1jzfJCQBfL5BFoYktaxwIhuttaQPsVWerH9/EEKx0w= +cel.dev/expr v0.15.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg= dario.cat/mergo v0.3.5 h1:rybKppoxBoyv1JiXjzlqE4gdrhB0Xk/us0OW7yDEAl0= dario.cat/mergo v0.3.5/go.mod h1:fvkCdyGtdx6UQvuEimZ9mB2dzc2AymrLoRgHC4lz6ec= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= @@ -19,17 +23,14 @@ github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= -github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= -github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/Microsoft/hcsshim v0.12.3 h1:LS9NXqXhMoqNCplK1ApmVSfB4UnVLRDWRapB6EIlxE0= +github.com/Microsoft/hcsshim v0.12.3/go.mod h1:Iyl1WVpZzr+UkzjekHZbV8o5Z9ZkxNGx6CtY2Qg/JVQ= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves= -github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df h1:7RFfzj4SSt6nnvCPbCqijJi1nWCd+TqAT3bYCStRC18= -github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= +github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= +github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= @@ -38,37 +39,40 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= -github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng= -github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= -github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ= -github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= -github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= -github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cert-manager/cert-manager v1.12.1 h1:QA8/diGdInzBRhqiyTITPC+wI9FaXbgOAAT3Dwe9KZE= github.com/cert-manager/cert-manager v1.12.1/go.mod h1:ql0msU88JCcQSceN+PFjEY8U+AMe13y06vO2klJk8bs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= -github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= -github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ= -github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chai2010/gettext-go v1.0.3 h1:9liNh8t+u26xl5ddmWLmsOsdNLwkdRTg5AG+JnTiM80= +github.com/chai2010/gettext-go v1.0.3/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= +github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b h1:ga8SEFjZ60pxLcmhnThWgvH2wg8376yUJmPhEH4H3kw= +github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= -github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= -github.com/containerd/containerd v1.7.12 h1:+KQsnv4VnzyxWcfO9mlxxELaoztsDEjOuCMPAuPqgU0= -github.com/containerd/containerd v1.7.12/go.mod h1:/5OMpE1p0ylxtEUGY8kuCYkDRzJm9NO1TFMWjUpdevk= +github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0= +github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0= +github.com/containerd/containerd v1.7.17 h1:KjNnn0+tAVQHAoaWRjmdak9WlvnFR/8rU1CHHy8Rm2A= +github.com/containerd/containerd v1.7.17/go.mod h1:vK+hhT4TIv2uejlcDlbVIc8+h/BqtKLIyNrtCZol8lI= github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM= github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= +github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM= +github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= -github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/cyphar/filepath-securejoin v0.2.5 h1:6iR5tXJ/e6tJZzzdMc1km3Sa7RRIVBKAK32O2s7AYfo= +github.com/cyphar/filepath-securejoin v0.2.5/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/datawire/dlib v1.3.0 h1:KkmyXU1kwm3oPBk1ypR70YbcOlEXWzEbx5RE0iRXTGk= github.com/datawire/dlib v1.3.0/go.mod h1:NiGDmetmbkBvtznpWSx6C0vA0s0LK9aHna3LJDqjruk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -77,26 +81,26 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= -github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc= -github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2/go.mod h1:WHNsWjnIn2V1LYOrME7e8KxSeKunYHsxEm4am0BUtcI= -github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v25.0.1+incompatible h1:mFpqnrS6Hsm3v1k7Wa/BO23oz0k121MTbTO1lpcGSkU= -github.com/docker/cli v25.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/distribution/distribution/v3 v3.0.0-alpha.1 h1:jn7I1gvjOvmLztH1+1cLiUFud7aeJCIQcgzugtwjyJo= +github.com/distribution/distribution/v3 v3.0.0-alpha.1/go.mod h1:LCp4JZp1ZalYg0W/TN05jarCQu+h4w7xc7ZfQF4Y/cY= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/cli v27.0.3+incompatible h1:usGs0/BoBW8MWxGeEtqPMkzOY56jZ6kYlSN5BLDioCQ= +github.com/docker/cli v27.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0= -github.com/docker/docker v24.0.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.8.1 h1:j/eKUktUltBtMzKqmfLB0PAgqYyMHOp5vfsD1807oKo= -github.com/docker/docker-credential-helpers v0.8.1/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/docker v27.0.3+incompatible h1:aBGI9TeQ4MPlhquTQKq9XbK79rKFVwXNUAYz9aXyEBE= +github.com/docker/docker v27.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= +github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/elliotchance/orderedmap/v2 v2.2.0 h1:7/2iwO98kYT4XkOjA9mBEIwvi4KpGB4cyHeOFOnj4Vk= @@ -105,8 +109,10 @@ github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A= github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/envoyproxy/go-control-plane v0.12.1-0.20240202001849-656bed747258 h1:PL88OYv87Y9v9e9snibPx72PYcyWy0fyARABSJ5+5FQ= -github.com/envoyproxy/go-control-plane v0.12.1-0.20240202001849-656bed747258/go.mod h1:lFu6itz1hckLR2A3aJ+ZKf3lu8HpjTsJSsqvVF6GL6g= +github.com/envoyproxy/gateway v1.1.0 h1:4+mNtirUNwLudBj8y9qXqL10qXxEDwBytQOU/2YgINQ= +github.com/envoyproxy/gateway v1.1.0/go.mod h1:9hrhbkuH7sBwB0pzMXm3AFyw+lxG3e+YCvuWaM6JKrw= +github.com/envoyproxy/go-control-plane v0.12.1-0.20240612043845-c54ec4ce422d h1:RopQsG28t61pLLZRkwzwBsi60yDsOP8RvW47A3eAcGo= +github.com/envoyproxy/go-control-plane v0.12.1-0.20240612043845-c54ec4ce422d/go.mod h1:5Wkq+JduFtdAXihLmeTJf+tRYIT4KBc2vPXDhwVo1pA= github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= @@ -115,8 +121,8 @@ github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0 github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI= @@ -145,17 +151,12 @@ github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= -github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU= -github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs= -github.com/gobuffalo/packd v1.0.1 h1:U2wXfRr4E9DH8IdsDLlRFwTZTK7hLfq9qT/QHXGVe/0= -github.com/gobuffalo/packd v1.0.1/go.mod h1:PP2POP3p3RXGz7Jh6eYEf93S7vA2za6xM7QT85L4+VY= -github.com/gobuffalo/packr/v2 v2.8.3 h1:xE1yzvnO56cUC0sTpKR3DIbxZgB54AftTFMhB2XEWlY= -github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXsOdiU5KwbKc= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= @@ -170,12 +171,10 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k= -github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/cel-go v0.17.8 h1:j9m730pMZt1Fc4oKhCLUHfjj6527LuhYcYw0Rl8gqto= -github.com/google/cel-go v0.17.8/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY= +github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84= +github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -192,8 +191,8 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= -github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= +github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= +github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -205,15 +204,21 @@ github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd h1:PpuIBO5P3e9hpqBD github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd/go.mod h1:M5qHK+eWfAv8VR/265dIuEpL3fNfeC21tXXp9itM24A= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= -github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru/arc/v2 v2.0.7 h1:QxkVTxwColcduO+LP7eJO56r2hFiG8zEbfAAzRv52KQ= +github.com/hashicorp/golang-lru/arc/v2 v2.0.7/go.mod h1:Pe7gBlGdc8clY5LJ0LpJXMt5AmgmWNH1g+oFFVUHOEc= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= @@ -228,12 +233,10 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= -github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= +github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -264,8 +267,8 @@ github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZ github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= -github.com/lestrrat-go/jwx v1.2.28 h1:uadI6o0WpOVrBSf498tRXZIwPpEtLnR9CvqPFXeI5sA= -github.com/lestrrat-go/jwx v1.2.28/go.mod h1:nF+91HEMh/MYFVwKPl5HHsBGMPscqbQb+8IDQdIazP8= +github.com/lestrrat-go/jwx v1.2.29 h1:QT0utmUJ4/12rmsVQrJ3u55bycPkKqGYuGT4tyRhxSQ= +github.com/lestrrat-go/jwx v1.2.29/go.mod h1:hU8k2l6WF0ncx20uQdOmik/Gjg6E3/wIRtXSNFeZuB8= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -277,12 +280,6 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/maistra/istio-operator v0.0.0-20240217080932-98753cb28cd7 h1:hb04aN5i9DRUXBQ1SuU+p006IGgaqqDQY1/8vzAUvY4= github.com/maistra/istio-operator v0.0.0-20240217080932-98753cb28cd7/go.mod h1:E/Qq7Lq+C43qZ8G3XSEDgDc2qMjZdKx8Er0jdAK1fKE= -github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI= -github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc= -github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY= -github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= -github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= -github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/martinlindhe/base36 v1.1.1 h1:1F1MZ5MGghBXDZ2KJ3QfxmiydlWOGB8HCEtkap5NkVg= github.com/martinlindhe/base36 v1.1.1/go.mod h1:vMS8PaZ5e/jV9LwFKlm0YLnXl/hpOihiBxKkIoc3g08= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -293,11 +290,11 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= -github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= +github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= -github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= +github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs= +github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= @@ -312,8 +309,8 @@ github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= -github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= -github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= +github.com/moby/sys/mountinfo v0.7.1 h1:/tTvQaSJRr2FshkhXiIpux6fQ2Zvc4j7tAhMTStAG2g= +github.com/moby/sys/mountinfo v0.7.1/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -325,8 +322,6 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -338,8 +333,8 @@ github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= -github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= @@ -347,8 +342,8 @@ github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rK github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/planetscale/vtprotobuf v0.6.0 h1:nBeETjudeJ5ZgBHUz1fVHvbqUKnYOXNhsIEabROxmNA= -github.com/planetscale/vtprotobuf v0.6.0/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -361,26 +356,32 @@ github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQ github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= -github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/prometheus/prometheus v0.49.1 h1:90mDvjrFnca2m+0qPSIDr3y7iHPTAagOAElz7j+HtGk= -github.com/prometheus/prometheus v0.49.1/go.mod h1:aDogiyqmv3aBIWDb5z5Sdcxuuf2BOfiJwOIm9JGpMnI= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/prometheus/prometheus v0.51.1 h1:V2e7x2oiUC0Megp26+xjffxBf9EGkyP1iQuGd4VjUSU= +github.com/prometheus/prometheus v0.51.1/go.mod h1:yv4MwOn3yHMQ6MZGHPg/U7Fcyqf+rxqiZfSur6myVtc= +github.com/redis/go-redis/extra/rediscmd/v9 v9.5.3 h1:1/BDligzCa40GTllkDnY3Y5DTHuKCONbB2JcRyIfl20= +github.com/redis/go-redis/extra/rediscmd/v9 v9.5.3/go.mod h1:3dZmcLn3Qw6FLlWASn1g4y+YO9ycEFUOM+bhBmzLVKQ= +github.com/redis/go-redis/extra/redisotel/v9 v9.5.3 h1:kuvuJL/+MZIEdvtb/kTBRiRgYaOmx1l+lYJyVdrRUOs= +github.com/redis/go-redis/extra/redisotel/v9 v9.5.3/go.mod h1:7f/FMrf5RRRVHXgfk7CzSVzXHiWeuOQUu2bsVqWoa+g= +github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4= +github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg= -github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/rubenv/sql-migrate v1.5.2 h1:bMDqOnrJVV/6JQgQ/MxOpU+AdO8uzYYA/TxFUBzFtS0= -github.com/rubenv/sql-migrate v1.5.2/go.mod h1:H38GW8Vqf8F0Su5XignRyaRcbXbJunSWxs+kmzlg0Is= +github.com/rubenv/sql-migrate v1.6.1 h1:bo6/sjsan9HaXAsNxYP/jCEDUGibHp8JmOBw7NTGRos= +github.com/rubenv/sql-migrate v1.6.1/go.mod h1:tPzespupJS0jacLfhbwto/UjSX+8h2FdWB7ar+QlHa0= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= @@ -388,24 +389,24 @@ github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXn github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= -github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0= -github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= @@ -435,48 +436,68 @@ github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= -github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= -github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= -github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= -github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= -github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw= -go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= -go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= -go.opentelemetry.io/otel/exporters/prometheus v0.45.0 h1:BeIK2KGho0oCWa7LxEGSqfDZbs7Fpv/Viz+FS4P8CXE= -go.opentelemetry.io/otel/exporters/prometheus v0.45.0/go.mod h1:UVJZPLnfDSvHj+eJuZE+E1GjIBD267mEMfAAHJdghWg= -go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg= -go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= -go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= -go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= -go.opentelemetry.io/otel/sdk/metric v1.22.0 h1:ARrRetm1HCVxq0cbnaZQlfwODYJHo3gFL8Z3tSmHBcI= -go.opentelemetry.io/otel/sdk/metric v1.22.0/go.mod h1:KjQGeMIDlBNEOo6HvjhxIec1p/69/kULDcp4gr0oLQQ= -go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0= -go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= -go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= -go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= -go.starlark.net v0.0.0-20231121155337-90ade8b19d09 h1:hzy3LFnSN8kuQK8h9tHl4ndF6UruMj47OqwqsS+/Ai4= -go.starlark.net v0.0.0-20231121155337-90ade8b19d09/go.mod h1:LcLNIzVOMp4oV+uusnpk+VU+SzXaJakUuBjoCSWH5dM= +go.opentelemetry.io/contrib/bridges/prometheus v0.53.0 h1:BdkKDtcrHThgjcEia1737OUuFdP6xzBKAMx2sNZCkvE= +go.opentelemetry.io/contrib/bridges/prometheus v0.53.0/go.mod h1:ZkhVxcJgeXlL/lVyT/vxNHVFiSG5qOaDwYaSgD8IfZo= +go.opentelemetry.io/contrib/exporters/autoexport v0.53.0 h1:13K+tY7E8GJInkrvRiPAhC0gi/7vKjzDNhtmCf+QXG8= +go.opentelemetry.io/contrib/exporters/autoexport v0.53.0/go.mod h1:lyQF6xQ4iDnMg4sccNdFs1zf62xd79YI8vZqKjOTwMs= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.4.0 h1:zBPZAISA9NOc5cE8zydqDiS0itvg/P/0Hn9m72a5gvM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.4.0/go.mod h1:gcj2fFjEsqpV3fXuzAA+0Ze1p2/4MJ4T7d77AmkvueQ= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 h1:U2guen0GhqH8o/G2un8f/aG/y++OuW6MyCo6hT9prXk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0/go.mod h1:yeGZANgEcpdx/WK0IvvRFC+2oLiMS2u4L/0Rj2M2Qr0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0 h1:aLmmtjRke7LPDQ3lvpFz+kNEH43faFhzW7v8BFIEydg= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0/go.mod h1:TC1pyCt6G9Sjb4bQpShH+P5R53pO6ZuGnHuuln9xMeE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 h1:R3X6ZXmNPRR8ul6i3WgFURCHzaXjHdm0karRG/+dj3s= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0/go.mod h1:QWFXnDavXWwMx2EEcZsf3yxgEKAqsxQ+Syjp+seyInw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 h1:j9+03ymgYhPKmeXGk5Zu+cIZOlVzd9Zv7QIiyItjFBU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0/go.mod h1:Y5+XiUG4Emn1hTfciPzGPJaSI+RpDts6BnCIir0SLqk= +go.opentelemetry.io/otel/exporters/prometheus v0.50.0 h1:2Ewsda6hejmbhGFyUvWZjUThC98Cf8Zy6g0zkIimOng= +go.opentelemetry.io/otel/exporters/prometheus v0.50.0/go.mod h1:pMm5PkUo5YwbLiuEf7t2xg4wbP0/eSJrMxIMxKosynY= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.4.0 h1:0MH3f8lZrflbUWXVxyBg/zviDFdGE062uKh5+fu8Vv0= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.4.0/go.mod h1:Vh68vYiHY5mPdekTr0ox0sALsqjoVy0w3Os278yX5SQ= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.28.0 h1:BJee2iLkfRfl9lc7aFmBwkWxY/RI1RDdXepSF6y8TPE= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.28.0/go.mod h1:DIzlHs3DRscCIBU3Y9YSzPfScwnYnzfnCd4g8zA7bZc= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0 h1:EVSnY9JbEEW92bEkIYOVMw4q1WJxIAGoFTrtYOzWuRQ= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0/go.mod h1:Ea1N1QQryNXpCD0I1fdLibBAIpQuBkznMmkdKrapk1Y= +go.opentelemetry.io/otel/log v0.4.0 h1:/vZ+3Utqh18e8TPjuc3ecg284078KWrR8BRz+PQAj3o= +go.opentelemetry.io/otel/log v0.4.0/go.mod h1:DhGnQvky7pHy82MIRV43iXh3FlKN8UUKftn0KbLOq6I= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= +go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= +go.opentelemetry.io/otel/sdk/log v0.4.0 h1:1mMI22L82zLqf6KtkjrRy5BbagOTWdJsqMY/HSqILAA= +go.opentelemetry.io/otel/sdk/log v0.4.0/go.mod h1:AYJ9FVF0hNOgAVzUG/ybg/QttnXhUePWAupmCqtdESo= +go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnCQArXCKlg08= +go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.starlark.net v0.0.0-20240520160348-046347dcd104 h1:3qhteRISupnJvaWshOmeqEUs2y9oc/+/ePPvDh3Eygg= +go.starlark.net v0.0.0-20240520160348-046347dcd104/go.mod h1:YKMCv9b1WrfWmeqdV5MAuEHWsu5iC+fe6kYl2sQjdI8= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= -go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -493,8 +514,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -525,8 +546,8 @@ golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= -golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -548,20 +569,20 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= +google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0= +google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw= google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf h1:liao9UHurZLtiEwBgT9LMOnKYsHze6eA6w1KQCMVN2Q= google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= -google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= -google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= @@ -578,38 +599,38 @@ gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -helm.sh/helm/v3 v3.14.3 h1:HmvRJlwyyt9HjgmAuxHbHv3PhMz9ir/XNWHyXfmnOP4= -helm.sh/helm/v3 v3.14.3/go.mod h1:v6myVbyseSBJTzhmeE39UcPLNv6cQK6qss3dvgAySaE= -istio.io/api v1.20.0 h1:heE1eQoMsuZlwWOf7Xm8TKqKLNKVs11G/zMe5QyR1u4= -istio.io/api v1.20.0/go.mod h1:hm1PE/mGdIAsjCDkTIAplP53H7TjO5LUQCiVvF26SVg= -istio.io/client-go v1.20.0 h1:TSSv6A4sYvuBtoKOwyuRmBmPwSb4s++lWlh7RB7+7gY= -istio.io/client-go v1.20.0/go.mod h1:6D76gZsdjz8JtVeIarUYdOn3WA8Zh+j8fIv2+2K3M+Q= -istio.io/istio v0.0.0-20240208010324-ffed2074bd92 h1:YxhEu2lLwuBomTsEZZuLrQgYgmSYTfTl05QHdhEi0fI= -istio.io/istio v0.0.0-20240208010324-ffed2074bd92/go.mod h1:c/EJafelMaRruaRomQm9cffS3TX0AbD60itcC6Z//Fs= +helm.sh/helm/v3 v3.15.3 h1:HcZDaVFe9uHa6hpsR54mJjYyRy4uz/pc6csg27nxFOc= +helm.sh/helm/v3 v3.15.3/go.mod h1:FzSIP8jDQaa6WAVg9F+OkKz7J0ZmAga4MABtTbsb9WQ= +istio.io/api v1.22.3-0.20240703105953-437a88321a16 h1:0XRnzLqAZ1BYX1jBzBG1slXytUkdhRoVwC23upTyFl4= +istio.io/api v1.22.3-0.20240703105953-437a88321a16/go.mod h1:S3l8LWqNYS9yT+d4bH+jqzH2lMencPkW7SKM1Cu9EyM= +istio.io/client-go v1.22.3-0.20240703110620-5f69a1e4c030 h1:K/G2h2qeso2/xuNQFDNiJqcVNvOlyjsdACDN6Db+zkI= +istio.io/client-go v1.22.3-0.20240703110620-5f69a1e4c030/go.mod h1:D/vNne1n5586423NgGXMnPgshE/99mQgnjnxK/Vw2yM= +istio.io/istio v0.0.0-20240709015522-1e0dc8dd8809 h1:Befi25J316GG748aL96JtBf/fbFEyxJ3tosqG0b5SaI= +istio.io/istio v0.0.0-20240709015522-1e0dc8dd8809/go.mod h1:SUACpPO3/iqxIDDMTPbngKB6gv7ht90iMBa6ddfq9qw= k8s.io/api v0.30.2 h1:+ZhRj+28QT4UOH+BKznu4CBgPWgkXO7XAvMcMl0qKvI= k8s.io/api v0.30.2/go.mod h1:ULg5g9JvOev2dG0u2hig4Z7tQ2hHIuS+m8MNZ+X6EmI= -k8s.io/apiextensions-apiserver v0.30.1 h1:4fAJZ9985BmpJG6PkoxVRpXv9vmPUOVzl614xarePws= -k8s.io/apiextensions-apiserver v0.30.1/go.mod h1:R4GuSrlhgq43oRY9sF2IToFh7PVlF1JjfWdoG3pixk4= +k8s.io/apiextensions-apiserver v0.30.2 h1:l7Eue2t6QiLHErfn2vwK4KgF4NeDgjQkCXtEbOocKIE= +k8s.io/apiextensions-apiserver v0.30.2/go.mod h1:lsJFLYyK40iguuinsb3nt+Sj6CmodSI4ACDLep1rgjw= k8s.io/apimachinery v0.30.2 h1:fEMcnBj6qkzzPGSVsAZtQThU62SmQ4ZymlXRC5yFSCg= k8s.io/apimachinery v0.30.2/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= -k8s.io/apiserver v0.30.1 h1:BEWEe8bzS12nMtDKXzCF5Q5ovp6LjjYkSp8qOPk8LZ8= -k8s.io/apiserver v0.30.1/go.mod h1:i87ZnQ+/PGAmSbD/iEKM68bm1D5reX8fO4Ito4B01mo= -k8s.io/cli-runtime v0.29.1 h1:By3WVOlEWYfyxhGko0f/IuAOLQcbBSMzwSaDren2JUs= -k8s.io/cli-runtime v0.29.1/go.mod h1:vjEY9slFp8j8UoMhV5AlO8uulX9xk6ogfIesHobyBDU= +k8s.io/apiserver v0.30.2 h1:ACouHiYl1yFI2VFI3YGM+lvxgy6ir4yK2oLOsLI1/tw= +k8s.io/apiserver v0.30.2/go.mod h1:BOTdFBIch9Sv0ypSEcUR6ew/NUFGocRFNl72Ra7wTm8= +k8s.io/cli-runtime v0.30.2 h1:ooM40eEJusbgHNEqnHziN9ZpLN5U4WcQGsdLKVxpkKE= +k8s.io/cli-runtime v0.30.2/go.mod h1:Y4g/2XezFyTATQUbvV5WaChoUGhojv/jZAtdp5Zkm0A= k8s.io/client-go v0.30.2 h1:sBIVJdojUNPDU/jObC+18tXWcTJVcwyqS9diGdWHk50= k8s.io/client-go v0.30.2/go.mod h1:JglKSWULm9xlJLx4KCkfLLQ7XwtlbflV6uFFSHTMgVs= -k8s.io/component-base v0.30.1 h1:bvAtlPh1UrdaZL20D9+sWxsJljMi0QZ3Lmw+kmZAaxQ= -k8s.io/component-base v0.30.1/go.mod h1:e/X9kDiOebwlI41AvBHuWdqFriSRrX50CdwA9TFaHLI= -k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= -k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/component-base v0.30.2 h1:pqGBczYoW1sno8q9ObExUqrYSKhtE5rW3y6gX88GZII= +k8s.io/component-base v0.30.2/go.mod h1:yQLkQDrkK8J6NtP+MGJOws+/PPeEXNpwFixsUI7h/OE= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20240521193020-835d969ad83a h1:zD1uj3Jf+mD4zmA7W+goE5TxDkI7OGJjBNBzq5fJtLA= k8s.io/kube-openapi v0.0.0-20240521193020-835d969ad83a/go.mod h1:UxDHUPsUwTOOxSU+oXURfFBcAS6JwiRXTYqYwfuGowc= -k8s.io/kubectl v0.29.1 h1:rWnW3hi/rEUvvg7jp4iYB68qW5un/urKbv7fu3Vj0/s= -k8s.io/kubectl v0.29.1/go.mod h1:SZzvLqtuOJYSvZzPZR9weSuP0wDQ+N37CENJf0FhDF4= +k8s.io/kubectl v0.30.2 h1:cgKNIvsOiufgcs4yjvgkK0+aPCfa8pUwzXdJtkbhsH8= +k8s.io/kubectl v0.30.2/go.mod h1:rz7GHXaxwnigrqob0lJsiA07Df8RE3n1TSaC2CTeuB4= k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 h1:jgGTlFYnhF1PM1Ax/lAlxUPE+KfCIXHaathvJg1C3ak= k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -oras.land/oras-go v1.2.4 h1:djpBY2/2Cs1PV87GSJlxv4voajVOMZxqqtq9AB8YNvY= -oras.land/oras-go v1.2.4/go.mod h1:DYcGfb3YF1nKjcezfX2SNlDAeQFKSXmf+qrFmrh4324= +oras.land/oras-go v1.2.5 h1:XpYuAwAb0DfQsunIyMfeET92emK8km3W4yEzZvUbsTo= +oras.land/oras-go v1.2.5/go.mod h1:PuAwRShRZCsZb7g8Ar3jKKQR/2A/qN+pkYxIOd/FAoo= sigs.k8s.io/controller-runtime v0.18.4 h1:87+guW1zhvuPLh1PHybKdYFLU0YJp4FhJRmiHvm5BZw= sigs.k8s.io/controller-runtime v0.18.4/go.mod h1:TVoGrfdpbA9VRFaRnKgk9P5/atA0pMwq+f+msb9M8Sg= sigs.k8s.io/external-dns v0.14.0 h1:pgY3DdyoBei+ej1nyZUzRt9ECm9RRwb9s6/CPWe51tc= @@ -618,10 +639,10 @@ sigs.k8s.io/gateway-api v1.1.0 h1:DsLDXCi6jR+Xz8/xd0Z1PYl2Pn0TyaFMOPPZIj4inDM= sigs.k8s.io/gateway-api v1.1.0/go.mod h1:ZH4lHrL2sDi0FHZ9jjneb8kKnGzFWyrTya35sWUTrRs= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 h1:XX3Ajgzov2RKUdc5jW3t5jwY7Bo7dcRm+tFxT+NfgY0= -sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3/go.mod h1:9n16EZKMhXBNSiUC5kSdFQJkdH3zbxS/JoO619G1VAY= -sigs.k8s.io/kustomize/kyaml v0.16.0 h1:6J33uKSoATlKZH16unr2XOhDI+otoe2sR3M8PDzW3K0= -sigs.k8s.io/kustomize/kyaml v0.16.0/go.mod h1:xOK/7i+vmE14N2FdFyugIshB8eF6ALpy7jI87Q2nRh4= +sigs.k8s.io/kustomize/api v0.17.2 h1:E7/Fjk7V5fboiuijoZHgs4aHuexi5Y2loXlVOAVAG5g= +sigs.k8s.io/kustomize/api v0.17.2/go.mod h1:UWTz9Ct+MvoeQsHcJ5e+vziRRkwimm3HytpZgIYqye0= +sigs.k8s.io/kustomize/kyaml v0.17.1 h1:TnxYQxFXzbmNG6gOINgGWQt09GghzgTP6mIurOgrLCQ= +sigs.k8s.io/kustomize/kyaml v0.17.1/go.mod h1:9V0mCjIEYjlXuCdYsSXvyoy2BTsLESH7TlGV81S282U= sigs.k8s.io/mcs-api v0.1.0 h1:edDbg0oRGfXw8TmZjKYep06LcJLv/qcYLidejnUp0PM= sigs.k8s.io/mcs-api v0.1.0/go.mod h1:gGiAryeFNB4GBsq2LBmVqSgKoobLxt+p7ii/WG5QYYw= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= diff --git a/main.go b/main.go index 1b8b68fcb..347f02985 100644 --- a/main.go +++ b/main.go @@ -23,6 +23,7 @@ import ( "runtime" certmanv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + egv1alpha1 "github.com/envoyproxy/gateway/api/v1alpha1" authorinoopapi "github.com/kuadrant/authorino-operator/api/v1beta1" authorinoapi "github.com/kuadrant/authorino/api/v1beta2" kuadrantdnsv1alpha1 "github.com/kuadrant/dns-operator/api/v1alpha1" @@ -45,6 +46,7 @@ import ( metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" "sigs.k8s.io/controller-runtime/pkg/webhook" gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" + gatewayapiv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" maistraapis "github.com/kuadrant/kuadrant-operator/api/external/maistra" kuadrantv1alpha1 "github.com/kuadrant/kuadrant-operator/api/v1alpha1" @@ -77,6 +79,7 @@ func init() { utilruntime.Must(istiosecurityv1beta1.AddToScheme(scheme)) utilruntime.Must(istiov1alpha1.AddToScheme(scheme)) utilruntime.Must(gatewayapiv1.Install(scheme)) + utilruntime.Must(gatewayapiv1beta1.Install(scheme)) utilruntime.Must(istioextensionv1alpha1.AddToScheme(scheme)) utilruntime.Must(apiextv1.AddToScheme(scheme)) utilruntime.Must(istioapis.AddToScheme(scheme)) @@ -87,6 +90,7 @@ func init() { utilruntime.Must(kuadrantv1beta2.AddToScheme(scheme)) utilruntime.Must(kuadrantdnsv1alpha1.AddToScheme(scheme)) utilruntime.Must(certmanv1.AddToScheme(scheme)) + utilruntime.Must(egv1alpha1.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme logger := log.NewLogger( @@ -262,6 +266,18 @@ func main() { os.Exit(1) } + authPolicyIstioAuthorizationPolicyReconciler := reconcilers.NewBaseReconciler( + mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), + log.Log.WithName("authpolicy").WithName("istioauthorizationpolicy"), + mgr.GetEventRecorderFor("AuthPolicyIstioAuthorizationPolicy"), + ) + 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"), @@ -286,6 +302,54 @@ func main() { os.Exit(1) } + authPolicyEnvoySecurityPolicyReconciler := reconcilers.NewBaseReconciler( + mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), + log.Log.WithName("authpolicy").WithName("securitypolicy"), + mgr.GetEventRecorderFor("AuthPolicyEnvoySecurityPolicy"), + ) + 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"), + mgr.GetEventRecorderFor("EnvoySecurityPolicyReferenceGrant"), + ) + if err = (&controllers.EnvoySecurityPolicyReferenceGrantReconciler{ + BaseReconciler: envoySecurityPolicyReferenceGrantReconciler, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "EnvoySecurityPolicyReferenceGrant") + os.Exit(1) + } + + envoyGatewayWasmReconciler := reconcilers.NewBaseReconciler( + mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), + log.Log.WithName("envoyGatewayWasmReconciler"), + mgr.GetEventRecorderFor("EnvoyGatewayWasmReconciler"), + ) + if err = (&controllers.EnvoyGatewayWasmReconciler{ + BaseReconciler: envoyGatewayWasmReconciler, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "EnvoyGatewayWasmReconciler") + os.Exit(1) + } + + envoyGatewayLimitadorClusterReconciler := reconcilers.NewBaseReconciler( + mgr.GetClient(), mgr.GetScheme(), mgr.GetAPIReader(), + log.Log.WithName("envoyGatewayLimitadorClusterReconciler"), + mgr.GetEventRecorderFor("EnvoyGatewayLimitadorClusterReconciler"), + ) + if err = (&controllers.EnvoyGatewayLimitadorClusterReconciler{ + BaseReconciler: envoyGatewayLimitadorClusterReconciler, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "EnvoyGatewayLimitadorClusterReconciler") + os.Exit(1) + } + //+kubebuilder:scaffold:builder if err = mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/make/alerts.mk b/make/alerts.mk index fd4499443..73c796fb4 100644 --- a/make/alerts.mk +++ b/make/alerts.mk @@ -1,12 +1,9 @@ ##@ Alerts -export WORKDIR ?= $(shell pwd) export IMAGE ?= quay.io/prometheus/prometheus -export AVAILABILITY_SLO_RULES ?= $(WORKDIR)/examples/alerts/slo-availability.yaml -export LATENCY_SLO_RULES ?= $(WORKDIR)/examples/alerts/slo-latency.yaml -export UNIT_TEST_DIR ?= $(WORKDIR)/examples/alerts/tests -export OS = $(shell uname | tr '[:upper:]' '[:lower:]') -export ARCH = $(shell uname -m | tr '[:upper:]' '[:lower:]') -export SLOTH = $(WORKDIR)/bin/sloth +export AVAILABILITY_SLO_RULES ?= $(PROJECT_PATH)/examples/alerts/slo-availability.yaml +export LATENCY_SLO_RULES ?= $(PROJECT_PATH)/examples/alerts/slo-latency.yaml +export UNIT_TEST_DIR ?= $(PROJECT_PATH)/examples/alerts/tests +export SLOTH = $(PROJECT_PATH)/bin/sloth export ALERTS_SLOTH_INPUT_DIR = /examples/alerts/sloth export ALERTS_SLOTH_OUTPUT_DIR = /examples/alerts @@ -30,7 +27,7 @@ test-alerts: container-runtime-tool ## Test alerts using promtool sloth: $(SLOTH) ## Install Sloth $(SLOTH): - cd $(WORKDIR)/bin && curl -L https://github.com/slok/sloth/releases/download/v0.11.0/sloth-$(OS)-$(ARCH) > sloth && chmod +x sloth + cd $(PROJECT_PATH)/bin && curl -L https://github.com/slok/sloth/releases/download/v0.11.0/sloth-$(OS)-$(ARCH) > sloth && chmod +x sloth sloth-generate: sloth ## Generate alerts using Sloth templates - $(SLOTH) generate -i $(WORKDIR)$(ALERTS_SLOTH_INPUT_DIR) -o $(WORKDIR)$(ALERTS_SLOTH_OUTPUT_DIR) --default-slo-period=28d + $(SLOTH) generate -i $(PROJECT_PATH)$(ALERTS_SLOTH_INPUT_DIR) -o $(PROJECT_PATH)$(ALERTS_SLOTH_OUTPUT_DIR) --default-slo-period=28d diff --git a/make/development-environments.mk b/make/development-environments.mk index 986265e16..91c016d27 100644 --- a/make/development-environments.mk +++ b/make/development-environments.mk @@ -1,7 +1,5 @@ ##@ Deployment -GATEWAYAPI_PROVIDER = istio - install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. # Use server side apply, otherwise will hit into this issue https://medium.com/pareture/kubectl-install-crd-failed-annotations-too-long-2ebc91b40c7d $(KUSTOMIZE) build config/crd | kubectl apply --server-side -f - @@ -111,12 +109,20 @@ istio-env-setup: ## Install Istio, istio gateway and gatewayapi-env-setup $(MAKE) deploy-istio-gateway @echo @echo "Now you can open local access to the istio gateway by doing:" - @echo "kubectl port-forward -n istio-system service/istio-ingressgateway-istio 9080:80 &" + @echo "kubectl port-forward -n gateway-system service/kuadrant-ingressgateway-istio 9080:80 &" @echo "export GATEWAY_URL=localhost:9080" @echo "after that, you can curl -H \"Host: myhost.com\" \$$GATEWAY_URL" @echo "-- Linux only -- Ingress gateway is exported using loadbalancer service in port 80" - @echo "export INGRESS_HOST=\$$(kubectl get gtw istio-ingressgateway -n istio-system -o jsonpath='{.status.addresses[0].value}')" - @echo "export INGRESS_PORT=\$$(kubectl get gtw istio-ingressgateway -n istio-system -o jsonpath='{.spec.listeners[?(@.name==\"http\")].port}')" + @echo "export INGRESS_HOST=\$$(kubectl get gtw kuadrant-ingressgateway -n gateway-system -o jsonpath='{.status.addresses[0].value}')" + @echo "export INGRESS_PORT=\$$(kubectl get gtw kuadrant-ingressgateway -n gateway-system -o jsonpath='{.spec.listeners[?(@.name==\"http\")].port}')" @echo "export GATEWAY_URL=\$$INGRESS_HOST:\$$INGRESS_PORT" @echo "curl -H \"Host: myhost.com\" \$$GATEWAY_URL" @echo + +##@ Development Environment: Kubernetes with GatewayAPI and EnvoyGateway installed + +.PHONY: envoygateway-env-setup +envoygateway-env-setup: ## Install Envoy Gateway and one gateway + $(MAKE) k8s-env-setup + $(MAKE) envoy-gateway-install # envoy gateway k8s manifests include gateway API CRDs + $(MAKE) deploy-eg-gateway diff --git a/make/envoy-gateway.mk b/make/envoy-gateway.mk new file mode 100644 index 000000000..ad97af2dd --- /dev/null +++ b/make/envoy-gateway.mk @@ -0,0 +1,66 @@ + +##@ Envoy Gateway + +## Targets to help install and configure EG + +EG_CONFIG_DIR = config/dependencies/envoy-gateway +EG_NAMESPACE = envoy-gateway-system + +# egctl tool +EGCTL=$(PROJECT_PATH)/bin/egctl +EGCTL_VERSION ?= v1.1.0 + +ifeq ($(ARCH),x86_64) + EG_ARCH = amd64 +endif +ifeq ($(ARCH),aarch64) + EG_ARCH = arm64 +endif +ifneq ($(filter armv5%,$(ARCH)),) + EG_ARCH = armv5 +endif +ifneq ($(filter armv6%,$(ARCH)),) + EG_ARCH = armv6 +endif +ifneq ($(filter armv7%,$(ARCH)),) + EG_ARCH = arm +endif + +$(EGCTL): + mkdir -p $(PROJECT_PATH)/bin + ## get-egctl.sh requires sudo and does not allow installing in a custom location. Fails if not in the PATH as well + # curl -sSL https://gateway.envoyproxy.io/get-egctl.sh | EGCTL_INSTALL_DIR=$(PROJECT_PATH)/bin VERSION=$(EGCTL_VERSION) bash + $(eval TMP := $(shell mktemp -d)) + cd $(TMP); curl -sSL https://github.com/envoyproxy/gateway/releases/download/$(EGCTL_VERSION)/egctl_$(EGCTL_VERSION)_$(OS)_$(EG_ARCH).tar.gz -o egctl.tar.gz + tar xf $(TMP)/egctl.tar.gz -C $(TMP) + cp $(TMP)/bin/$(OS)/$(EG_ARCH)/egctl $(EGCTL) + -rm -rf $(TMP) + +.PHONY: egctl +egctl: $(EGCTL) ## Download egctl locally if necessary. + +envoy-gateway-enable-envoypatchpolicy: $(YQ) + $(eval TMP := $(shell mktemp -d)) + kubectl get configmap -n $(EG_NAMESPACE) envoy-gateway-config -o jsonpath='{.data.envoy-gateway\.yaml}' > $(TMP)/envoy-gateway.yaml + yq e '.extensionApis.enableEnvoyPatchPolicy = true' -i $(TMP)/envoy-gateway.yaml + kubectl create configmap -n $(EG_NAMESPACE) envoy-gateway-config --from-file=envoy-gateway.yaml=$(TMP)/envoy-gateway.yaml -o yaml --dry-run=client | kubectl replace -f - + -rm -rf $(TMP) + kubectl rollout restart deployment envoy-gateway -n $(EG_NAMESPACE) + +EG_VERSION ?= v1.1.0 +.PHONY: envoy-gateway-install +envoy-gateway-install: kustomize $(HELM) + $(HELM) install eg oci://docker.io/envoyproxy/gateway-helm --version $(EG_VERSION) -n $(EG_NAMESPACE) --create-namespace + $(MAKE) envoy-gateway-enable-envoypatchpolicy + kubectl wait --timeout=5m -n $(EG_NAMESPACE) deployment/envoy-gateway --for=condition=Available + +.PHONY: deploy-eg-gateway +deploy-eg-gateway: kustomize ## Deploy Gateway API gateway + $(KUSTOMIZE) build $(EG_CONFIG_DIR)/gateway | kubectl apply -f - + kubectl wait --timeout=5m -n gateway-system gateway/kuadrant-ingressgateway --for=condition=Programmed + @echo + @echo "-- Linux only -- Ingress gateway is exported using loadbalancer service in port 80" + @echo "export INGRESS_HOST=\$$(kubectl get gtw kuadrant-ingressgateway -n gateway-system-o jsonpath='{.status.addresses[0].value}')" + @echo "export INGRESS_PORT=\$$(kubectl get gtw kuadrant-ingressgateway -n gateway-system -o jsonpath='{.spec.listeners[?(@.name==\"http\")].port}')" + @echo "Now you can hit the gateway:" + @echo "curl --verbose --resolve www.example.com:\$${INGRESS_PORT}:\$${INGRESS_HOST} http://www.example.com:\$${INGRESS_PORT}/get" diff --git a/make/integration-tests.mk b/make/integration-tests.mk index 24437244f..0fe4db81e 100644 --- a/make/integration-tests.mk +++ b/make/integration-tests.mk @@ -45,7 +45,7 @@ test-gatewayapi-env-integration: clean-cov generate fmt vet ginkgo ## Requires k test-istio-env-integration: clean-cov generate fmt vet ginkgo ## Requires kubernetes cluster with GatewayAPI and Istio installed. mkdir -p $(PROJECT_PATH)/coverage/istio-integration # Check `ginkgo help run` for command line options. For example to filtering tests. - $(GINKGO) \ + GATEWAYAPI_PROVIDER=istio $(GINKGO) \ --coverpkg $(INTEGRATION_COVER_PKGS) \ --output-dir $(PROJECT_PATH)/coverage/istio-integration \ --coverprofile cover.out \ @@ -59,11 +59,28 @@ test-istio-env-integration: clean-cov generate fmt vet ginkgo ## Requires kubern --trace \ $(INTEGRATION_TESTS_EXTRA_ARGS) tests/istio/... +test-envoygateway-env-integration: clean-cov generate fmt vet ginkgo ## Requires kubernetes cluster with GatewayAPI and EnvoyGateway installed. + mkdir -p $(PROJECT_PATH)/coverage/envoygateway-integration +# Check `ginkgo help run` for command line options. For example to filtering tests. + GATEWAYAPI_PROVIDER=envoygateway $(GINKGO) \ + --coverpkg $(INTEGRATION_COVER_PKGS) \ + --output-dir $(PROJECT_PATH)/coverage/envoygateway-integration \ + --coverprofile cover.out \ + -tags integration \ + --compilers=$(INTEGRATION_TEST_NUM_CORES) \ + --procs=$(INTEGRATION_TEST_NUM_PROCESSES) \ + --randomize-all \ + --randomize-suites \ + --fail-on-pending \ + --keep-going \ + --trace \ + $(INTEGRATION_TESTS_EXTRA_ARGS) tests/envoygateway/... + .PHONY: test-integration test-integration: clean-cov generate fmt vet ginkgo ## Requires kubernetes cluster with at least one GatewayAPI provider installed. mkdir -p $(PROJECT_PATH)/coverage/integration # Check `ginkgo help run` for command line options. For example to filtering tests. - $(GINKGO) \ + GATEWAYAPI_PROVIDER=$(GATEWAYAPI_PROVIDER) $(GINKGO) \ --coverpkg $(INTEGRATION_COVER_PKGS) \ --output-dir $(PROJECT_PATH)/coverage/integration \ --coverprofile cover.out \ diff --git a/make/istio.mk b/make/istio.mk index 7d41b5744..d9df5202a 100644 --- a/make/istio.mk +++ b/make/istio.mk @@ -15,7 +15,10 @@ endif # istioctl tool ISTIOCTL=$(shell pwd)/bin/istioctl -ISTIOVERSION = 1.20.0 +# OSSM 2.6 is based on Istio 1.20.8 +# https://github.com/maistra/istio/blob/maistra-2.6/go.mod#L116-L117 +# https://github.com/maistra/istio/pull/1039 +ISTIOVERSION = 1.20.8 $(ISTIOCTL): mkdir -p $(shell pwd)/bin $(eval TMP := $(shell mktemp -d)) @@ -28,7 +31,7 @@ istioctl: $(ISTIOCTL) ## Download istioctl locally if necessary. .PHONY: istioctl-install istioctl-install: istioctl ## Install istio. - $(ISTIOCTL) operator init + $(ISTIOCTL) operator init --operatorNamespace $(ISTIO_NAMESPACE) --watchedNamespaces $(ISTIO_NAMESPACE) kubectl apply -f $(ISTIO_INSTALL_DIR)/istio-operator.yaml .PHONY: istioctl-uninstall @@ -42,7 +45,7 @@ istioctl-verify-install: istioctl ## Verify istio installation. .PHONY: sail-install sail-install: kustomize $(KUSTOMIZE) build $(ISTIO_INSTALL_DIR)/sail | kubectl apply -f - - kubectl -n istio-system wait --for=condition=Available deployment istio-operator --timeout=300s + kubectl -n $(ISTIO_NAMESPACE) wait --for=condition=Available deployment istio-operator --timeout=300s kubectl apply -f $(ISTIO_INSTALL_DIR)/sail/istio.yaml .PHONY: sail-uninstall diff --git a/pkg/envoygateway/mutators.go b/pkg/envoygateway/mutators.go new file mode 100644 index 000000000..b1f59ffc5 --- /dev/null +++ b/pkg/envoygateway/mutators.go @@ -0,0 +1,134 @@ +package envoygateway + +import ( + "fmt" + "reflect" + + egv1alpha1 "github.com/envoyproxy/gateway/api/v1alpha1" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "sigs.k8s.io/controller-runtime/pkg/client" + gatewayapiv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + "github.com/kuadrant/kuadrant-operator/pkg/rlptools/wasm" +) + +func EnvoyExtensionPolicyMutator(existingObj, desiredObj client.Object) (bool, error) { + existing, ok := existingObj.(*egv1alpha1.EnvoyExtensionPolicy) + if !ok { + return false, fmt.Errorf("%T is not an *egapi.EnvoyExtensionPolicy", existingObj) + } + desired, ok := desiredObj.(*egv1alpha1.EnvoyExtensionPolicy) + if !ok { + return false, fmt.Errorf("%T is not an *egapi.EnvoyExtensionPolicy", desiredObj) + } + + var update bool + + if len(existing.Spec.Wasm) != len(desired.Spec.Wasm) { + update = true + existing.Spec.Wasm = desired.Spec.Wasm + } + + for idx := range desired.Spec.Wasm { + opts := cmp.Options{ + cmpopts.IgnoreFields(egv1alpha1.Wasm{}, "Config"), + cmpopts.IgnoreMapEntries(func(k string, _ any) bool { + return k == "config" + }), + } + + // Compare all except config (which is serialized into bytes) + if cmp.Equal(desired.Spec.Wasm[idx], existing.Spec.Wasm[idx], opts) { + update = true + existing.Spec.Wasm[idx] = desired.Spec.Wasm[idx] + } + + existingWasmConfig, err := wasm.ConfigFromJSON(existing.Spec.Wasm[idx].Config) + if err != nil { + return false, err + } + + desiredWasmConfig, err := wasm.ConfigFromJSON(desired.Spec.Wasm[idx].Config) + if err != nil { + return false, err + } + + // TODO(eastizle): reflect.DeepEqual does not work well with lists without order + if !reflect.DeepEqual(desiredWasmConfig, existingWasmConfig) { + update = true + existing.Spec.Wasm[idx].Config = desired.Spec.Wasm[idx].Config + } + } + + if !reflect.DeepEqual(existing.Spec.PolicyTargetReferences.TargetRefs, desired.Spec.PolicyTargetReferences.TargetRefs) { + update = true + existing.Spec.PolicyTargetReferences.TargetRefs = desired.Spec.PolicyTargetReferences.TargetRefs + } + + if !reflect.DeepEqual(existing.Annotations, desired.Annotations) { + update = true + existing.Annotations = desired.Annotations + } + + return update, nil +} + +func EnvoySecurityPolicyMutator(existingObj, desiredObj client.Object) (bool, error) { + existing, ok := existingObj.(*egv1alpha1.SecurityPolicy) + if !ok { + return false, fmt.Errorf("%T is not an *egapi.SecurityPolicy", existingObj) + } + desired, ok := desiredObj.(*egv1alpha1.SecurityPolicy) + if !ok { + return false, fmt.Errorf("%T is not an *egapi.SecurityPolicy", desiredObj) + } + + var update bool + + if !reflect.DeepEqual(existing.Spec.ExtAuth, desired.Spec.ExtAuth) { + update = true + existing.Spec.ExtAuth = desired.Spec.ExtAuth + } + + if !reflect.DeepEqual(existing.Spec.PolicyTargetReferences.TargetRefs, desired.Spec.PolicyTargetReferences.TargetRefs) { + update = true + existing.Spec.PolicyTargetReferences.TargetRefs = desired.Spec.PolicyTargetReferences.TargetRefs + } + + if !reflect.DeepEqual(existing.Annotations, desired.Annotations) { + update = true + existing.Annotations = desired.Annotations + } + + return update, nil +} + +func SecurityPolicyReferenceGrantMutator(existingObj, desiredObj client.Object) (bool, error) { + existing, ok := existingObj.(*gatewayapiv1beta1.ReferenceGrant) + if !ok { + return false, fmt.Errorf("%T is not an *gatewayapiv1beta1.ReferenceGrant", existingObj) + } + desired, ok := desiredObj.(*gatewayapiv1beta1.ReferenceGrant) + if !ok { + return false, fmt.Errorf("%T is not an *gatewayapiv1beta1.ReferenceGrant", desiredObj) + } + + var update bool + if !reflect.DeepEqual(existing.Spec.From, desired.Spec.From) { + update = true + existing.Spec.From = desired.Spec.From + } + + if !reflect.DeepEqual(existing.Spec.To, desired.Spec.To) { + update = true + existing.Spec.To = desired.Spec.To + } + + if !reflect.DeepEqual(existing.Annotations, desired.Annotations) { + update = true + existing.Annotations = desired.Annotations + } + + return update, nil +} diff --git a/pkg/envoygateway/utils.go b/pkg/envoygateway/utils.go new file mode 100644 index 000000000..e06d5fec8 --- /dev/null +++ b/pkg/envoygateway/utils.go @@ -0,0 +1,61 @@ +package envoygateway + +import ( + egv1alpha1 "github.com/envoyproxy/gateway/api/v1alpha1" + "k8s.io/apimachinery/pkg/api/meta" + + kuadrantgatewayapi "github.com/kuadrant/kuadrant-operator/pkg/library/gatewayapi" +) + +func IsEnvoyPatchPolicyInstalled(restMapper meta.RESTMapper) (bool, error) { + return kuadrantgatewayapi.IsCRDInstalled( + restMapper, + egv1alpha1.GroupName, + egv1alpha1.KindEnvoyPatchPolicy, + egv1alpha1.GroupVersion.Version) +} + +func IsEnvoyExtensionPolicyInstalled(restMapper meta.RESTMapper) (bool, error) { + return kuadrantgatewayapi.IsCRDInstalled( + restMapper, + egv1alpha1.GroupName, + egv1alpha1.KindEnvoyExtensionPolicy, + egv1alpha1.GroupVersion.Version) +} + +func IsEnvoyGatewaySecurityPolicyInstalled(restMapper meta.RESTMapper) (bool, error) { + return kuadrantgatewayapi.IsCRDInstalled( + restMapper, + egv1alpha1.GroupName, + egv1alpha1.KindSecurityPolicy, + egv1alpha1.GroupVersion.Version) +} + +func IsEnvoyGatewayInstalled(restMapper meta.RESTMapper) (bool, error) { + ok, err := IsEnvoyGatewaySecurityPolicyInstalled(restMapper) + if err != nil { + return false, err + } + if !ok { + return false, nil + } + + ok, err = IsEnvoyExtensionPolicyInstalled(restMapper) + if err != nil { + return false, err + } + if !ok { + return false, nil + } + + ok, err = IsEnvoyPatchPolicyInstalled(restMapper) + if err != nil { + return false, err + } + if !ok { + return false, nil + } + + // EnvoyGateway found + return true, nil +} diff --git a/pkg/istio/mesh_config.go b/pkg/istio/mesh_config.go index 68574c6d3..ed7478509 100644 --- a/pkg/istio/mesh_config.go +++ b/pkg/istio/mesh_config.go @@ -15,6 +15,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" maistrav2 "github.com/kuadrant/kuadrant-operator/api/external/maistra/v2" + "github.com/kuadrant/kuadrant-operator/pkg/library/kuadrant" ) const ( @@ -52,7 +53,7 @@ func createKuadrantAuthorizer(namespace string) *istiomeshv1alpha1.MeshConfig_Ex envoyExtAuthGRPC := &istiomeshv1alpha1.MeshConfig_ExtensionProvider_EnvoyExtAuthzGrpc{ EnvoyExtAuthzGrpc: &istiomeshv1alpha1.MeshConfig_ExtensionProvider_EnvoyExternalAuthorizationGrpcProvider{ Port: 50051, - Service: fmt.Sprintf("authorino-authorino-authorization.%s.svc.cluster.local", namespace), + Service: fmt.Sprintf("%s.%s.svc.cluster.local", kuadrant.AuthorinoServiceName, namespace), }, } return &istiomeshv1alpha1.MeshConfig_ExtensionProvider{ diff --git a/pkg/istio/mesh_config_test.go b/pkg/istio/mesh_config_test.go index ed1a6c8a1..fc2599512 100644 --- a/pkg/istio/mesh_config_test.go +++ b/pkg/istio/mesh_config_test.go @@ -3,8 +3,10 @@ package istio import ( + "fmt" "testing" + "github.com/kuadrant/kuadrant-operator/pkg/library/kuadrant" "google.golang.org/protobuf/types/known/structpb" "gotest.tools/assert" istiomeshv1alpha1 "istio.io/api/mesh/v1alpha1" @@ -41,7 +43,7 @@ func TestKuadrantAuthorizer_GetExtensionProvider(t *testing.T) { provider := authorizer.GetExtensionProvider() assert.Equal(t, provider.Name, ExtAuthorizerName) - assert.Equal(t, provider.GetEnvoyExtAuthzGrpc().Service, "authorino-authorino-authorization.default.svc.cluster.local") + assert.Equal(t, provider.GetEnvoyExtAuthzGrpc().Service, fmt.Sprintf("%s.default.svc.cluster.local", kuadrant.AuthorinoServiceName)) } func TestHasKuadrantAuthorizer(t *testing.T) { diff --git a/pkg/istio/mutators.go b/pkg/istio/mutators.go index 3126c131f..b332ee2d6 100644 --- a/pkg/istio/mutators.go +++ b/pkg/istio/mutators.go @@ -5,6 +5,7 @@ import ( "reflect" istioclientgoextensionv1alpha1 "istio.io/client-go/pkg/apis/extensions/v1alpha1" + istiov1beta1 "istio.io/client-go/pkg/apis/security/v1beta1" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/kuadrant/kuadrant-operator/pkg/rlptools/wasm" @@ -39,3 +40,43 @@ func WASMPluginMutator(existingObj, desiredObj client.Object) (bool, error) { return update, nil } + +func AuthorizationPolicyMutator(existingObj, desiredObj client.Object) (bool, error) { + existing, ok := existingObj.(*istiov1beta1.AuthorizationPolicy) + if !ok { + return false, fmt.Errorf("%T is not an *istiov1beta1.AuthorizationPolicy", existingObj) + } + desired, ok := desiredObj.(*istiov1beta1.AuthorizationPolicy) + if !ok { + return false, fmt.Errorf("%T is not an *istiov1beta1.AuthorizationPolicy", desiredObj) + } + + var update bool + + if !reflect.DeepEqual(existing.Spec.Action, desired.Spec.Action) { + update = true + existing.Spec.Action = desired.Spec.Action + } + + if !reflect.DeepEqual(existing.Spec.ActionDetail, desired.Spec.ActionDetail) { + update = true + existing.Spec.ActionDetail = desired.Spec.ActionDetail + } + + if !reflect.DeepEqual(existing.Spec.Rules, desired.Spec.Rules) { + update = true + existing.Spec.Rules = desired.Spec.Rules + } + + if !reflect.DeepEqual(existing.Spec.Selector, desired.Spec.Selector) { + update = true + existing.Spec.Selector = desired.Spec.Selector + } + + if !reflect.DeepEqual(existing.Annotations, desired.Annotations) { + update = true + existing.Annotations = desired.Annotations + } + + return update, nil +} diff --git a/pkg/istio/utils.go b/pkg/istio/utils.go index e8b0c3817..277b6af98 100644 --- a/pkg/istio/utils.go +++ b/pkg/istio/utils.go @@ -9,7 +9,6 @@ import ( istioclientnetworkingv1alpha3 "istio.io/client-go/pkg/apis/networking/v1alpha3" istioclientgosecurityv1beta1 "istio.io/client-go/pkg/apis/security/v1beta1" "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/controller-runtime/pkg/client" gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" @@ -37,54 +36,27 @@ func PolicyTargetRefFromGateway(gateway *gatewayapiv1.Gateway) *istiocommon.Poli } func IsEnvoyFilterInstalled(restMapper meta.RESTMapper) (bool, error) { - _, err := restMapper.RESTMapping( - schema.GroupKind{Group: istioclientnetworkingv1alpha3.GroupName, Kind: "EnvoyFilter"}, - istioclientnetworkingv1alpha3.SchemeGroupVersion.Version, - ) - - if err == nil { - return true, nil - } - - if meta.IsNoMatchError(err) { - return false, nil - } - - return false, err + return kuadrantgatewayapi.IsCRDInstalled( + restMapper, + istioclientnetworkingv1alpha3.GroupName, + "EnvoyFilter", + istioclientnetworkingv1alpha3.SchemeGroupVersion.Version) } func IsWASMPluginInstalled(restMapper meta.RESTMapper) (bool, error) { - _, err := restMapper.RESTMapping( - schema.GroupKind{Group: istioclientgoextensionv1alpha1.GroupName, Kind: "WasmPlugin"}, - istioclientgoextensionv1alpha1.SchemeGroupVersion.Version, - ) - - if err == nil { - return true, nil - } - - if meta.IsNoMatchError(err) { - return false, nil - } - - return false, err + return kuadrantgatewayapi.IsCRDInstalled( + restMapper, + istioclientgoextensionv1alpha1.GroupName, + "WasmPlugin", + istioclientgoextensionv1alpha1.SchemeGroupVersion.Version) } func IsAuthorizationPolicyInstalled(restMapper meta.RESTMapper) (bool, error) { - _, err := restMapper.RESTMapping( - schema.GroupKind{Group: istioclientgosecurityv1beta1.GroupName, Kind: "AuthorizationPolicy"}, - istioclientgosecurityv1beta1.SchemeGroupVersion.Version, - ) - - if err == nil { - return true, nil - } - - if meta.IsNoMatchError(err) { - return false, nil - } - - return false, err + return kuadrantgatewayapi.IsCRDInstalled( + restMapper, + istioclientgosecurityv1beta1.GroupName, + "AuthorizationPolicy", + istioclientgosecurityv1beta1.SchemeGroupVersion.Version) } func IsIstioInstalled(restMapper meta.RESTMapper) (bool, error) { diff --git a/pkg/kuadranttools/topology_tools.go b/pkg/kuadranttools/topology_tools.go new file mode 100644 index 000000000..7fb6edb04 --- /dev/null +++ b/pkg/kuadranttools/topology_tools.go @@ -0,0 +1,97 @@ +package kuadranttools + +import ( + "context" + + "github.com/go-logr/logr" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + 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/utils" +) + +func TopologyFromGateway(ctx context.Context, cl client.Client, gw *gatewayapiv1.Gateway, policyType kuadrantgatewayapi.PolicyType) (*kuadrantgatewayapi.Topology, error) { + logger, err := logr.FromContext(ctx) + if err != nil { + return nil, err + } + + routeList := &gatewayapiv1.HTTPRouteList{} + // Get all the routes having the gateway as parent + err = cl.List( + ctx, + routeList, + client.MatchingFields{ + fieldindexers.HTTPRouteGatewayParentField: client.ObjectKeyFromObject(gw).String(), + }) + logger.V(1).Info("TopologyFromGateway: list httproutes from gateway", + "gateway", client.ObjectKeyFromObject(gw), + "#HTTPRoutes", len(routeList.Items), + "err", err) + if err != nil { + return nil, err + } + + // Get all the policyKind policies + policies, err := policyType.GetList(ctx, cl) + logger.V(1).Info("TopologyFromGateway: list policies", + "#policies", len(policies), + "err", err) + if err != nil { + return nil, err + } + + return kuadrantgatewayapi.NewTopology( + kuadrantgatewayapi.WithGateways([]*gatewayapiv1.Gateway{gw}), + kuadrantgatewayapi.WithRoutes(utils.Map(routeList.Items, ptr.To[gatewayapiv1.HTTPRoute])), + kuadrantgatewayapi.WithPolicies(policies), + kuadrantgatewayapi.WithLogger(logger), + ) +} + +func TopologyForPolicies(ctx context.Context, cl client.Client, policyType kuadrantgatewayapi.PolicyType) (*kuadrantgatewayapi.Topology, error) { + logger, err := logr.FromContext(ctx) + if err != nil { + return nil, err + } + + gatewayList := &gatewayapiv1.GatewayList{} + err = cl.List( + ctx, + gatewayList) + logger.V(1).Info("TopologyForPolicies: list all gateways", + "#Gateways", len(gatewayList.Items), + "err", err) + if err != nil { + return nil, err + } + + routeList := &gatewayapiv1.HTTPRouteList{} + err = cl.List( + ctx, + routeList) + logger.V(1).Info("TopologyForPolicies: list all httproutes", + "#HTTPRoutes", len(routeList.Items), + "err", err) + if err != nil { + return nil, err + } + + policies, err := policyType.GetList(ctx, cl) + logger.V(1).Info("TopologyForPolicies: list policies", + "#policies", len(policies), + "err", err) + if err != nil { + return nil, err + } + + return kuadrantgatewayapi.NewTopology( + kuadrantgatewayapi.WithGateways(utils.Map(gatewayList.Items, ptr.To[gatewayapiv1.Gateway])), + kuadrantgatewayapi.WithRoutes(utils.Map(routeList.Items, ptr.To[gatewayapiv1.HTTPRoute])), + kuadrantgatewayapi.WithPolicies(policies), + kuadrantgatewayapi.WithLogger(logger), + ) +} diff --git a/pkg/library/gatewayapi/topology.go b/pkg/library/gatewayapi/topology.go index 29422d87d..9632aa249 100644 --- a/pkg/library/gatewayapi/topology.go +++ b/pkg/library/gatewayapi/topology.go @@ -23,6 +23,8 @@ const ( type PolicyTargetNode interface { GetGatewayNode() *GatewayNode GetRouteNode() *RouteNode + GetObject() client.Object + AttachedPolicies() []PolicyNode } type PolicyNode struct { @@ -81,6 +83,10 @@ func (r *RouteNode) ObjectKey() client.ObjectKey { return client.ObjectKeyFromObject(r.HTTPRoute) } +func (r *RouteNode) GetObject() client.Object { + return r.Route() +} + func (r *RouteNode) GetGatewayNode() *GatewayNode { return nil } @@ -130,6 +136,10 @@ func (g *GatewayNode) ObjectKey() client.ObjectKey { return client.ObjectKeyFromObject(g.Gateway) } +func (g *GatewayNode) GetObject() client.Object { + return g.GetGateway() +} + func (g *GatewayNode) GetGatewayNode() *GatewayNode { return g } @@ -309,18 +319,18 @@ type edge struct { } func buildDAGEdges(opts *topologyOptions, gateways []gatewayDAGNode, routes []httpRouteDAGNode, policies []policyDAGNode) []edge { - effectiveGatewys := gateways + effectiveGateways := gateways if opts.programmedGatewaysOnly { // filter out not programmed gateways - effectiveGatewys = utils.Filter(gateways, func(g gatewayDAGNode) bool { + effectiveGateways = utils.Filter(gateways, func(g gatewayDAGNode) bool { return meta.IsStatusConditionTrue(g.Status.Conditions, string(gatewayapiv1.GatewayConditionProgrammed)) }) } // internal index: key -> gateway for reference - gatewaysIndex := make(map[client.ObjectKey]gatewayDAGNode, len(effectiveGatewys)) - for _, gateway := range effectiveGatewys { + gatewaysIndex := make(map[client.ObjectKey]gatewayDAGNode, len(effectiveGateways)) + for _, gateway := range effectiveGateways { gatewaysIndex[client.ObjectKeyFromObject(gateway.Gateway)] = gateway } @@ -359,7 +369,7 @@ func buildDAGEdges(opts *topologyOptions, gateways []gatewayDAGNode, routes []ht } } - for _, g := range effectiveGatewys { + for _, g := range effectiveGateways { // Compute gateway's child (attached) policies attachedPolicies := utils.Filter(policies, func(p policyDAGNode) bool { group := p.GetTargetRef().Group diff --git a/pkg/library/gatewayapi/topology_indexes.go b/pkg/library/gatewayapi/topology_indexes.go index dfe435865..7f1ff6310 100644 --- a/pkg/library/gatewayapi/topology_indexes.go +++ b/pkg/library/gatewayapi/topology_indexes.go @@ -22,6 +22,11 @@ type TopologyIndexes struct { // Type: Policy -> HTTPRoute policyRoute map[client.ObjectKey]*gatewayapiv1.HTTPRoute + // policyTarget is an index of policies mapping to objects + // The index only includes policies targeting either Gateways or existing and accepted (by parent gateways) HTTPRoutes + // Type: Policy -> client.Object + policyTarget map[client.ObjectKey]client.Object + // untargetedRoutes is an index of gateways mapping to HTTPRoutes not targeted by a kuadrant policy // Gateway -> []HTTPRoute untargetedRoutes map[client.ObjectKey][]*gatewayapiv1.HTTPRoute @@ -39,6 +44,7 @@ func NewTopologyIndexes(t *Topology) *TopologyIndexes { return &TopologyIndexes{ gatewayPolicies: buildGatewayPoliciesIndex(t), policyRoute: buildPolicyRouteIndex(t), + policyTarget: buildPolicyTargetIndex(t), untargetedRoutes: buildUntargetedRoutesIndex(t), internalTopology: t, } @@ -58,6 +64,12 @@ func (k *TopologyIndexes) GetPolicyHTTPRoute(policy Policy) *gatewayapiv1.HTTPRo return k.policyRoute[client.ObjectKeyFromObject(policy)] } +// GetPolicyTargetObject returns the client.Object targeted by the policy. +// Type: Policy -> client.Object +func (k *TopologyIndexes) GetPolicyTargetObject(policy Policy) client.Object { + return k.policyTarget[client.ObjectKeyFromObject(policy)] +} + // GetUntargetedRoutes returns the HTTPRoutes not targeted by any kuadrant policy // having the gateway given as input as parent. // Gateway -> []HTTPRoute @@ -92,6 +104,17 @@ func (k *TopologyIndexes) String() string { return index }() + policiesTargetingObjects := func() map[string]string { + index := make(map[string]string) + for policyKey, target := range k.policyTarget { + index[policyKey.String()] = client.ObjectKeyFromObject(target).String() + } + if len(index) == 0 { + return nil + } + return index + }() + untargetedRoutesPerGateway := func() map[string][]string { index := make(map[string][]string, 0) for gatewayKey, routeList := range k.untargetedRoutes { @@ -108,10 +131,12 @@ func (k *TopologyIndexes) String() string { indexesRepr := struct { GatewayPolicies map[string][]string `json:"policiesPerGateway"` PolicyRoute map[string]string `json:"policiesTargetingRoutes"` + PolicyTarget map[string]string `json:"policiesTargetingObjects"` UntargetedRoutes map[string][]string `json:"untargetedRoutesPerGateway"` }{ policiesPerGateway, policiesTargetingRoutes, + policiesTargetingObjects, untargetedRoutesPerGateway, } @@ -157,6 +182,24 @@ func buildPolicyRouteIndex(t *Topology) map[client.ObjectKey]*gatewayapiv1.HTTPR return index } +func buildPolicyTargetIndex(t *Topology) map[client.ObjectKey]client.Object { + index := make(map[client.ObjectKey]client.Object) + for _, gatewayNode := range t.Gateways() { + obj := gatewayNode.GetObject() + for _, policy := range gatewayNode.AttachedPolicies() { + index[client.ObjectKeyFromObject(policy)] = obj + } + } + for _, routeNode := range t.Routes() { + obj := routeNode.GetObject() + for _, policy := range routeNode.AttachedPolicies() { + index[client.ObjectKeyFromObject(policy)] = obj + } + } + + return index +} + func buildUntargetedRoutesIndex(t *Topology) map[client.ObjectKey][]*gatewayapiv1.HTTPRoute { // Build Gateway -> []HTTPRoute index with all the routes not targeted by a policy index := make(map[client.ObjectKey][]*gatewayapiv1.HTTPRoute, 0) diff --git a/pkg/library/gatewayapi/utils.go b/pkg/library/gatewayapi/utils.go index c1fa51c62..cf016111c 100644 --- a/pkg/library/gatewayapi/utils.go +++ b/pkg/library/gatewayapi/utils.go @@ -154,21 +154,21 @@ func FilterValidSubdomains(domains, subdomains []gatewayapiv1.Hostname) []gatewa } func IsGatewayAPIInstalled(restMapper meta.RESTMapper) (bool, error) { - return isCRDInstalled(restMapper, gatewayapiv1.GroupName, "HTTPRoute", gatewayapiv1.GroupVersion.Version) + return IsCRDInstalled(restMapper, gatewayapiv1.GroupName, "HTTPRoute", gatewayapiv1.GroupVersion.Version) } func IsCertManagerInstalled(restMapper meta.RESTMapper, logger logr.Logger) (bool, error) { - if ok, err := isCRDInstalled(restMapper, certmanager.GroupName, certmanv1.CertificateKind, certmanv1.SchemeGroupVersion.Version); !ok || err != nil { + if ok, err := IsCRDInstalled(restMapper, certmanager.GroupName, certmanv1.CertificateKind, certmanv1.SchemeGroupVersion.Version); !ok || err != nil { logger.V(1).Error(err, "CertManager CRD was not installed", "group", certmanager.GroupName, "kind", certmanv1.CertificateKind, "version", certmanv1.SchemeGroupVersion.Version) return false, err } - if ok, err := isCRDInstalled(restMapper, certmanager.GroupName, certmanv1.IssuerKind, certmanv1.SchemeGroupVersion.Version); !ok || err != nil { + if ok, err := IsCRDInstalled(restMapper, certmanager.GroupName, certmanv1.IssuerKind, certmanv1.SchemeGroupVersion.Version); !ok || err != nil { logger.V(1).Error(err, "CertManager CRD was not installed", "group", certmanager.GroupName, "kind", certmanv1.IssuerKind, "version", certmanv1.SchemeGroupVersion.Version) return false, err } - if ok, err := isCRDInstalled(restMapper, certmanager.GroupName, certmanv1.ClusterIssuerKind, certmanv1.SchemeGroupVersion.Version); !ok || err != nil { + if ok, err := IsCRDInstalled(restMapper, certmanager.GroupName, certmanv1.ClusterIssuerKind, certmanv1.SchemeGroupVersion.Version); !ok || err != nil { logger.V(1).Error(err, "CertManager CRD was not installed", "group", certmanager.GroupName, "kind", certmanv1.ClusterIssuerKind, "version", certmanv1.SchemeGroupVersion.Version) return false, err } @@ -176,7 +176,7 @@ func IsCertManagerInstalled(restMapper meta.RESTMapper, logger logr.Logger) (boo return true, nil } -func isCRDInstalled(restMapper meta.RESTMapper, group, kind, version string) (bool, error) { +func IsCRDInstalled(restMapper meta.RESTMapper, group, kind, version string) (bool, error) { _, err := restMapper.RESTMapping( schema.GroupKind{Group: group, Kind: kind}, version, diff --git a/pkg/library/kuadrant/kuadrant.go b/pkg/library/kuadrant/kuadrant.go index a607f5009..e50a6d033 100644 --- a/pkg/library/kuadrant/kuadrant.go +++ b/pkg/library/kuadrant/kuadrant.go @@ -20,6 +20,7 @@ const ( KuadrantNamespaceAnnotation = "kuadrant.io/namespace" TopologyLabel = "kuadrant.io/topology" ControllerName = "kuadrant.io/policy-controller" + AuthorinoServiceName = "authorino-authorino-authorization" ) type Policy interface { diff --git a/pkg/library/mappers/envoysecuritypolicy_to_kuadrant.go b/pkg/library/mappers/envoysecuritypolicy_to_kuadrant.go new file mode 100644 index 000000000..8c70642f2 --- /dev/null +++ b/pkg/library/mappers/envoysecuritypolicy_to_kuadrant.go @@ -0,0 +1,54 @@ +package mappers + +import ( + "context" + "fmt" + + egv1alpha1 "github.com/envoyproxy/gateway/api/v1alpha1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" + "github.com/kuadrant/kuadrant-operator/pkg/library/kuadrant" +) + +type SecurityPolicyToKuadrantEventMapper struct { + opts MapperOptions +} + +func NewSecurityPolicyToKuadrantEventMapper(o ...MapperOption) *SecurityPolicyToKuadrantEventMapper { + return &SecurityPolicyToKuadrantEventMapper{opts: Apply(o...)} +} + +func (m *SecurityPolicyToKuadrantEventMapper) Map(ctx context.Context, obj client.Object) []reconcile.Request { + logger := m.opts.Logger.WithValues("object", client.ObjectKeyFromObject(obj)) + + esp, ok := obj.(*egv1alpha1.SecurityPolicy) + if !ok { + logger.Error(fmt.Errorf("%T is not a *egv1alpha1.SecurityPolicy", obj), "cannot map") + return []reconcile.Request{} + } + + if !kuadrant.IsKuadrantManaged(esp) { + logger.V(1).Info("SecurityPolicy is not kuadrant managed", "securitypolicy", esp) + return []reconcile.Request{} + } + + kuadrantNamespace, err := kuadrant.GetKuadrantNamespace(esp) + if err != nil { + logger.Error(err, "cannot get kuadrant namespace") + return []reconcile.Request{} + } + kuadrantList := &kuadrantv1beta1.KuadrantList{} + err = m.opts.Client.List(ctx, kuadrantList, &client.ListOptions{Namespace: kuadrantNamespace}) + if err != nil { + logger.Error(err, "cannot list kuadrants") + return []reconcile.Request{} + } + if len(kuadrantList.Items) == 0 { + logger.Error(err, "kuadrant does not exist in expected namespace") + return []reconcile.Request{} + } + + return []reconcile.Request{{NamespacedName: client.ObjectKeyFromObject(&kuadrantList.Items[0])}} +} diff --git a/pkg/library/mappers/gateway_to_kuadrant.go b/pkg/library/mappers/gateway_to_kuadrant.go new file mode 100644 index 000000000..e8e87679c --- /dev/null +++ b/pkg/library/mappers/gateway_to_kuadrant.go @@ -0,0 +1,54 @@ +package mappers + +import ( + "context" + "fmt" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" + + kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" + "github.com/kuadrant/kuadrant-operator/pkg/library/kuadrant" +) + +type GatewayToKuadrantEventMapper struct { + opts MapperOptions +} + +func NewGatewayToKuadrantEventMapper(o ...MapperOption) *GatewayToKuadrantEventMapper { + return &GatewayToKuadrantEventMapper{opts: Apply(o...)} +} + +func (m *GatewayToKuadrantEventMapper) Map(ctx context.Context, obj client.Object) []reconcile.Request { + logger := m.opts.Logger.WithValues("object", client.ObjectKeyFromObject(obj)) + + gw, ok := obj.(*gatewayapiv1.Gateway) + if !ok { + logger.Error(fmt.Errorf("%T is not a *gatweayapiv1.Gateway", obj), "cannot map") + return []reconcile.Request{} + } + + if !kuadrant.IsKuadrantManaged(gw) { + logger.V(1).Info("gateway is not kuadrant managed", "gateway", gw) + return []reconcile.Request{} + } + + kuadrantNamespace, err := kuadrant.GetKuadrantNamespace(gw) + if err != nil { + logger.Error(err, "cannot get kuadrant namespace") + return []reconcile.Request{} + } + kuadrantList := &kuadrantv1beta1.KuadrantList{} + err = m.opts.Client.List(ctx, kuadrantList, &client.ListOptions{Namespace: kuadrantNamespace}) + if err != nil { + logger.Error(err, "cannot list kuadrants") + return []reconcile.Request{} + } + if len(kuadrantList.Items) == 0 { + logger.Error(err, "kuadrant does not exist in expected namespace") + return []reconcile.Request{} + } + + return []reconcile.Request{{NamespacedName: client.ObjectKeyFromObject(&kuadrantList.Items[0])}} +} diff --git a/pkg/library/mappers/httproute_to_kuadrant.go b/pkg/library/mappers/httproute_to_kuadrant.go new file mode 100644 index 000000000..28fbb6c43 --- /dev/null +++ b/pkg/library/mappers/httproute_to_kuadrant.go @@ -0,0 +1,66 @@ +package mappers + +import ( + "context" + "fmt" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" + + kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" + kuadrantgatewayapi "github.com/kuadrant/kuadrant-operator/pkg/library/gatewayapi" + "github.com/kuadrant/kuadrant-operator/pkg/library/kuadrant" +) + +type HTTPRouteToKuadrantEventMapper struct { + opts MapperOptions +} + +func NewHTTPRouteToKuadrantEventMapper(o ...MapperOption) *HTTPRouteToKuadrantEventMapper { + return &HTTPRouteToKuadrantEventMapper{opts: Apply(o...)} +} + +func (m *HTTPRouteToKuadrantEventMapper) Map(ctx context.Context, obj client.Object) []reconcile.Request { + logger := m.opts.Logger.WithValues("object", client.ObjectKeyFromObject(obj)) + + httpRoute, ok := obj.(*gatewayapiv1.HTTPRoute) + if !ok { + logger.Error(fmt.Errorf("%T is not a *gatweayapiv1.HTTPRoute", obj), "cannot map") + 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 + } + + if !kuadrant.IsKuadrantManaged(gateway) { + logger.V(1).Info("gateway is not kuadrant managed", "gateway", gateway) + continue + } + kuadrantNamespace, err := kuadrant.GetKuadrantNamespace(gateway) + if err != nil { + logger.Error(err, "cannot get kuadrant namespace") + continue + } + kuadrantList := &kuadrantv1beta1.KuadrantList{} + err = m.opts.Client.List(ctx, kuadrantList, &client.ListOptions{Namespace: kuadrantNamespace}) + if err != nil { + logger.Error(err, "cannot list kuadrants") + return []reconcile.Request{} + } + if len(kuadrantList.Items) == 0 { + logger.Error(err, "kuadrant does not exist in expected namespace") + return []reconcile.Request{} + } + return []reconcile.Request{{NamespacedName: client.ObjectKeyFromObject(&kuadrantList.Items[0])}} + } + logger.V(1).Info("no matching kuadrant instance found") + return []reconcile.Request{} +} diff --git a/pkg/library/mappers/kuadrant_list_mapper.go b/pkg/library/mappers/kuadrant_list_mapper.go new file mode 100644 index 000000000..d3554edce --- /dev/null +++ b/pkg/library/mappers/kuadrant_list_mapper.go @@ -0,0 +1,33 @@ +package mappers + +import ( + "context" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" +) + +type KuadrantListEventMapper struct { + opts MapperOptions +} + +func NewKuadrantListEventMapper(o ...MapperOption) *KuadrantListEventMapper { + return &KuadrantListEventMapper{opts: Apply(o...)} +} + +func (m *KuadrantListEventMapper) Map(ctx context.Context, _ client.Object) []reconcile.Request { + kuadrantList := &kuadrantv1beta1.KuadrantList{} + err := m.opts.Client.List(ctx, kuadrantList) + if err != nil { + m.opts.Logger.Error(err, "cannot list kuadrants") + return []reconcile.Request{} + } + if len(kuadrantList.Items) == 0 { + m.opts.Logger.Error(err, "kuadrant does not exist in expected namespace") + return []reconcile.Request{} + } + + return []reconcile.Request{{NamespacedName: client.ObjectKeyFromObject(&kuadrantList.Items[0])}} +} diff --git a/pkg/library/mappers/policy_to_kuadrant.go b/pkg/library/mappers/policy_to_kuadrant.go new file mode 100644 index 000000000..4fc96cfe9 --- /dev/null +++ b/pkg/library/mappers/policy_to_kuadrant.go @@ -0,0 +1,48 @@ +package mappers + +import ( + "context" + "fmt" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" + "github.com/kuadrant/kuadrant-operator/pkg/library/kuadrant" +) + +type PolicyToKuadrantEventMapper struct { + opts MapperOptions +} + +func NewPolicyToKuadrantEventMapper(o ...MapperOption) *PolicyToKuadrantEventMapper { + return &PolicyToKuadrantEventMapper{opts: Apply(o...)} +} + +func (m *PolicyToKuadrantEventMapper) Map(ctx context.Context, obj client.Object) []reconcile.Request { + logger := m.opts.Logger.WithValues("object", client.ObjectKeyFromObject(obj)) + + policy, ok := obj.(kuadrant.Policy) + if !ok { + logger.Error(fmt.Errorf("%T is not a kuadrant.Policy", obj), "cannot map") + return []reconcile.Request{} + } + + kuadrantNamespace, err := kuadrant.GetKuadrantNamespaceFromPolicyTargetRef(ctx, m.opts.Client, policy) + if err != nil { + logger.Error(err, "cannot get kuadrant namespace") + return []reconcile.Request{} + } + kuadrantList := &kuadrantv1beta1.KuadrantList{} + err = m.opts.Client.List(ctx, kuadrantList, &client.ListOptions{Namespace: kuadrantNamespace}) + if err != nil { + logger.Error(err, "cannot list kuadrants") + return []reconcile.Request{} + } + if len(kuadrantList.Items) == 0 { + logger.Error(err, "kuadrant does not exist in expected namespace") + return []reconcile.Request{} + } + + return []reconcile.Request{{NamespacedName: client.ObjectKeyFromObject(&kuadrantList.Items[0])}} +} diff --git a/pkg/rlptools/topology_index.go b/pkg/rlptools/topology_index.go deleted file mode 100644 index 4b067b8f3..000000000 --- a/pkg/rlptools/topology_index.go +++ /dev/null @@ -1,69 +0,0 @@ -package rlptools - -import ( - "context" - - "github.com/go-logr/logr" - "k8s.io/utils/ptr" - "sigs.k8s.io/controller-runtime/pkg/client" - gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" - - kuadrantv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2" - "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" -) - -func TopologyIndexesFromGateway(ctx context.Context, cl client.Client, gw *gatewayapiv1.Gateway) (*kuadrantgatewayapi.TopologyIndexes, error) { - logger, err := logr.FromContext(ctx) - if err != nil { - return nil, err - } - - routeList := &gatewayapiv1.HTTPRouteList{} - // Get all the routes having the gateway as parent - err = cl.List( - ctx, - routeList, - client.MatchingFields{ - fieldindexers.HTTPRouteGatewayParentField: client.ObjectKeyFromObject(gw).String(), - }) - logger.V(1).Info("topologyIndexesFromGateway: list httproutes from gateway", - "gateway", client.ObjectKeyFromObject(gw), - "#HTTPRoutes", len(routeList.Items), - "err", err) - if err != nil { - return nil, err - } - - rlpList := &kuadrantv1beta2.RateLimitPolicyList{} - // Get all the rate limit policies - err = cl.List(ctx, rlpList) - logger.V(1).Info("topologyIndexesFromGateway: list rate limit policies", - "#RLPS", len(rlpList.Items), - "err", err) - if err != nil { - return nil, err - } - - policies := utils.Map(rlpList.Items, func(p kuadrantv1beta2.RateLimitPolicy) kuadrantgatewayapi.Policy { return &p }) - - topology, err := kuadrantgatewayapi.NewTopology( - kuadrantgatewayapi.WithAcceptedRoutesLinkedOnly(), - kuadrantgatewayapi.WithProgrammedGatewaysOnly(), - kuadrantgatewayapi.WithGateways([]*gatewayapiv1.Gateway{gw}), - kuadrantgatewayapi.WithRoutes(utils.Map(routeList.Items, ptr.To)), - kuadrantgatewayapi.WithPolicies(policies), - kuadrantgatewayapi.WithLogger(logger), - ) - if err != nil { - return nil, err - } - - overriddenTopology, err := ApplyOverrides(topology, gw) - if err != nil { - return nil, err - } - - return kuadrantgatewayapi.NewTopologyIndexes(overriddenTopology), nil -} diff --git a/pkg/rlptools/utils.go b/pkg/rlptools/utils.go index b90653287..a8ca94d92 100644 --- a/pkg/rlptools/utils.go +++ b/pkg/rlptools/utils.go @@ -12,7 +12,7 @@ import ( ) func LimitsNameFromRLP(rlp *kuadrantv1beta2.RateLimitPolicy) string { - return LimitsNamespaceFromRLP(rlp) + return wasm.LimitsNamespaceFromRLP(rlp) } var timeUnitMap = map[kuadrantv1beta2.TimeUnit]int{ @@ -22,14 +22,10 @@ var timeUnitMap = map[kuadrantv1beta2.TimeUnit]int{ kuadrantv1beta2.TimeUnit("day"): 60 * 60 * 24, } -func LimitsNamespaceFromRLP(rlp *kuadrantv1beta2.RateLimitPolicy) string { - return fmt.Sprintf("%s/%s", rlp.GetNamespace(), rlp.GetName()) -} - // LimitadorRateLimitsFromRLP converts rate limits from a Kuadrant RateLimitPolicy into a list of Limitador rate limit // objects func LimitadorRateLimitsFromRLP(rlp *kuadrantv1beta2.RateLimitPolicy) []limitadorv1alpha1.RateLimit { - limitsNamespace := LimitsNamespaceFromRLP(rlp) + limitsNamespace := wasm.LimitsNamespaceFromRLP(rlp) rlpKey := client.ObjectKeyFromObject(rlp) rateLimits := make([]limitadorv1alpha1.RateLimit, 0) diff --git a/pkg/rlptools/wasm/types.go b/pkg/rlptools/wasm/types.go index ea9d007e2..2e470e6a5 100644 --- a/pkg/rlptools/wasm/types.go +++ b/pkg/rlptools/wasm/types.go @@ -5,6 +5,7 @@ import ( "errors" _struct "google.golang.org/protobuf/types/known/structpb" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" kuadrantv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2" @@ -116,6 +117,15 @@ func (w *Config) ToStruct() (*_struct.Struct, error) { return configStruct, nil } +func (w *Config) ToJSON() (*apiextensionsv1.JSON, error) { + configJSON, err := json.Marshal(w) + if err != nil { + return nil, err + } + + return &apiextensionsv1.JSON{Raw: configJSON}, nil +} + func ConfigFromStruct(structure *_struct.Struct) (*Config, error) { if structure == nil { return nil, errors.New("cannot desestructure config from nil") @@ -133,3 +143,16 @@ func ConfigFromStruct(structure *_struct.Struct) (*Config, error) { return config, nil } + +func ConfigFromJSON(configJSON *apiextensionsv1.JSON) (*Config, error) { + if configJSON == nil { + return nil, errors.New("cannot desestructure config from nil") + } + + config := &Config{} + if err := json.Unmarshal(configJSON.Raw, config); err != nil { + return nil, err + } + + return config, nil +} diff --git a/pkg/rlptools/wasm/utils.go b/pkg/rlptools/wasm/utils.go index a190aed6a..afb192303 100644 --- a/pkg/rlptools/wasm/utils.go +++ b/pkg/rlptools/wasm/utils.go @@ -1,14 +1,17 @@ package wasm import ( + "context" "crypto/sha256" "encoding/hex" "errors" "fmt" "slices" + "sort" "strings" "unicode" + "github.com/go-logr/logr" "github.com/samber/lo" "k8s.io/apimachinery/pkg/types" "k8s.io/utils/env" @@ -16,6 +19,9 @@ import ( gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" kuadrantv1beta2 "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/utils" ) const ( @@ -26,6 +32,10 @@ var ( WASMFilterImageURL = env.GetString("RELATED_IMAGE_WASMSHIM", "oci://quay.io/kuadrant/wasm-shim:latest") ) +func LimitsNamespaceFromRLP(rlp *kuadrantv1beta2.RateLimitPolicy) string { + return fmt.Sprintf("%s/%s", rlp.GetNamespace(), rlp.GetName()) +} + // Rules computes WASM rules from the policy and the targeted route. // It returns an empty list of wasm rules if the policy specifies no limits or if all limits specified in the policy // fail to match any route rule according to the limits route selectors. @@ -287,3 +297,129 @@ func dataFromLimit(limitIdentifier string, limit *kuadrantv1beta2.Limit) (data [ return data } + +func routeFromRLP(ctx context.Context, t *kuadrantgatewayapi.TopologyIndexes, rlp *kuadrantv1beta2.RateLimitPolicy, gw *gatewayapiv1.Gateway) (*gatewayapiv1.HTTPRoute, error) { + logger, err := logr.FromContext(ctx) + if err != nil { + return nil, err + } + + route := t.GetPolicyHTTPRoute(rlp) + + if route == nil { + // The policy is targeting a gateway + // This gateway policy will be enforced into all HTTPRoutes that do not have a policy attached to it + + // Build imaginary route with all the routes not having a RLP targeting it + untargetedRoutes := t.GetUntargetedRoutes(gw) + + if len(untargetedRoutes) == 0 { + // For policies targeting a gateway, when no httproutes is attached to the gateway, skip wasm config + // test wasm config when no http routes attached to the gateway + logger.V(1).Info("no untargeted httproutes attached to the targeted gateway, skipping wasm config for the gateway rlp", "ratelimitpolicy", client.ObjectKeyFromObject(rlp)) + return nil, nil + } + + untargetedRules := make([]gatewayapiv1.HTTPRouteRule, 0) + for idx := range untargetedRoutes { + untargetedRules = append(untargetedRules, untargetedRoutes[idx].Spec.Rules...) + } + gwHostnamesTmp := kuadrantgatewayapi.TargetHostnames(gw) + gwHostnames := utils.Map(gwHostnamesTmp, func(str string) gatewayapiv1.Hostname { return gatewayapiv1.Hostname(str) }) + route = &gatewayapiv1.HTTPRoute{ + Spec: gatewayapiv1.HTTPRouteSpec{ + Hostnames: gwHostnames, + Rules: untargetedRules, + }, + } + } + + return route, nil +} + +func wasmRateLimitPolicy(ctx context.Context, t *kuadrantgatewayapi.TopologyIndexes, rlp *kuadrantv1beta2.RateLimitPolicy, gw *gatewayapiv1.Gateway) (*RateLimitPolicy, error) { + route, err := routeFromRLP(ctx, t, rlp, gw) + if err != nil { + return nil, err + } + if route == nil { + // no need to add the policy if there are no routes; + // a rlp can return no rules if all its limits fail to match any route rule + // or targeting a gateway with no "free" routes. "free" meaning no route with policies targeting it + return nil, nil + } + + // narrow the list of hostnames specified in the route so we don't generate wasm rules that only apply to other gateways + // this is a no-op for the gateway rlp + gwHostnames := kuadrantgatewayapi.GatewayHostnames(gw) + if len(gwHostnames) == 0 { + gwHostnames = []gatewayapiv1.Hostname{"*"} + } + hostnames := kuadrantgatewayapi.FilterValidSubdomains(gwHostnames, route.Spec.Hostnames) + if len(hostnames) == 0 { // it should only happen when the route specifies no hostnames + hostnames = gwHostnames + } + + // + // The route selectors logic rely on the "hostnames" field of the route object. + // However, routes effective hostname can be inherited from parent gateway, + // hence it depends on the context as multiple gateways can be targeted by a route + // The route selectors logic needs to be refactored + // or just deleted as soon as the HTTPRoute has name in the route object + // + routeWithEffectiveHostnames := route.DeepCopy() + routeWithEffectiveHostnames.Spec.Hostnames = hostnames + + rules := Rules(rlp, routeWithEffectiveHostnames) + if len(rules) == 0 { + // no need to add the policy if there are no rules; a rlp can return no rules if all its limits fail to match any route rule + return nil, nil + } + + return &RateLimitPolicy{ + Name: client.ObjectKeyFromObject(rlp).String(), + Domain: LimitsNamespaceFromRLP(rlp), + Hostnames: utils.HostnamesToStrings(hostnames), // we might be listing more hostnames than needed due to route selectors hostnames possibly being more restrictive + Service: common.KuadrantRateLimitClusterName, + Rules: rules, + }, nil +} + +func ConfigForGateway( + ctx context.Context, gw *gatewayapiv1.Gateway, + topology *kuadrantgatewayapi.Topology) (*Config, error) { + logger, err := logr.FromContext(ctx) + if err != nil { + return nil, err + } + + topologyIndex := kuadrantgatewayapi.NewTopologyIndexes(topology) + + rateLimitPolicies := topologyIndex.PoliciesFromGateway(gw) + logger.V(1).Info("WasmConfig", "#RLPS", len(rateLimitPolicies)) + + // Sort RLPs for consistent comparison with existing objects + sort.Sort(kuadrantgatewayapi.PolicyByTargetRefKindAndCreationTimeStamp(rateLimitPolicies)) + + config := &Config{ + FailureMode: FailureModeDeny, + RateLimitPolicies: make([]RateLimitPolicy, 0), + } + + for _, policy := range rateLimitPolicies { + rlp := policy.(*kuadrantv1beta2.RateLimitPolicy) + wasmRLP, err := wasmRateLimitPolicy(ctx, topologyIndex, rlp, gw) + if err != nil { + return nil, err + } + + if wasmRLP == nil { + // skip this RLP + continue + } + + config.RateLimitPolicies = append(config.RateLimitPolicies, *wasmRLP) + } + + return config, nil +} diff --git a/tests/common/authpolicy/authpolicy_controller_test.go b/tests/common/authpolicy/authpolicy_controller_test.go index cda61cd6e..45a07b8f2 100644 --- a/tests/common/authpolicy/authpolicy_controller_test.go +++ b/tests/common/authpolicy/authpolicy_controller_test.go @@ -99,7 +99,7 @@ var _ = Describe("AuthPolicy controller (Serial)", Serial, func() { Name: TestHTTPRouteName, }, Defaults: &api.AuthPolicyCommonSpec{ - AuthScheme: testBasicAuthScheme(), + AuthScheme: tests.BuildBasicAuthScheme(), }, }, } @@ -180,7 +180,7 @@ var _ = Describe("AuthPolicy controller", func() { Name: TestHTTPRouteName, }, Defaults: &api.AuthPolicyCommonSpec{ - AuthScheme: testBasicAuthScheme(), + AuthScheme: tests.BuildBasicAuthScheme(), }, }, } @@ -1188,7 +1188,7 @@ var _ = Describe("AuthPolicy controller", func() { policy.Spec.TargetRef.Name = TestGatewayName policy.Spec.Overrides = &api.AuthPolicyCommonSpec{} policy.Spec.Defaults = nil - policy.Spec.Overrides.AuthScheme = testBasicAuthScheme() + policy.Spec.Overrides.AuthScheme = tests.BuildBasicAuthScheme() policy.Spec.Overrides.AuthScheme.Authentication["apiKey"].ApiKey.Selector.MatchLabels["admin"] = "yes" }) @@ -1225,7 +1225,7 @@ var _ = Describe("AuthPolicy controller", func() { policy.Spec.TargetRef.Name = TestGatewayName policy.Spec.Overrides = &api.AuthPolicyCommonSpec{} policy.Spec.Defaults = nil - policy.Spec.Overrides.AuthScheme = testBasicAuthScheme() + policy.Spec.Overrides.AuthScheme = tests.BuildBasicAuthScheme() policy.Spec.Overrides.AuthScheme.Authentication["apiKey"].ApiKey.Selector.MatchLabels["admin"] = "yes" }) @@ -1255,7 +1255,7 @@ var _ = Describe("AuthPolicy controller", func() { policy.Spec.TargetRef.Name = TestGatewayName policy.Spec.Overrides = &api.AuthPolicyCommonSpec{} policy.Spec.Defaults = nil - policy.Spec.Overrides.AuthScheme = testBasicAuthScheme() + policy.Spec.Overrides.AuthScheme = tests.BuildBasicAuthScheme() policy.Spec.Overrides.AuthScheme.Authentication["apiKey"].ApiKey.Selector.MatchLabels["admin"] = "yes" }) @@ -1309,7 +1309,7 @@ var _ = Describe("AuthPolicy controller", func() { } gatewayPolicy.Spec.Overrides = &api.AuthPolicyCommonSpec{} gatewayPolicy.Spec.Defaults = nil - gatewayPolicy.Spec.Overrides.AuthScheme = testBasicAuthScheme() + gatewayPolicy.Spec.Overrides.AuthScheme = tests.BuildBasicAuthScheme() gatewayPolicy.Spec.Overrides.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) @@ -1338,7 +1338,7 @@ var _ = Describe("AuthPolicy controller", func() { policy.Spec.TargetRef.Name = TestGatewayName policy.Spec.Overrides = &api.AuthPolicyCommonSpec{} policy.Spec.Defaults = nil - policy.Spec.Overrides.AuthScheme = testBasicAuthScheme() + policy.Spec.Overrides.AuthScheme = tests.BuildBasicAuthScheme() policy.Spec.Overrides.AuthScheme.Authentication["apiKey"].ApiKey.Selector.MatchLabels["admin"] = "yes" }) @@ -1357,7 +1357,7 @@ var _ = Describe("AuthPolicy controller", func() { return false } gatewayPolicy.Spec.Overrides = nil - gatewayPolicy.Spec.CommonSpec().AuthScheme = testBasicAuthScheme() + gatewayPolicy.Spec.CommonSpec().AuthScheme = tests.BuildBasicAuthScheme() gatewayPolicy.Spec.CommonSpec().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) @@ -1374,7 +1374,7 @@ var _ = Describe("AuthPolicy controller", func() { routePolicy := policyFactory(func(policy *api.AuthPolicy) { policy.Spec.Overrides = &api.AuthPolicyCommonSpec{} policy.Spec.Defaults = nil - policy.Spec.Overrides.AuthScheme = testBasicAuthScheme() + policy.Spec.Overrides.AuthScheme = tests.BuildBasicAuthScheme() }) err := k8sClient.Create(ctx, routePolicy) logf.Log.V(1).Info("Creating AuthPolicy", "key", client.ObjectKeyFromObject(routePolicy).String(), "error", err) @@ -1458,7 +1458,7 @@ var _ = Describe("AuthPolicy CEL Validations", func() { Context("Defaults mutual exclusivity validation", func() { It("Valid when only implicit defaults are used", func(ctx SpecContext) { policy := policyFactory(func(policy *api.AuthPolicy) { - policy.Spec.AuthScheme = testBasicAuthScheme() + policy.Spec.AuthScheme = tests.BuildBasicAuthScheme() }) Expect(k8sClient.Create(ctx, policy)).To(Succeed()) }) @@ -1466,7 +1466,7 @@ var _ = Describe("AuthPolicy CEL Validations", func() { It("Valid when only explicit defaults are used", func(ctx SpecContext) { policy := policyFactory(func(policy *api.AuthPolicy) { policy.Spec.Defaults = &api.AuthPolicyCommonSpec{ - AuthScheme: testBasicAuthScheme(), + AuthScheme: tests.BuildBasicAuthScheme(), } }) Expect(k8sClient.Create(ctx, policy)).To(Succeed()) @@ -1475,7 +1475,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 *api.AuthPolicy) { policy.Spec.Defaults = &api.AuthPolicyCommonSpec{} - policy.Spec.AuthScheme = testBasicAuthScheme() + policy.Spec.AuthScheme = tests.BuildBasicAuthScheme() }) err := k8sClient.Create(ctx, policy) Expect(err).To(Not(BeNil())) @@ -1911,28 +1911,3 @@ var _ = Describe("AuthPolicy CEL Validations", func() { }) }) }) - -func testBasicAuthScheme() *api.AuthSchemeSpec { - return &api.AuthSchemeSpec{ - Authentication: map[string]api.AuthenticationSpec{ - "apiKey": { - 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", - }, - }, - }, - }, - }, - } -} diff --git a/tests/common/authpolicy/suite_test.go b/tests/common/authpolicy/suite_test.go index e4a37eecf..e56f58fc2 100644 --- a/tests/common/authpolicy/suite_test.go +++ b/tests/common/authpolicy/suite_test.go @@ -111,6 +111,9 @@ var _ = SynchronizedBeforeSuite(func() []byte { k8sClient, err = client.New(cfg, client.Options{Scheme: s}) Expect(err).NotTo(HaveOccurred()) Expect(k8sClient).NotTo(BeNil()) + + tests.GatewayClassName = os.Getenv("GATEWAYAPI_PROVIDER") + Expect(tests.GatewayClassName).NotTo(BeZero(), "Please make sure GATEWAYAPI_PROVIDER is set correctly.") }) var _ = SynchronizedAfterSuite(func() {}, func() { diff --git a/tests/common/gatewaykuadrant/suite_test.go b/tests/common/gatewaykuadrant/suite_test.go index 9ab61e0f4..adcb6e97d 100644 --- a/tests/common/gatewaykuadrant/suite_test.go +++ b/tests/common/gatewaykuadrant/suite_test.go @@ -23,6 +23,7 @@ import ( "os" "testing" + "github.com/kuadrant/kuadrant-operator/tests" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "k8s.io/client-go/rest" @@ -92,6 +93,9 @@ var _ = SynchronizedBeforeSuite(func() []byte { k8sClient, err = client.New(cfg, client.Options{Scheme: s}) Expect(err).NotTo(HaveOccurred()) Expect(k8sClient).NotTo(BeNil()) + + tests.GatewayClassName = os.Getenv("GATEWAYAPI_PROVIDER") + Expect(tests.GatewayClassName).NotTo(BeZero(), "Please make sure GATEWAYAPI_PROVIDER is set correctly.") }) var _ = SynchronizedAfterSuite(func() {}, func() { diff --git a/tests/common/ratelimitpolicy/ratelimitpolicy_controller_test.go b/tests/common/ratelimitpolicy/ratelimitpolicy_controller_test.go index 9726d45a7..fcbd70fdf 100644 --- a/tests/common/ratelimitpolicy/ratelimitpolicy_controller_test.go +++ b/tests/common/ratelimitpolicy/ratelimitpolicy_controller_test.go @@ -145,7 +145,6 @@ var _ = Describe("RateLimitPolicy controller (Serial)", Serial, func() { "RateLimitPolicy has been successfully enforced")).WithContext(ctx).Should(Succeed()) }, testTimeOut) }) - }) var _ = Describe("RateLimitPolicy controller", func() { @@ -271,7 +270,7 @@ var _ = Describe("RateLimitPolicy controller", func() { Expect(existingLimitador.Spec.Limits).To(ContainElements(limitadorv1alpha1.RateLimit{ MaxValue: 1, Seconds: 3 * 60, - Namespace: rlptools.LimitsNamespaceFromRLP(rlp), + Namespace: wasm.LimitsNamespaceFromRLP(rlp), Conditions: []string{fmt.Sprintf(`%s == "1"`, wasm.LimitNameToLimitadorIdentifier(rlpKey, "l1"))}, Variables: []string{}, Name: rlptools.LimitsNameFromRLP(rlp), @@ -359,7 +358,7 @@ var _ = Describe("RateLimitPolicy controller", func() { Expect(existingLimitador.Spec.Limits).To(ContainElements(limitadorv1alpha1.RateLimit{ MaxValue: 1, Seconds: 3 * 60, - Namespace: rlptools.LimitsNamespaceFromRLP(rlp), + Namespace: wasm.LimitsNamespaceFromRLP(rlp), Conditions: []string{fmt.Sprintf(`%s == "1"`, wasm.LimitNameToLimitadorIdentifier(rlpKey, "l1"))}, Variables: []string{}, Name: rlptools.LimitsNameFromRLP(rlp), @@ -411,7 +410,7 @@ var _ = Describe("RateLimitPolicy controller", func() { Expect(existingLimitador.Spec.Limits).To(ContainElements(limitadorv1alpha1.RateLimit{ MaxValue: 1, Seconds: 3 * 60, - Namespace: rlptools.LimitsNamespaceFromRLP(rlp), + Namespace: wasm.LimitsNamespaceFromRLP(rlp), Conditions: []string{fmt.Sprintf(`%s == "1"`, wasm.LimitNameToLimitadorIdentifier(rlpKey, "l1"))}, Variables: []string{}, Name: rlptools.LimitsNameFromRLP(rlp), @@ -487,7 +486,7 @@ var _ = Describe("RateLimitPolicy controller", func() { Eventually(limitadorContainsLimit(ctx, limitadorv1alpha1.RateLimit{ MaxValue: 10, Seconds: 5, - Namespace: rlptools.LimitsNamespaceFromRLP(routeRLP), + Namespace: wasm.LimitsNamespaceFromRLP(routeRLP), Conditions: []string{fmt.Sprintf(`%s == "1"`, wasm.LimitNameToLimitadorIdentifier(routeRLPKey, "l1"))}, Variables: []string{}, Name: rlptools.LimitsNameFromRLP(routeRLP), @@ -602,7 +601,7 @@ var _ = Describe("RateLimitPolicy controller", func() { Eventually(limitadorContainsLimit(ctx, limitadorv1alpha1.RateLimit{ MaxValue: 1, Seconds: 180, - Namespace: rlptools.LimitsNamespaceFromRLP(routeRLP), + Namespace: wasm.LimitsNamespaceFromRLP(routeRLP), Conditions: []string{fmt.Sprintf(`%s == "1"`, wasm.LimitNameToLimitadorIdentifier(routeRLPKey, "l1"))}, Variables: []string{}, Name: rlptools.LimitsNameFromRLP(routeRLP), @@ -624,7 +623,7 @@ var _ = Describe("RateLimitPolicy controller", func() { Eventually(limitadorContainsLimit(ctx, limitadorv1alpha1.RateLimit{ MaxValue: 10, Seconds: 5, - Namespace: rlptools.LimitsNamespaceFromRLP(routeRLP), + Namespace: wasm.LimitsNamespaceFromRLP(routeRLP), Conditions: []string{fmt.Sprintf(`%s == "1"`, wasm.LimitNameToLimitadorIdentifier(routeRLPKey, "route"))}, Variables: []string{}, Name: rlptools.LimitsNameFromRLP(routeRLP), @@ -659,7 +658,7 @@ var _ = Describe("RateLimitPolicy controller", func() { Eventually(limitadorContainsLimit(ctx, limitadorv1alpha1.RateLimit{ MaxValue: 1, Seconds: 180, - Namespace: rlptools.LimitsNamespaceFromRLP(routeRLP), + Namespace: wasm.LimitsNamespaceFromRLP(routeRLP), Conditions: []string{fmt.Sprintf(`%s == "1"`, wasm.LimitNameToLimitadorIdentifier(routeRLPKey, "l1"))}, Variables: []string{}, Name: rlptools.LimitsNameFromRLP(routeRLP), @@ -698,7 +697,7 @@ var _ = Describe("RateLimitPolicy controller", func() { Eventually(limitadorContainsLimit(ctx, limitadorv1alpha1.RateLimit{ MaxValue: 10, Seconds: 5, - Namespace: rlptools.LimitsNamespaceFromRLP(routeRLP), + Namespace: wasm.LimitsNamespaceFromRLP(routeRLP), Conditions: []string{fmt.Sprintf(`%s == "1"`, wasm.LimitNameToLimitadorIdentifier(routeRLPKey, "route"))}, Variables: []string{}, Name: rlptools.LimitsNameFromRLP(routeRLP), @@ -721,7 +720,7 @@ var _ = Describe("RateLimitPolicy controller", func() { Eventually(limitadorContainsLimit(ctx, limitadorv1alpha1.RateLimit{ MaxValue: 1, Seconds: 180, - Namespace: rlptools.LimitsNamespaceFromRLP(routeRLP), + Namespace: wasm.LimitsNamespaceFromRLP(routeRLP), Conditions: []string{fmt.Sprintf(`%s == "1"`, wasm.LimitNameToLimitadorIdentifier(routeRLPKey, "l1"))}, Variables: []string{}, Name: rlptools.LimitsNameFromRLP(routeRLP), @@ -747,7 +746,7 @@ var _ = Describe("RateLimitPolicy controller", func() { Eventually(limitadorContainsLimit(ctx, limitadorv1alpha1.RateLimit{ MaxValue: 1, Seconds: 180, - Namespace: rlptools.LimitsNamespaceFromRLP(routeRLP), + Namespace: wasm.LimitsNamespaceFromRLP(routeRLP), Conditions: []string{fmt.Sprintf(`%s == "1"`, wasm.LimitNameToLimitadorIdentifier(routeRLPKey, "l1"))}, Variables: []string{}, Name: rlptools.LimitsNameFromRLP(routeRLP), @@ -770,7 +769,7 @@ var _ = Describe("RateLimitPolicy controller", func() { Eventually(limitadorContainsLimit(ctx, limitadorv1alpha1.RateLimit{ MaxValue: 10, Seconds: 5, - Namespace: rlptools.LimitsNamespaceFromRLP(routeRLP), + Namespace: wasm.LimitsNamespaceFromRLP(routeRLP), Conditions: []string{fmt.Sprintf(`%s == "1"`, wasm.LimitNameToLimitadorIdentifier(routeRLPKey, "route"))}, Variables: []string{}, Name: rlptools.LimitsNameFromRLP(routeRLP), @@ -1295,8 +1294,8 @@ var _ = Describe("RateLimitPolicy controller", func() { gatewayParentsFunc := func(r *gatewayapiv1.HTTPRoute) { r.Spec.ParentRefs = []gatewayapiv1.ParentReference{ - {Name: gatewayapiv1.ObjectName(gatewayAName)}, - {Name: gatewayapiv1.ObjectName(gatewayBName)}, + {Name: gatewayapiv1.ObjectName(gatewayAName), Namespace: ptr.To(gatewayapiv1.Namespace(testNamespace))}, + {Name: gatewayapiv1.ObjectName(gatewayBName), Namespace: ptr.To(gatewayapiv1.Namespace(testNamespace))}, } } @@ -1374,7 +1373,7 @@ var _ = Describe("RateLimitPolicy controller", func() { limitadorv1alpha1.RateLimit{ MaxValue: 1000, Seconds: 1, - Namespace: rlptools.LimitsNamespaceFromRLP(rlpTargetedRoute), + Namespace: wasm.LimitsNamespaceFromRLP(rlpTargetedRoute), Conditions: []string{fmt.Sprintf(`%s == "1"`, wasm.LimitNameToLimitadorIdentifier(client.ObjectKeyFromObject(rlpTargetedRoute), "gw-a-1000rps"))}, Variables: []string{}, Name: rlptools.LimitsNameFromRLP(rlpTargetedRoute), @@ -1382,7 +1381,7 @@ var _ = Describe("RateLimitPolicy controller", func() { limitadorv1alpha1.RateLimit{ MaxValue: 100, Seconds: 1, - Namespace: rlptools.LimitsNamespaceFromRLP(rlpTargetedRoute), + Namespace: wasm.LimitsNamespaceFromRLP(rlpTargetedRoute), Conditions: []string{fmt.Sprintf(`%s == "1"`, wasm.LimitNameToLimitadorIdentifier(client.ObjectKeyFromObject(rlpTargetedRoute), "gw-b-100rps"))}, Variables: []string{}, Name: rlptools.LimitsNameFromRLP(rlpTargetedRoute), @@ -1390,7 +1389,7 @@ var _ = Describe("RateLimitPolicy controller", func() { limitadorv1alpha1.RateLimit{ // FIXME(@guicassolato): we need to create one limit definition per gateway × route combination, not one per gateway × policy combination MaxValue: 1000, Seconds: 1, - Namespace: rlptools.LimitsNamespaceFromRLP(rlpGatewayA), + Namespace: wasm.LimitsNamespaceFromRLP(rlpGatewayA), Conditions: []string{fmt.Sprintf(`%s == "1"`, wasm.LimitNameToLimitadorIdentifier(client.ObjectKeyFromObject(rlpGatewayA), "gw-a-1000rps"))}, Variables: []string{}, Name: rlptools.LimitsNameFromRLP(rlpGatewayA), @@ -1398,7 +1397,7 @@ var _ = Describe("RateLimitPolicy controller", func() { limitadorv1alpha1.RateLimit{ MaxValue: 100, Seconds: 1, - Namespace: rlptools.LimitsNamespaceFromRLP(rlpGatewayB), + Namespace: wasm.LimitsNamespaceFromRLP(rlpGatewayB), Conditions: []string{fmt.Sprintf(`%s == "1"`, wasm.LimitNameToLimitadorIdentifier(client.ObjectKeyFromObject(rlpGatewayB), "gw-b-100rps"))}, Variables: []string{}, Name: rlptools.LimitsNameFromRLP(rlpGatewayB), diff --git a/tests/common/ratelimitpolicy/suite_test.go b/tests/common/ratelimitpolicy/suite_test.go index c7306b8b0..f695b58d9 100644 --- a/tests/common/ratelimitpolicy/suite_test.go +++ b/tests/common/ratelimitpolicy/suite_test.go @@ -112,6 +112,9 @@ var _ = SynchronizedBeforeSuite(func() []byte { k8sClient, err = client.New(cfg, client.Options{Scheme: s}) Expect(err).NotTo(HaveOccurred()) Expect(k8sClient).NotTo(BeNil()) + + tests.GatewayClassName = os.Getenv("GATEWAYAPI_PROVIDER") + Expect(tests.GatewayClassName).NotTo(BeZero(), "Please make sure GATEWAYAPI_PROVIDER is set correctly.") }) var _ = SynchronizedAfterSuite(func() {}, func() { diff --git a/tests/common/targetstatus/suite_test.go b/tests/common/targetstatus/suite_test.go index 73e88478c..9646cf5f0 100644 --- a/tests/common/targetstatus/suite_test.go +++ b/tests/common/targetstatus/suite_test.go @@ -112,6 +112,9 @@ var _ = SynchronizedBeforeSuite(func() []byte { k8sClient, err = client.New(cfg, client.Options{Scheme: s}) Expect(err).NotTo(HaveOccurred()) Expect(k8sClient).NotTo(BeNil()) + + tests.GatewayClassName = os.Getenv("GATEWAYAPI_PROVIDER") + Expect(tests.GatewayClassName).NotTo(BeZero(), "Please make sure GATEWAYAPI_PROVIDER is set correctly.") }) var _ = SynchronizedAfterSuite(func() {}, func() { diff --git a/tests/commons.go b/tests/commons.go index 9c3534343..820bd235c 100644 --- a/tests/commons.go +++ b/tests/commons.go @@ -9,6 +9,7 @@ import ( "strings" "time" + authorinoapi "github.com/kuadrant/authorino/api/v1beta2" . "github.com/onsi/gomega" "sigs.k8s.io/external-dns/endpoint" @@ -29,6 +30,7 @@ import ( kuadrantdnsv1alpha1 "github.com/kuadrant/dns-operator/api/v1alpha1" kuadrantdnsbuilder "github.com/kuadrant/dns-operator/pkg/builder" + kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" kuadrantv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2" kuadrantgatewayapi "github.com/kuadrant/kuadrant-operator/pkg/library/gatewayapi" @@ -51,6 +53,8 @@ const ( HTTPRouteName = "toystore-route" ) +var GatewayClassName string + func HostWildcard(domain string) string { return fmt.Sprintf("*.%s", domain) } @@ -76,7 +80,7 @@ func BuildBasicGateway(gwName, ns string, mutateFns ...func(*gatewayapiv1.Gatewa Annotations: map[string]string{"networking.istio.io/service-type": string(corev1.ServiceTypeClusterIP)}, }, Spec: gatewayapiv1.GatewaySpec{ - GatewayClassName: "istio", + GatewayClassName: gatewayapiv1.ObjectName(GatewayClassName), Listeners: []gatewayapiv1.Listener{ { Name: "default", @@ -635,3 +639,43 @@ func KuadrantIsReady(ctx context.Context, cl client.Client, key client.ObjectKey g.Expect(meta.IsStatusConditionTrue(kuadrantCR.Status.Conditions, "Ready")).To(BeTrue()) } } + +func BuildBasicAuthScheme() *kuadrantv1beta2.AuthSchemeSpec { + return &kuadrantv1beta2.AuthSchemeSpec{ + Authentication: map[string]kuadrantv1beta2.AuthenticationSpec{ + "apiKey": { + 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", + }, + }, + }, + }, + }, + } +} + +func IsRLPAcceptedAndEnforced(g Gomega, ctx context.Context, cl client.Client, policyKey client.ObjectKey) { + existingPolicy := &kuadrantv1beta2.RateLimitPolicy{} + g.Expect(cl.Get(ctx, policyKey, existingPolicy)).To(Succeed()) + + acceptedCond := meta.FindStatusCondition(existingPolicy.Status.Conditions, string(gatewayapiv1alpha2.PolicyConditionAccepted)) + g.Expect(acceptedCond).ToNot(BeNil()) + g.Expect(acceptedCond.Status).To(Equal(metav1.ConditionTrue)) + g.Expect(acceptedCond.Reason).To(Equal(string(gatewayapiv1alpha2.PolicyReasonAccepted))) + + enforcedCond := meta.FindStatusCondition(existingPolicy.Status.Conditions, string(kuadrant.PolicyConditionEnforced)) + g.Expect(enforcedCond).ToNot(BeNil()) + g.Expect(enforcedCond.Status).To(Equal(metav1.ConditionTrue)) + g.Expect(enforcedCond.Reason).To(Equal(string(kuadrant.PolicyReasonEnforced))) +} diff --git a/tests/envoygateway/authpolicy_envoysecuritypolicy_controller_test.go b/tests/envoygateway/authpolicy_envoysecuritypolicy_controller_test.go new file mode 100644 index 000000000..37e9215b8 --- /dev/null +++ b/tests/envoygateway/authpolicy_envoysecuritypolicy_controller_test.go @@ -0,0 +1,275 @@ +//go:build integration + +package envoygateway_test + +import ( + "fmt" + "strings" + "time" + + egv1alpha1 "github.com/envoyproxy/gateway/api/v1alpha1" + kuadrantv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2" + "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" + "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" +) + +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 *kuadrantv1beta2.AuthPolicy)) *kuadrantv1beta2.AuthPolicy { + policy := &kuadrantv1beta2.AuthPolicy{ + TypeMeta: metav1.TypeMeta{ + Kind: "AuthPolicy", + APIVersion: kuadrantv1beta2.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "toystore", + Namespace: testNamespace, + }, + Spec: kuadrantv1beta2.AuthPolicySpec{ + TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "HTTPRoute", + Name: TestHTTPRouteName, + }, + Defaults: &kuadrantv1beta2.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 *kuadrantv1beta2.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 *kuadrantv1beta2.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 *kuadrantv1beta2.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 *kuadrantv1beta2.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/envoygateway_limitador_cluster_controller_test.go b/tests/envoygateway/envoygateway_limitador_cluster_controller_test.go new file mode 100644 index 000000000..5c7040343 --- /dev/null +++ b/tests/envoygateway/envoygateway_limitador_cluster_controller_test.go @@ -0,0 +1,237 @@ +//go:build integration + +package envoygateway_test + +import ( + "context" + "encoding/json" + "fmt" + "strings" + "time" + + egv1alpha1 "github.com/envoyproxy/gateway/api/v1alpha1" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "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" + + kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1" + kuadrantv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2" + "github.com/kuadrant/kuadrant-operator/controllers" + "github.com/kuadrant/kuadrant-operator/pkg/common" + "github.com/kuadrant/kuadrant-operator/tests" +) + +var _ = Describe("limitador cluster 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 := testClient().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 *kuadrantv1beta2.RateLimitPolicy)) *kuadrantv1beta2.RateLimitPolicy { + policy := &kuadrantv1beta2.RateLimitPolicy{ + TypeMeta: metav1.TypeMeta{ + Kind: "RateLimitPolicy", + APIVersion: kuadrantv1beta2.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "rlp", + Namespace: testNamespace, + }, + Spec: kuadrantv1beta2.RateLimitPolicySpec{}, + } + + for _, mutateFn := range mutateFns { + mutateFn(policy) + } + + return policy + } + + randomHostFromGWHost := func() string { + return strings.Replace(gwHost, "*", rand.String(4), 1) + } + + getKuadrantNamespace := func(ctx context.Context, cl client.Client) string { + kuadrantList := &kuadrantv1beta1.KuadrantList{} + err := cl.List(ctx, kuadrantList) + // must exist + Expect(err).ToNot(HaveOccurred()) + Expect(kuadrantList.Items).To(HaveLen(1)) + return kuadrantList.Items[0].Namespace + } + + Context("RateLimitPolicy attached to the gateway", func() { + + var ( + gwPolicy *kuadrantv1beta2.RateLimitPolicy + gwRoute *gatewayapiv1.HTTPRoute + ) + + BeforeEach(func(ctx SpecContext) { + gwRoute = tests.BuildBasicHttpRoute(TestHTTPRouteName, TestGatewayName, testNamespace, []string{randomHostFromGWHost()}) + err := testClient().Create(ctx, gwRoute) + Expect(err).ToNot(HaveOccurred()) + Eventually(tests.RouteIsAccepted(ctx, testClient(), client.ObjectKeyFromObject(gwRoute))).WithContext(ctx).Should(BeTrue()) + + gwPolicy = policyFactory(func(policy *kuadrantv1beta2.RateLimitPolicy) { + policy.Name = "gw" + policy.Spec.TargetRef.Group = gatewayapiv1.GroupName + policy.Spec.TargetRef.Kind = "Gateway" + policy.Spec.TargetRef.Name = TestGatewayName + policy.Spec.Defaults = &kuadrantv1beta2.RateLimitPolicyCommonSpec{ + Limits: map[string]kuadrantv1beta2.Limit{ + "l1": { + Rates: []kuadrantv1beta2.Rate{ + { + Limit: 1, Duration: 3, Unit: "minute", + }, + }, + }, + }, + } + }) + + gwPolicyKey := client.ObjectKeyFromObject(gwPolicy) + + err = testClient().Create(ctx, gwPolicy) + logf.Log.V(1).Info("Creating RateLimitPolicy", "key", gwPolicyKey.String(), "error", err) + Expect(err).ToNot(HaveOccurred()) + + // check policy status + Eventually(tests.IsRLPAcceptedAndEnforced). + WithContext(ctx). + WithArguments(testClient(), gwPolicyKey).Should(Succeed()) + }) + + It("Creates envoypatchpolicy for limitador cluster", func(ctx SpecContext) { + patchKey := client.ObjectKey{ + Name: controllers.LimitadorClusterEnvoyPatchPolicyName( + controllers.EnvoyExtensionPolicyName(TestGatewayName), + ), + Namespace: testNamespace, + } + + Eventually(IsEnvoyPatchPolicyAccepted). + WithContext(ctx). + WithArguments(testClient(), patchKey, client.ObjectKeyFromObject(gateway)). + Should(Succeed()) + + patch := &egv1alpha1.EnvoyPatchPolicy{} + err := testClient().Get(ctx, patchKey, patch) + // must exist + Expect(err).ToNot(HaveOccurred()) + + Expect(patch.Spec.TargetRef.Group).To(Equal(gatewayapiv1.Group("gateway.networking.k8s.io"))) + Expect(patch.Spec.TargetRef.Kind).To(Equal(gatewayapiv1.Kind("Gateway"))) + Expect(patch.Spec.TargetRef.Name).To(Equal(gatewayapiv1.ObjectName(gateway.Name))) + Expect(patch.Spec.Type).To(Equal(egv1alpha1.JSONPatchEnvoyPatchType)) + Expect(patch.Spec.JSONPatches).To(HaveLen(1)) + Expect(patch.Spec.JSONPatches[0].Type).To(Equal(egv1alpha1.ClusterEnvoyResourceType)) + Expect(patch.Spec.JSONPatches[0].Name).To(Equal(common.KuadrantRateLimitClusterName)) + Expect(patch.Spec.JSONPatches[0].Operation.Op).To(Equal(egv1alpha1.JSONPatchOperationType("add"))) + + // Check patch value + patchValueBytes, err := patch.Spec.JSONPatches[0].Operation.Value.MarshalJSON() + Expect(err).ToNot(HaveOccurred()) + var existingPatchValue map[string]any + err = json.Unmarshal(patchValueBytes, &existingPatchValue) + Expect(err).ToNot(HaveOccurred()) + + kuadrantNs := getKuadrantNamespace(ctx, testClient()) + expectedLimitadorSvcHost := fmt.Sprintf("limitador-limitador.%s.svc.cluster.local", kuadrantNs) + var expectedLimitadorGRPCPort float64 = 8081 + + Expect(existingPatchValue).To(Equal( + map[string]any{ + "name": common.KuadrantRateLimitClusterName, + "type": "STRICT_DNS", + "connect_timeout": "1s", + "lb_policy": "ROUND_ROBIN", + "http2_protocol_options": map[string]any{}, + "load_assignment": map[string]any{ + "cluster_name": common.KuadrantRateLimitClusterName, + "endpoints": []any{ + map[string]any{ + "lb_endpoints": []any{ + map[string]any{ + "endpoint": map[string]any{ + "address": map[string]any{ + "socket_address": map[string]any{ + "address": expectedLimitadorSvcHost, + "port_value": expectedLimitadorGRPCPort, + }, + }, + }, + }, + }, + }, + }, + }, + })) + }, testTimeOut) + + It("Deletes envoypatchpolicy when rate limit policy is deleted", func(ctx SpecContext) { + gwPolicyKey := client.ObjectKeyFromObject(gwPolicy) + err := testClient().Delete(ctx, gwPolicy) + logf.Log.V(1).Info("Deleting RateLimitPolicy", "key", gwPolicyKey.String(), "error", err) + Expect(err).ToNot(HaveOccurred()) + + patchKey := client.ObjectKey{ + Name: controllers.LimitadorClusterEnvoyPatchPolicyName( + controllers.EnvoyExtensionPolicyName(TestGatewayName), + ), + Namespace: testNamespace, + } + + Eventually(func() bool { + err := testClient().Get(ctx, patchKey, &egv1alpha1.EnvoyPatchPolicy{}) + logf.Log.V(1).Info("Fetching EnvoyPatchPolicy", "key", patchKey.String(), "error", err) + return apierrors.IsNotFound(err) + }).WithContext(ctx).Should(BeTrue()) + }, testTimeOut) + + It("Deletes envoypatchpolicy if gateway is deleted", func(ctx SpecContext) { + err := testClient().Delete(ctx, gateway) + logf.Log.V(1).Info("Deleting Gateway", "key", client.ObjectKeyFromObject(gateway).String(), "error", err) + Expect(err).ToNot(HaveOccurred()) + + patchKey := client.ObjectKey{ + Name: controllers.LimitadorClusterEnvoyPatchPolicyName( + controllers.EnvoyExtensionPolicyName(TestGatewayName), + ), + Namespace: testNamespace, + } + + Eventually(func() bool { + err := testClient().Get(ctx, patchKey, &egv1alpha1.EnvoyPatchPolicy{}) + logf.Log.V(1).Info("Fetching EnvoyPatchPolicy", "key", patchKey.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 new file mode 100644 index 000000000..85e9542b9 --- /dev/null +++ b/tests/envoygateway/envoysecuritypolicy_referencegrant_controller_test.go @@ -0,0 +1,270 @@ +//go:build integration + +package envoygateway_test + +import ( + "time" + + egv1alpha1 "github.com/envoyproxy/gateway/api/v1alpha1" + kuadrantv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2" + "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" +) + +var _ = Describe("Envoy SecurityPolicy ReferenceGrant controller", func() { + const ( + testTimeOut = SpecTimeout(2 * time.Minute) + afterEachTimeOut = NodeTimeout(3 * time.Minute) + ) + var ( + routePolicyOne *kuadrantv1beta2.AuthPolicy + gateway *gatewayapiv1.Gateway + route *gatewayapiv1.HTTPRoute + ) + + initGatewayRoutePolicy := func(ctx SpecContext, testNamespace string, policy *kuadrantv1beta2.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 *kuadrantv1beta2.AuthPolicy)) *kuadrantv1beta2.AuthPolicy { + policy := &kuadrantv1beta2.AuthPolicy{ + TypeMeta: metav1.TypeMeta{ + Kind: "AuthPolicy", + APIVersion: kuadrantv1beta2.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "toystore", + Namespace: testNamespace, + }, + Spec: kuadrantv1beta2.AuthPolicySpec{ + TargetRef: gatewayapiv1alpha2.LocalPolicyTargetReference{ + Group: gatewayapiv1.GroupName, + Kind: "HTTPRoute", + Name: TestHTTPRouteName, + }, + Defaults: &kuadrantv1beta2.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 *kuadrantv1beta2.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/envoygateway/suite_test.go b/tests/envoygateway/suite_test.go new file mode 100644 index 000000000..9fa1ffb1d --- /dev/null +++ b/tests/envoygateway/suite_test.go @@ -0,0 +1,112 @@ +//go:build integration + +package envoygateway_test + +import ( + "context" + "encoding/json" + "os" + "testing" + + "github.com/kuadrant/kuadrant-operator/controllers" + "github.com/kuadrant/kuadrant-operator/pkg/log" + "github.com/kuadrant/kuadrant-operator/tests" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +var k8sClient client.Client +var testEnv *envtest.Environment +var kuadrantInstallationNS string + +func testClient() client.Client { return k8sClient } + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Controller Suite on Envoy Gateway") +} + +const ( + TestGatewayName = "test-placed-gateway" + TestHTTPRouteName = "toystore-route" +) + +var _ = SynchronizedBeforeSuite(func() []byte { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + UseExistingCluster: &[]bool{true}[0], + } + + cfg, err := testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + s := controllers.BootstrapScheme() + controllers.SetupKuadrantOperatorForTest(s, cfg) + + k8sClient, err = client.New(cfg, client.Options{Scheme: s}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + + ctx := context.Background() + ns := tests.CreateNamespace(ctx, testClient()) + tests.ApplyKuadrantCR(ctx, testClient(), ns) + + data := controllers.MarshalConfig(cfg, controllers.WithKuadrantInstallNS(ns)) + + return data +}, func(data []byte) { + // Unmarshal the shared configuration struct + var sharedCfg controllers.SharedConfig + Expect(json.Unmarshal(data, &sharedCfg)).To(Succeed()) + + // Create the rest.Config object from the shared configuration + cfg := &rest.Config{ + Host: sharedCfg.Host, + TLSClientConfig: rest.TLSClientConfig{ + Insecure: sharedCfg.TLSClientConfig.Insecure, + CertData: sharedCfg.TLSClientConfig.CertData, + KeyData: sharedCfg.TLSClientConfig.KeyData, + CAData: sharedCfg.TLSClientConfig.CAData, + }, + } + + kuadrantInstallationNS = sharedCfg.KuadrantNS + + // Create new scheme for each client + s := controllers.BootstrapScheme() + + // Set the shared configuration + var err error + k8sClient, err = client.New(cfg, client.Options{Scheme: s}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + + tests.GatewayClassName = os.Getenv("GATEWAYAPI_PROVIDER") + Expect(tests.GatewayClassName).To(Equal("envoygateway"), "Please make sure GATEWAYAPI_PROVIDER is set correctly.") +}) + +var _ = SynchronizedAfterSuite(func() {}, func() { + By("tearing down the test environment") + tests.DeleteNamespace(context.Background(), k8sClient, kuadrantInstallationNS) + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) + +func TestMain(m *testing.M) { + logger := log.NewLogger( + log.SetLevel(log.DebugLevel), + log.SetMode(log.ModeDev), + log.WriteTo(GinkgoWriter), + ).WithName("envoygateway_controller_test") + log.SetLogger(logger) + os.Exit(m.Run()) +} diff --git a/tests/envoygateway/utils_test.go b/tests/envoygateway/utils_test.go new file mode 100644 index 000000000..aa9f8acde --- /dev/null +++ b/tests/envoygateway/utils_test.go @@ -0,0 +1,58 @@ +//go:build integration + +package envoygateway_test + +import ( + "context" + + egv1alpha1 "github.com/envoyproxy/gateway/api/v1alpha1" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" + gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + + "github.com/kuadrant/kuadrant-operator/pkg/library/utils" +) + +// IsEnvoyExtenstionPolicyAccepted checks extension policy accepted status for a given target ref. +// This method can be useful for non-test code for the status controller that should +// check for extension policy accepted state. Currently status controller does not do that. Not yet. +func IsEnvoyExtensionPolicyAccepted(g Gomega, ctx context.Context, cl client.Client, key client.ObjectKey, gwKey client.ObjectKey) { + policy := &egv1alpha1.EnvoyExtensionPolicy{} + g.Expect(cl.Get(ctx, key, policy)).To(Succeed()) + + ancestor, ok := utils.Find(policy.Status.Ancestors, func(ancestor gatewayapiv1alpha2.PolicyAncestorStatus) bool { + // Only supporting gateways + ancestorNamespace := ptr.Deref(ancestor.AncestorRef.Namespace, gatewayapiv1.Namespace(gwKey.Namespace)) + return string(ancestor.AncestorRef.Name) == gwKey.Name && string(ancestorNamespace) == gwKey.Namespace + }) + g.Expect(ok).To(BeTrue()) + + acceptedCond := meta.FindStatusCondition(ancestor.Conditions, string(gatewayapiv1alpha2.PolicyConditionAccepted)) + g.Expect(acceptedCond).ToNot(BeNil()) + g.Expect(acceptedCond.Status).To(Equal(metav1.ConditionTrue)) + g.Expect(acceptedCond.Reason).To(Equal(string(gatewayapiv1alpha2.PolicyReasonAccepted))) +} + +// IsEnvoyPatchPolicyAccepted checks patch policy accepted status for a given target ref. +// This method can be useful for non-test code for the status controller that should +// check for the patch policy accepted state. Currently status controller does not do that. Not yet. +func IsEnvoyPatchPolicyAccepted(g Gomega, ctx context.Context, cl client.Client, key client.ObjectKey, gwKey client.ObjectKey) { + policy := &egv1alpha1.EnvoyPatchPolicy{} + g.Expect(cl.Get(ctx, key, policy)).To(Succeed()) + + ancestor, ok := utils.Find(policy.Status.Ancestors, func(ancestor gatewayapiv1alpha2.PolicyAncestorStatus) bool { + // Only supporting gateways + ancestorNamespace := ptr.Deref(ancestor.AncestorRef.Namespace, gatewayapiv1.Namespace(gwKey.Namespace)) + return string(ancestor.AncestorRef.Name) == gwKey.Name && string(ancestorNamespace) == gwKey.Namespace + }) + g.Expect(ok).To(BeTrue()) + + acceptedCond := meta.FindStatusCondition(ancestor.Conditions, string(gatewayapiv1alpha2.PolicyConditionAccepted)) + g.Expect(acceptedCond).ToNot(BeNil()) + g.Expect(acceptedCond.Status).To(Equal(metav1.ConditionTrue)) + g.Expect(acceptedCond.Reason).To(Equal(string(gatewayapiv1alpha2.PolicyReasonAccepted))) +} diff --git a/tests/envoygateway/wasm_controller_test.go b/tests/envoygateway/wasm_controller_test.go new file mode 100644 index 000000000..a2f835d5b --- /dev/null +++ b/tests/envoygateway/wasm_controller_test.go @@ -0,0 +1,372 @@ +//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" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "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" + + kuadrantv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2" + "github.com/kuadrant/kuadrant-operator/controllers" + "github.com/kuadrant/kuadrant-operator/pkg/common" + "github.com/kuadrant/kuadrant-operator/pkg/rlptools/wasm" + "github.com/kuadrant/kuadrant-operator/tests" +) + +var _ = Describe("wasm 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 := testClient().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 *kuadrantv1beta2.RateLimitPolicy)) *kuadrantv1beta2.RateLimitPolicy { + policy := &kuadrantv1beta2.RateLimitPolicy{ + TypeMeta: metav1.TypeMeta{ + Kind: "RateLimitPolicy", + APIVersion: kuadrantv1beta2.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "rlp", + Namespace: testNamespace, + }, + Spec: kuadrantv1beta2.RateLimitPolicySpec{}, + } + + for _, mutateFn := range mutateFns { + mutateFn(policy) + } + + return policy + } + + randomHostFromGWHost := func() string { + return strings.Replace(gwHost, "*", rand.String(4), 1) + } + + Context("RateLimitPolicy attached to the gateway", func() { + + var ( + gwPolicy *kuadrantv1beta2.RateLimitPolicy + gwRoute *gatewayapiv1.HTTPRoute + ) + + BeforeEach(func(ctx SpecContext) { + gwRoute = tests.BuildBasicHttpRoute(TestHTTPRouteName, TestGatewayName, testNamespace, []string{randomHostFromGWHost()}) + err := testClient().Create(ctx, gwRoute) + Expect(err).ToNot(HaveOccurred()) + Eventually(tests.RouteIsAccepted(ctx, testClient(), client.ObjectKeyFromObject(gwRoute))).WithContext(ctx).Should(BeTrue()) + + gwPolicy = policyFactory(func(policy *kuadrantv1beta2.RateLimitPolicy) { + policy.Name = "gw" + policy.Spec.TargetRef.Group = gatewayapiv1.GroupName + policy.Spec.TargetRef.Kind = "Gateway" + policy.Spec.TargetRef.Name = TestGatewayName + policy.Spec.Defaults = &kuadrantv1beta2.RateLimitPolicyCommonSpec{ + Limits: map[string]kuadrantv1beta2.Limit{ + "l1": { + Rates: []kuadrantv1beta2.Rate{ + { + Limit: 1, Duration: 3, Unit: "minute", + }, + }, + }, + }, + } + }) + + gwPolicyKey := client.ObjectKeyFromObject(gwPolicy) + + err = testClient().Create(ctx, gwPolicy) + logf.Log.V(1).Info("Creating RateLimitPolicy", "key", gwPolicyKey.String(), "error", err) + Expect(err).ToNot(HaveOccurred()) + + // check policy status + Eventually(tests.IsRLPAcceptedAndEnforced). + WithContext(ctx). + WithArguments(testClient(), gwPolicyKey).Should(Succeed()) + }) + + It("Creates envoyextensionpolicy", func(ctx SpecContext) { + extKey := client.ObjectKey{ + Name: controllers.EnvoyExtensionPolicyName(TestGatewayName), + Namespace: testNamespace, + } + + gwPolicyKey := client.ObjectKeyFromObject(gwPolicy) + + Eventually(IsEnvoyExtensionPolicyAccepted). + WithContext(ctx). + WithArguments(testClient(), extKey, client.ObjectKeyFromObject(gateway)). + Should(Succeed()) + + ext := &egv1alpha1.EnvoyExtensionPolicy{} + err := testClient().Get(ctx, extKey, ext) + // must exist + Expect(err).ToNot(HaveOccurred()) + + Expect(ext.Spec.PolicyTargetReferences.TargetRefs).To(HaveLen(1)) + Expect(ext.Spec.PolicyTargetReferences.TargetRefs[0].LocalPolicyTargetReference.Group).To(Equal(gatewayapiv1.Group("gateway.networking.k8s.io"))) + Expect(ext.Spec.PolicyTargetReferences.TargetRefs[0].LocalPolicyTargetReference.Kind).To(Equal(gatewayapiv1.Kind("Gateway"))) + Expect(ext.Spec.PolicyTargetReferences.TargetRefs[0].LocalPolicyTargetReference.Name).To(Equal(gatewayapiv1.ObjectName(gateway.Name))) + Expect(ext.Spec.Wasm).To(HaveLen(1)) + Expect(ext.Spec.Wasm[0].Code.Type).To(Equal(egv1alpha1.ImageWasmCodeSourceType)) + Expect(ext.Spec.Wasm[0].Code.Image).To(Not(BeNil())) + Expect(ext.Spec.Wasm[0].Code.Image.URL).To(Equal(controllers.WASMFilterImageURL)) + existingWASMConfig, err := wasm.ConfigFromJSON(ext.Spec.Wasm[0].Config) + Expect(err).ToNot(HaveOccurred()) + Expect(existingWASMConfig).To(Equal(&wasm.Config{ + FailureMode: wasm.FailureModeDeny, + RateLimitPolicies: []wasm.RateLimitPolicy{ + { + Name: gwPolicyKey.String(), + Domain: wasm.LimitsNamespaceFromRLP(gwPolicy), + Rules: []wasm.Rule{ + { + Conditions: []wasm.Condition{ + { + AllOf: []wasm.PatternExpression{ + { + Selector: "request.url_path", + Operator: wasm.PatternOperator(kuadrantv1beta2.StartsWithOperator), + Value: "/toy", + }, + { + Selector: "request.method", + Operator: wasm.PatternOperator(kuadrantv1beta2.EqualOperator), + Value: "GET", + }, + }, + }, + }, + Data: []wasm.DataItem{ + { + Static: &wasm.StaticSpec{ + Key: wasm.LimitNameToLimitadorIdentifier(gwPolicyKey, "l1"), + Value: "1", + }, + }, + }, + }, + }, + Hostnames: []string{gwHost}, + Service: common.KuadrantRateLimitClusterName, + }, + }, + })) + }, testTimeOut) + + It("Deletes envoyextensionpolicy when rate limit policy is deleted", func(ctx SpecContext) { + gwPolicyKey := client.ObjectKeyFromObject(gwPolicy) + err := testClient().Delete(ctx, gwPolicy) + logf.Log.V(1).Info("Deleting RateLimitPolicy", "key", gwPolicyKey.String(), "error", err) + Expect(err).ToNot(HaveOccurred()) + + extKey := client.ObjectKey{ + Name: controllers.EnvoyExtensionPolicyName(TestGatewayName), + Namespace: testNamespace, + } + + Eventually(func() bool { + err := testClient().Get(ctx, extKey, &egv1alpha1.EnvoyExtensionPolicy{}) + logf.Log.V(1).Info("Fetching EnvoyExtensionPolicy", "key", extKey.String(), "error", err) + return apierrors.IsNotFound(err) + }).WithContext(ctx).Should(BeTrue()) + }, testTimeOut) + + It("Deletes envoyextensionpolicy if gateway is deleted", func(ctx SpecContext) { + err := testClient().Delete(ctx, gateway) + logf.Log.V(1).Info("Deleting Gateway", "key", client.ObjectKeyFromObject(gateway).String(), "error", err) + Expect(err).ToNot(HaveOccurred()) + + extKey := client.ObjectKey{ + Name: controllers.EnvoyExtensionPolicyName(TestGatewayName), + Namespace: testNamespace, + } + + Eventually(func() bool { + err := testClient().Get(ctx, extKey, &egv1alpha1.EnvoyExtensionPolicy{}) + logf.Log.V(1).Info("Fetching EnvoyExtensionPolicy", "key", extKey.String(), "error", err) + return apierrors.IsNotFound(err) + }).WithContext(ctx).Should(BeTrue()) + }, testTimeOut) + }) + + Context("RateLimitPolicy attached to the route", func() { + + var ( + routePolicy *kuadrantv1beta2.RateLimitPolicy + gwRoute *gatewayapiv1.HTTPRoute + ) + + BeforeEach(func(ctx SpecContext) { + gwRoute = tests.BuildBasicHttpRoute(TestHTTPRouteName, TestGatewayName, testNamespace, []string{randomHostFromGWHost()}) + err := testClient().Create(ctx, gwRoute) + Expect(err).ToNot(HaveOccurred()) + Eventually(tests.RouteIsAccepted(ctx, testClient(), client.ObjectKeyFromObject(gwRoute))).WithContext(ctx).Should(BeTrue()) + + routePolicy = policyFactory(func(policy *kuadrantv1beta2.RateLimitPolicy) { + policy.Name = "route" + policy.Spec.TargetRef.Group = gatewayapiv1.GroupName + policy.Spec.TargetRef.Kind = "HTTPRoute" + policy.Spec.TargetRef.Name = TestHTTPRouteName + policy.Spec.Defaults = &kuadrantv1beta2.RateLimitPolicyCommonSpec{ + Limits: map[string]kuadrantv1beta2.Limit{ + "l1": { + Rates: []kuadrantv1beta2.Rate{ + { + Limit: 1, Duration: 3, Unit: "minute", + }, + }, + }, + }, + } + }) + + err = testClient().Create(ctx, routePolicy) + logf.Log.V(1).Info("Creating RateLimitPolicy", "key", client.ObjectKeyFromObject(routePolicy).String(), "error", err) + Expect(err).ToNot(HaveOccurred()) + + // check policy status + Eventually(tests.IsRLPAcceptedAndEnforced). + WithContext(ctx). + WithArguments(testClient(), client.ObjectKeyFromObject(routePolicy)).Should(Succeed()) + }) + + It("Creates envoyextensionpolicy", func(ctx SpecContext) { + extKey := client.ObjectKey{ + Name: controllers.EnvoyExtensionPolicyName(TestGatewayName), + Namespace: testNamespace, + } + + routePolicyKey := client.ObjectKeyFromObject(routePolicy) + + Eventually(IsEnvoyExtensionPolicyAccepted). + WithContext(ctx). + WithArguments(testClient(), extKey, client.ObjectKeyFromObject(gateway)). + Should(Succeed()) + + ext := &egv1alpha1.EnvoyExtensionPolicy{} + err := testClient().Get(ctx, extKey, ext) + // must exist + Expect(err).ToNot(HaveOccurred()) + + Expect(gwRoute.Spec.Hostnames).To(Not(BeEmpty())) + Expect(ext.Spec.PolicyTargetReferences.TargetRefs).To(HaveLen(1)) + Expect(ext.Spec.PolicyTargetReferences.TargetRefs[0].LocalPolicyTargetReference.Group).To(Equal(gatewayapiv1.Group("gateway.networking.k8s.io"))) + Expect(ext.Spec.PolicyTargetReferences.TargetRefs[0].LocalPolicyTargetReference.Kind).To(Equal(gatewayapiv1.Kind("Gateway"))) + Expect(ext.Spec.PolicyTargetReferences.TargetRefs[0].LocalPolicyTargetReference.Name).To(Equal(gatewayapiv1.ObjectName(gateway.Name))) + Expect(ext.Spec.Wasm).To(HaveLen(1)) + Expect(ext.Spec.Wasm[0].Code.Type).To(Equal(egv1alpha1.ImageWasmCodeSourceType)) + Expect(ext.Spec.Wasm[0].Code.Image).To(Not(BeNil())) + Expect(ext.Spec.Wasm[0].Code.Image.URL).To(Equal(controllers.WASMFilterImageURL)) + existingWASMConfig, err := wasm.ConfigFromJSON(ext.Spec.Wasm[0].Config) + Expect(err).ToNot(HaveOccurred()) + Expect(existingWASMConfig).To(Equal(&wasm.Config{ + FailureMode: wasm.FailureModeDeny, + RateLimitPolicies: []wasm.RateLimitPolicy{ + { + Name: routePolicyKey.String(), + Domain: wasm.LimitsNamespaceFromRLP(routePolicy), + Rules: []wasm.Rule{ + { + Conditions: []wasm.Condition{ + { + AllOf: []wasm.PatternExpression{ + { + Selector: "request.url_path", + Operator: wasm.PatternOperator(kuadrantv1beta2.StartsWithOperator), + Value: "/toy", + }, + { + Selector: "request.method", + Operator: wasm.PatternOperator(kuadrantv1beta2.EqualOperator), + Value: "GET", + }, + }, + }, + }, + Data: []wasm.DataItem{ + { + Static: &wasm.StaticSpec{ + Key: wasm.LimitNameToLimitadorIdentifier(routePolicyKey, "l1"), + Value: "1", + }, + }, + }, + }, + }, + Hostnames: []string{string(gwRoute.Spec.Hostnames[0])}, + Service: common.KuadrantRateLimitClusterName, + }, + }, + })) + }, testTimeOut) + + It("Deletes envoyextensionpolicy when rate limit policy is deleted", func(ctx SpecContext) { + routePolicyKey := client.ObjectKeyFromObject(routePolicy) + err := testClient().Delete(ctx, routePolicy) + logf.Log.V(1).Info("Deleting RateLimitPolicy", "key", routePolicyKey.String(), "error", err) + Expect(err).ToNot(HaveOccurred()) + + extKey := client.ObjectKey{ + Name: controllers.EnvoyExtensionPolicyName(TestGatewayName), + Namespace: testNamespace, + } + + Eventually(func() bool { + err := testClient().Get(ctx, extKey, &egv1alpha1.EnvoyExtensionPolicy{}) + logf.Log.V(1).Info("Fetching EnvoyExtensionPolicy", "key", extKey.String(), "error", err) + return apierrors.IsNotFound(err) + }).WithContext(ctx).Should(BeTrue()) + }, testTimeOut) + + It("Deletes envoyextensionpolicy if route is deleted", func(ctx SpecContext) { + gwRouteKey := client.ObjectKeyFromObject(gwRoute) + err := testClient().Delete(ctx, gwRoute) + logf.Log.V(1).Info("Deleting Route", "key", gwRouteKey.String(), "error", err) + Expect(err).ToNot(HaveOccurred()) + + extKey := client.ObjectKey{ + Name: controllers.EnvoyExtensionPolicyName(TestGatewayName), + Namespace: testNamespace, + } + + Eventually(func() bool { + err := testClient().Get(ctx, extKey, &egv1alpha1.EnvoyExtensionPolicy{}) + logf.Log.V(1).Info("Fetching EnvoyExtensionPolicy", "key", extKey.String(), "error", err) + return apierrors.IsNotFound(err) + }).WithContext(ctx).Should(BeTrue()) + }, testTimeOut) + }) +}) diff --git a/tests/istio/authpolicy_controller_authorizationpolicy_test.go b/tests/istio/authpolicy_controller_authorizationpolicy_test.go index d903fb13b..1fe53bc9c 100644 --- a/tests/istio/authpolicy_controller_authorizationpolicy_test.go +++ b/tests/istio/authpolicy_controller_authorizationpolicy_test.go @@ -7,7 +7,6 @@ import ( "strings" "time" - authorinoapi "github.com/kuadrant/authorino/api/v1beta2" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" secv1beta1resources "istio.io/client-go/pkg/apis/security/v1beta1" @@ -71,7 +70,7 @@ var _ = Describe("AuthPolicy controller managing authorization policy", func() { Name: TestHTTPRouteName, }, Defaults: &kuadrantv1beta2.AuthPolicyCommonSpec{ - AuthScheme: testBasicAuthScheme(), + AuthScheme: tests.BuildBasicAuthScheme(), }, }, } @@ -566,28 +565,3 @@ var _ = Describe("AuthPolicy controller managing authorization policy", func() { }, testTimeOut) }) }) - -func testBasicAuthScheme() *kuadrantv1beta2.AuthSchemeSpec { - return &kuadrantv1beta2.AuthSchemeSpec{ - Authentication: map[string]kuadrantv1beta2.AuthenticationSpec{ - "apiKey": { - 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", - }, - }, - }, - }, - }, - } -} diff --git a/tests/istio/rate_limiting_istio_wasmplugin_controller_test.go b/tests/istio/rate_limiting_istio_wasmplugin_controller_test.go index 482d39d70..a5b06a1f0 100644 --- a/tests/istio/rate_limiting_istio_wasmplugin_controller_test.go +++ b/tests/istio/rate_limiting_istio_wasmplugin_controller_test.go @@ -23,7 +23,6 @@ import ( "github.com/kuadrant/kuadrant-operator/controllers" "github.com/kuadrant/kuadrant-operator/pkg/common" "github.com/kuadrant/kuadrant-operator/pkg/library/kuadrant" - "github.com/kuadrant/kuadrant-operator/pkg/rlptools" "github.com/kuadrant/kuadrant-operator/pkg/rlptools/wasm" "github.com/kuadrant/kuadrant-operator/tests" ) @@ -132,7 +131,7 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { RateLimitPolicies: []wasm.RateLimitPolicy{ { Name: rlpKey.String(), - Domain: rlptools.LimitsNamespaceFromRLP(rlp), + Domain: wasm.LimitsNamespaceFromRLP(rlp), Rules: []wasm.Rule{ { Conditions: []wasm.Condition{ @@ -291,7 +290,7 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { Expect(existingWASMConfig.RateLimitPolicies).To(HaveLen(1)) wasmRLP := existingWASMConfig.RateLimitPolicies[0] Expect(wasmRLP.Name).To(Equal(rlpKey.String())) - Expect(wasmRLP.Domain).To(Equal(rlptools.LimitsNamespaceFromRLP(rlp))) + Expect(wasmRLP.Domain).To(Equal(wasm.LimitsNamespaceFromRLP(rlp))) Expect(wasmRLP.Rules).To(ContainElement(wasm.Rule{ // rule to activate the 'toys' limit definition Conditions: []wasm.Condition{ { @@ -435,7 +434,7 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { RateLimitPolicies: []wasm.RateLimitPolicy{ { Name: rlpKey.String(), - Domain: rlptools.LimitsNamespaceFromRLP(rlp), + Domain: wasm.LimitsNamespaceFromRLP(rlp), Rules: []wasm.Rule{ { Conditions: []wasm.Condition{ @@ -750,7 +749,7 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { RateLimitPolicies: []wasm.RateLimitPolicy{ { Name: rlpAKey.String(), - Domain: rlptools.LimitsNamespaceFromRLP(rlpA), + Domain: wasm.LimitsNamespaceFromRLP(rlpA), Rules: []wasm.Rule{ { Conditions: []wasm.Condition{ @@ -898,7 +897,7 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { RateLimitPolicies: []wasm.RateLimitPolicy{ { Name: rlpKey.String(), - Domain: rlptools.LimitsNamespaceFromRLP(rlpA), + Domain: wasm.LimitsNamespaceFromRLP(rlpA), Rules: []wasm.Rule{ { Conditions: []wasm.Condition{ @@ -1093,7 +1092,7 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { RateLimitPolicies: []wasm.RateLimitPolicy{ { Name: rlpKey.String(), - Domain: rlptools.LimitsNamespaceFromRLP(rlpA), + Domain: wasm.LimitsNamespaceFromRLP(rlpA), Rules: []wasm.Rule{ { Conditions: []wasm.Condition{ @@ -1209,7 +1208,7 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { RateLimitPolicies: []wasm.RateLimitPolicy{ { Name: rlpKey.String(), - Domain: rlptools.LimitsNamespaceFromRLP(rlpA), + Domain: wasm.LimitsNamespaceFromRLP(rlpA), Rules: []wasm.Rule{ { Conditions: []wasm.Condition{ @@ -1390,7 +1389,7 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { RateLimitPolicies: []wasm.RateLimitPolicy{ { Name: rlpKey.String(), - Domain: rlptools.LimitsNamespaceFromRLP(rlpR), + Domain: wasm.LimitsNamespaceFromRLP(rlpR), Rules: []wasm.Rule{ { Conditions: []wasm.Condition{ @@ -1469,7 +1468,7 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { RateLimitPolicies: []wasm.RateLimitPolicy{ { Name: rlpKey.String(), - Domain: rlptools.LimitsNamespaceFromRLP(rlpR), + Domain: wasm.LimitsNamespaceFromRLP(rlpR), Rules: []wasm.Rule{ { Conditions: []wasm.Condition{ @@ -1625,7 +1624,7 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { RateLimitPolicies: []wasm.RateLimitPolicy{ { Name: rlp1Key.String(), - Domain: rlptools.LimitsNamespaceFromRLP(rlp1), + Domain: wasm.LimitsNamespaceFromRLP(rlp1), Rules: []wasm.Rule{ { Conditions: []wasm.Condition{ @@ -1729,7 +1728,7 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { RateLimitPolicies: []wasm.RateLimitPolicy{ { Name: rlp2Key.String(), - Domain: rlptools.LimitsNamespaceFromRLP(rlp2), + Domain: wasm.LimitsNamespaceFromRLP(rlp2), Rules: []wasm.Rule{ { Conditions: []wasm.Condition{ @@ -1919,7 +1918,7 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { RateLimitPolicies: []wasm.RateLimitPolicy{ { Name: rlp2Key.String(), - Domain: rlptools.LimitsNamespaceFromRLP(rlp2), + Domain: wasm.LimitsNamespaceFromRLP(rlp2), Rules: []wasm.Rule{ { Conditions: []wasm.Condition{ @@ -2013,7 +2012,7 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { RateLimitPolicies: []wasm.RateLimitPolicy{ { // First RLP 1 as the controller will sort based on RLP name Name: rlp1Key.String(), // Route B affected by RLP 1 -> Gateway - Domain: rlptools.LimitsNamespaceFromRLP(rlp1), + Domain: wasm.LimitsNamespaceFromRLP(rlp1), Rules: []wasm.Rule{ { Conditions: []wasm.Condition{ @@ -2047,7 +2046,7 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { }, { Name: rlp2Key.String(), // Route A affected by RLP 1 -> Route A - Domain: rlptools.LimitsNamespaceFromRLP(rlp2), + Domain: wasm.LimitsNamespaceFromRLP(rlp2), Rules: []wasm.Rule{ { Conditions: []wasm.Condition{ @@ -2175,7 +2174,7 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { RateLimitPolicies: []wasm.RateLimitPolicy{ { Name: rlpKey.String(), - Domain: rlptools.LimitsNamespaceFromRLP(rlp), + Domain: wasm.LimitsNamespaceFromRLP(rlp), Rules: []wasm.Rule{ { Conditions: []wasm.Condition{ @@ -2234,7 +2233,7 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { RateLimitPolicies: []wasm.RateLimitPolicy{ { Name: rlpKey.String(), - Domain: rlptools.LimitsNamespaceFromRLP(rlp), + Domain: wasm.LimitsNamespaceFromRLP(rlp), Rules: []wasm.Rule{ { Conditions: []wasm.Condition{ diff --git a/tests/istio/suite_test.go b/tests/istio/suite_test.go index c044ae287..1d2ac04ea 100644 --- a/tests/istio/suite_test.go +++ b/tests/istio/suite_test.go @@ -24,6 +24,9 @@ import ( "os" "testing" + "github.com/kuadrant/kuadrant-operator/controllers" + "github.com/kuadrant/kuadrant-operator/pkg/log" + "github.com/kuadrant/kuadrant-operator/tests" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "k8s.io/client-go/rest" @@ -31,10 +34,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/envtest" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" - - "github.com/kuadrant/kuadrant-operator/controllers" - "github.com/kuadrant/kuadrant-operator/pkg/log" - "github.com/kuadrant/kuadrant-operator/tests" ) // These tests use Ginkgo (BDD-style Go testing framework). Refer to @@ -111,6 +110,9 @@ var _ = SynchronizedBeforeSuite(func() []byte { k8sClient, err = client.New(cfg, client.Options{Scheme: s}) Expect(err).NotTo(HaveOccurred()) Expect(k8sClient).NotTo(BeNil()) + + tests.GatewayClassName = os.Getenv("GATEWAYAPI_PROVIDER") + Expect(tests.GatewayClassName).To(Equal("istio"), "Please make sure GATEWAYAPI_PROVIDER is set correctly.") }) var _ = SynchronizedAfterSuite(func() {}, func() {