From 0f87fcbad1aaa2913af3f768da87ea72a695a14d Mon Sep 17 00:00:00 2001 From: Guy Daich Date: Wed, 3 Apr 2024 08:04:42 -0500 Subject: [PATCH] feat(translator): Envoy Extension Policy and ExtProc (#2991) * start eep Signed-off-by: Guy Daich * fixes Signed-off-by: Guy Daich * tests Signed-off-by: Guy Daich * ext-proc xds Signed-off-by: Guy Daich * impl envoygateway config, remove priority, change backendref Signed-off-by: Guy Daich * fix cel Signed-off-by: Guy Daich * enable by default Signed-off-by: Guy Daich * fix nit Signed-off-by: Guy Daich * revert generated metrics data Signed-off-by: Guy Daich * fix review comments Signed-off-by: Guy Daich * nit: remove unused struct Signed-off-by: Guy Daich --------- Signed-off-by: Guy Daich --- api/v1alpha1/envoyextensionypolicy_types.go | 14 +- api/v1alpha1/envoygateway_types.go | 4 - api/v1alpha1/ext_proc_types.go | 29 + api/v1alpha1/zz_generated.deepcopy.go | 39 ++ ....envoyproxy.io_envoyextensionpolicies.yaml | 107 ++- charts/gateway-helm/templates/_rbac.tpl | 2 + internal/gatewayapi/envoyextensionpolicy.go | 430 ++++++++++++ internal/gatewayapi/ext_service.go | 104 +++ internal/gatewayapi/resource.go | 2 + internal/gatewayapi/runner/runner.go | 21 +- internal/gatewayapi/securitypolicy.go | 75 +-- ...tensionpolicy-invalid-cross-ns-ref.in.yaml | 31 + ...ensionpolicy-invalid-cross-ns-ref.out.yaml | 104 +++ ...oyextensionpolicy-override-replace.in.yaml | 106 +++ ...yextensionpolicy-override-replace.out.yaml | 272 ++++++++ ...yextensionpolicy-status-conditions.in.yaml | 207 ++++++ ...extensionpolicy-status-conditions.out.yaml | 621 ++++++++++++++++++ ...h-extproc-invalid-no-matching-port.in.yaml | 60 ++ ...-extproc-invalid-no-matching-port.out.yaml | 162 +++++ ...olicy-with-extproc-invalid-no-port.in.yaml | 59 ++ ...licy-with-extproc-invalid-no-port.out.yaml | 162 +++++ ...extproc-invalid-no-reference-grant.in.yaml | 61 ++ ...xtproc-invalid-no-reference-grant.out.yaml | 164 +++++ ...cy-with-extproc-invalid-no-service.in.yaml | 52 ++ ...y-with-extproc-invalid-no-service.out.yaml | 163 +++++ ...with-extproc-with-backendtlspolicy.in.yaml | 222 +++++++ ...ith-extproc-with-backendtlspolicy.out.yaml | 354 ++++++++++ internal/gatewayapi/translator.go | 9 +- internal/gatewayapi/zz_generated.deepcopy.go | 11 + internal/ir/xds.go | 16 + internal/ir/zz_generated.deepcopy.go | 23 + internal/message/types.go | 2 + internal/provider/kubernetes/controller.go | 98 +++ internal/provider/kubernetes/indexers.go | 70 +- internal/provider/kubernetes/predicates.go | 24 +- .../provider/kubernetes/predicates_test.go | 59 ++ internal/provider/kubernetes/status.go | 32 + internal/status/status.go | 7 +- internal/xds/translator/extauth.go | 34 - internal/xds/translator/extproc.go | 175 +++++ internal/xds/translator/httpfilters.go | 6 +- .../testdata/in/xds-ir/ext-proc.yaml | 81 +++ .../out/xds-ir/ext-proc.clusters.yaml | 34 + .../out/xds-ir/ext-proc.endpoints.yaml | 24 + .../out/xds-ir/ext-proc.listeners.yaml | 34 + .../testdata/out/xds-ir/ext-proc.routes.yaml | 21 + internal/xds/translator/translator_test.go | 3 + internal/xds/translator/utils.go | 39 ++ site/content/en/latest/api/extension_types.md | 37 +- .../design/envoy-extension-policy.md | 2 - .../ext-proc-envoyextensionpolicy.yaml | 70 ++ test/e2e/testdata/ext-proc-service.yaml | 391 +++++++++++ test/e2e/tests/ext_proc.go | 124 ++++ test/e2e/tests/utils.go | 22 + test/helm/default.yaml | 2 + 55 files changed, 4919 insertions(+), 158 deletions(-) create mode 100644 api/v1alpha1/ext_proc_types.go create mode 100644 internal/gatewayapi/envoyextensionpolicy.go create mode 100644 internal/gatewayapi/ext_service.go create mode 100644 internal/gatewayapi/testdata/envoyextensionpolicy-invalid-cross-ns-ref.in.yaml create mode 100755 internal/gatewayapi/testdata/envoyextensionpolicy-invalid-cross-ns-ref.out.yaml create mode 100644 internal/gatewayapi/testdata/envoyextensionpolicy-override-replace.in.yaml create mode 100755 internal/gatewayapi/testdata/envoyextensionpolicy-override-replace.out.yaml create mode 100644 internal/gatewayapi/testdata/envoyextensionpolicy-status-conditions.in.yaml create mode 100755 internal/gatewayapi/testdata/envoyextensionpolicy-status-conditions.out.yaml create mode 100644 internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-invalid-no-matching-port.in.yaml create mode 100755 internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-invalid-no-matching-port.out.yaml create mode 100644 internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-invalid-no-port.in.yaml create mode 100755 internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-invalid-no-port.out.yaml create mode 100644 internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-invalid-no-reference-grant.in.yaml create mode 100755 internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-invalid-no-reference-grant.out.yaml create mode 100644 internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-invalid-no-service.in.yaml create mode 100755 internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-invalid-no-service.out.yaml create mode 100644 internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-with-backendtlspolicy.in.yaml create mode 100755 internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-with-backendtlspolicy.out.yaml create mode 100644 internal/xds/translator/extproc.go create mode 100644 internal/xds/translator/testdata/in/xds-ir/ext-proc.yaml create mode 100755 internal/xds/translator/testdata/out/xds-ir/ext-proc.clusters.yaml create mode 100755 internal/xds/translator/testdata/out/xds-ir/ext-proc.endpoints.yaml create mode 100755 internal/xds/translator/testdata/out/xds-ir/ext-proc.listeners.yaml create mode 100755 internal/xds/translator/testdata/out/xds-ir/ext-proc.routes.yaml create mode 100644 test/e2e/testdata/ext-proc-envoyextensionpolicy.yaml create mode 100644 test/e2e/testdata/ext-proc-service.yaml create mode 100644 test/e2e/tests/ext_proc.go diff --git a/api/v1alpha1/envoyextensionypolicy_types.go b/api/v1alpha1/envoyextensionypolicy_types.go index 5a36ff9b6c6..f4bb6aa9d56 100644 --- a/api/v1alpha1/envoyextensionypolicy_types.go +++ b/api/v1alpha1/envoyextensionypolicy_types.go @@ -46,22 +46,16 @@ type EnvoyExtensionPolicySpec struct { // TargetRef TargetRef gwapiv1a2.PolicyTargetReferenceWithSectionName `json:"targetRef"` - // Priority of the EnvoyExtensionPolicy. - // If multiple EnvoyExtensionPolices are applied to the same - // TargetRef, extensions will execute in the ascending order of - // the priority i.e. int32.min has the highest priority and - // int32.max has the lowest priority. - // Defaults to 0. - // - // +optional - Priority int32 `json:"priority,omitempty"` - // WASM is a list of Wasm extensions to be loaded by the Gateway. // Order matters, as the extensions will be loaded in the order they are // defined in this list. // // +optional WASM []Wasm `json:"wasm,omitempty"` + + // ExtProc is an ordered list of external processing filters + // that should added to the envoy filter chain + ExtProc []ExtProc `json:"extProc,omitempty"` } //+kubebuilder:object:root=true diff --git a/api/v1alpha1/envoygateway_types.go b/api/v1alpha1/envoygateway_types.go index 777b6c50950..ea9e509d960 100644 --- a/api/v1alpha1/envoygateway_types.go +++ b/api/v1alpha1/envoygateway_types.go @@ -151,10 +151,6 @@ type ExtensionAPISettings struct { // EnableEnvoyPatchPolicy enables Envoy Gateway to // reconcile and implement the EnvoyPatchPolicy resources. EnableEnvoyPatchPolicy bool `json:"enableEnvoyPatchPolicy"` - - // EnableEnvoyExtensionPolicy enables Envoy Gateway to - // reconcile and implement the EnvoyExtensionPolicy resources. - EnableEnvoyExtensionPolicy bool `json:"enableEnvoyExtensionPolicy"` } // EnvoyGatewayProvider defines the desired configuration of a provider. diff --git a/api/v1alpha1/ext_proc_types.go b/api/v1alpha1/ext_proc_types.go new file mode 100644 index 00000000000..c94682198b4 --- /dev/null +++ b/api/v1alpha1/ext_proc_types.go @@ -0,0 +1,29 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +package v1alpha1 + +import ( + gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" +) + +// +kubebuilder:validation:XValidation:rule="has(self.backendRef) ? (!has(self.backendRef.group) || self.backendRef.group == \"\") : true", message="group is invalid, only the core API group (specified by omitting the group field or setting it to an empty string) is supported" +// +kubebuilder:validation:XValidation:rule="has(self.backendRef) ? (!has(self.backendRef.kind) || self.backendRef.kind == 'Service') : true", message="kind is invalid, only Service (specified by omitting the kind field or setting it to 'Service') is supported" +// +// ExtProc defines the configuration for External Processing filter. +type ExtProc struct { + // Service defines the configuration of the external processing service + BackendRef ExtProcBackendRef `json:"backendRef"` +} + +// ExtProcService defines the gRPC External Processing service using the envoy grpc client +// The processing request and response messages are defined in +// https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/ext_proc/v3/external_processor.proto +type ExtProcBackendRef struct { + // BackendObjectReference references a Kubernetes object that represents the + // backend server to which the processing requests will be sent. + // Only service Kind is supported for now. + gwapiv1.BackendObjectReference `json:",inline"` +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 9b3bbab456b..a5005944bbe 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -773,6 +773,13 @@ func (in *EnvoyExtensionPolicySpec) DeepCopyInto(out *EnvoyExtensionPolicySpec) (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.ExtProc != nil { + in, out := &in.ExtProc, &out.ExtProc + *out = make([]ExtProc, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvoyExtensionPolicySpec. @@ -1479,6 +1486,38 @@ func (in *ExtAuth) DeepCopy() *ExtAuth { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExtProc) DeepCopyInto(out *ExtProc) { + *out = *in + in.BackendRef.DeepCopyInto(&out.BackendRef) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtProc. +func (in *ExtProc) DeepCopy() *ExtProc { + if in == nil { + return nil + } + out := new(ExtProc) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExtProcBackendRef) DeepCopyInto(out *ExtProcBackendRef) { + *out = *in + in.BackendObjectReference.DeepCopyInto(&out.BackendObjectReference) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtProcBackendRef. +func (in *ExtProcBackendRef) DeepCopy() *ExtProcBackendRef { + if in == nil { + return nil + } + out := new(ExtProcBackendRef) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ExtensionAPISettings) DeepCopyInto(out *ExtensionAPISettings) { *out = *in diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml index 7be025deeba..1207b989e8f 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml @@ -49,16 +49,105 @@ spec: spec: description: Spec defines the desired state of EnvoyExtensionPolicy. properties: - priority: + extProc: description: |- - Priority of the EnvoyExtensionPolicy. - If multiple EnvoyExtensionPolices are applied to the same - TargetRef, extensions will execute in the ascending order of - the priority i.e. int32.min has the highest priority and - int32.max has the lowest priority. - Defaults to 0. - format: int32 - type: integer + ExtProc is an ordered list of external processing filters + that should added to the envoy filter chain + items: + description: ExtProc defines the configuration for External Processing + filter. + properties: + backendRef: + description: Service defines the configuration of the external + processing service + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + + Defaults to "Service" when not specified. + + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + + Support: Core (Services with a type other than ExternalName) + + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + required: + - backendRef + type: object + x-kubernetes-validations: + - message: group is invalid, only the core API group (specified + by omitting the group field or setting it to an empty string) + is supported + rule: 'has(self.backendRef) ? (!has(self.backendRef.group) || + self.backendRef.group == "") : true' + - message: kind is invalid, only Service (specified by omitting + the kind field or setting it to 'Service') is supported + rule: 'has(self.backendRef) ? (!has(self.backendRef.kind) || self.backendRef.kind + == ''Service'') : true' + type: array targetRef: description: |- TargetRef is the name of the Gateway resource this policy diff --git a/charts/gateway-helm/templates/_rbac.tpl b/charts/gateway-helm/templates/_rbac.tpl index 104f5f0f014..4cf4082a4c2 100644 --- a/charts/gateway-helm/templates/_rbac.tpl +++ b/charts/gateway-helm/templates/_rbac.tpl @@ -69,6 +69,7 @@ resources: - clienttrafficpolicies - backendtrafficpolicies - securitypolicies +- envoyextensionpolicies verbs: - get - list @@ -83,6 +84,7 @@ resources: - clienttrafficpolicies/status - backendtrafficpolicies/status - securitypolicies/status +- envoyextensionpolicies/status verbs: - update {{- end }} diff --git a/internal/gatewayapi/envoyextensionpolicy.go b/internal/gatewayapi/envoyextensionpolicy.go new file mode 100644 index 00000000000..ca843d4d7a9 --- /dev/null +++ b/internal/gatewayapi/envoyextensionpolicy.go @@ -0,0 +1,430 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +package gatewayapi + +import ( + "fmt" + "sort" + "strings" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/utils/ptr" + + gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" + gwv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" + "github.com/envoyproxy/gateway/internal/ir" + "github.com/envoyproxy/gateway/internal/status" + "github.com/envoyproxy/gateway/internal/utils" +) + +func (t *Translator) ProcessEnvoyExtensionPolicies(envoyExtensionPolicies []*egv1a1.EnvoyExtensionPolicy, + gateways []*GatewayContext, + routes []RouteContext, + resources *Resources, + xdsIR XdsIRMap) []*egv1a1.EnvoyExtensionPolicy { + var res []*egv1a1.EnvoyExtensionPolicy + + // Sort based on timestamp + sort.Slice(envoyExtensionPolicies, func(i, j int) bool { + return envoyExtensionPolicies[i].CreationTimestamp.Before(&(envoyExtensionPolicies[j].CreationTimestamp)) + }) + + // First build a map out of the routes and gateways for faster lookup since users might have thousands of routes or more. + routeMap := map[policyTargetRouteKey]*policyRouteTargetContext{} + for _, route := range routes { + key := policyTargetRouteKey{ + Kind: string(GetRouteType(route)), + Name: route.GetName(), + Namespace: route.GetNamespace(), + } + routeMap[key] = &policyRouteTargetContext{RouteContext: route} + } + + gatewayMap := map[types.NamespacedName]*policyGatewayTargetContext{} + for _, gw := range gateways { + key := utils.NamespacedName(gw) + gatewayMap[key] = &policyGatewayTargetContext{GatewayContext: gw} + } + + // Map of Gateway to the routes attached to it + gatewayRouteMap := make(map[string]sets.Set[string]) + + // Translate + // 1. First translate Policies targeting xRoutes + // 2. Finally, the policies targeting Gateways + + // Process the policies targeting xRoutes + for _, policy := range envoyExtensionPolicies { + if policy.Spec.TargetRef.Kind != KindGateway { + policy := policy.DeepCopy() + res = append(res, policy) + + // Negative statuses have already been assigned so its safe to skip + route, resolveErr := resolveEEPolicyRouteTargetRef(policy, routeMap) + if route == nil { + continue + } + + // Find the Gateway that the route belongs to and add it to the + // gatewayRouteMap and ancestor list, which will be used to check + // policy overrides and populate its ancestor status. + parentRefs := GetParentReferences(route) + ancestorRefs := make([]gwv1a2.ParentReference, 0, len(parentRefs)) + for _, p := range parentRefs { + if p.Kind == nil || *p.Kind == KindGateway { + namespace := route.GetNamespace() + if p.Namespace != nil { + namespace = string(*p.Namespace) + } + gwNN := types.NamespacedName{ + Namespace: namespace, + Name: string(p.Name), + } + + key := gwNN.String() + if _, ok := gatewayRouteMap[key]; !ok { + gatewayRouteMap[key] = make(sets.Set[string]) + } + gatewayRouteMap[key].Insert(utils.NamespacedName(route).String()) + + // Do need a section name since the policy is targeting to a route + ancestorRefs = append(ancestorRefs, getAncestorRefForPolicy(gwNN, p.SectionName)) + } + } + + // Set conditions for resolve error, then skip current xroute + if resolveErr != nil { + status.SetResolveErrorForPolicyAncestors(&policy.Status, + ancestorRefs, + t.GatewayControllerName, + policy.Generation, + resolveErr, + ) + + continue + } + + // Set conditions for translation error if it got any + if err := t.translateEnvoyExtensionPolicyForRoute(policy, route, xdsIR, resources); err != nil { + status.SetTranslationErrorForPolicyAncestors(&policy.Status, + ancestorRefs, + t.GatewayControllerName, + policy.Generation, + status.Error2ConditionMsg(err), + ) + } + + // Set Accepted condition if it is unset + status.SetAcceptedForPolicyAncestors(&policy.Status, ancestorRefs, t.GatewayControllerName) + } + } + + // Process the policies targeting Gateways + for _, policy := range envoyExtensionPolicies { + if policy.Spec.TargetRef.Kind == KindGateway { + policy := policy.DeepCopy() + res = append(res, policy) + + // Negative statuses have already been assigned so its safe to skip + gateway, resolveErr := resolveEEPolicyGatewayTargetRef(policy, gatewayMap) + if gateway == nil { + continue + } + + // Find its ancestor reference by resolved gateway, even with resolve error + gatewayNN := utils.NamespacedName(gateway) + ancestorRefs := []gwv1a2.ParentReference{ + // Don't need a section name since the policy is targeting to a gateway + getAncestorRefForPolicy(gatewayNN, nil), + } + + // Set conditions for resolve error, then skip current gateway + if resolveErr != nil { + status.SetResolveErrorForPolicyAncestors(&policy.Status, + ancestorRefs, + t.GatewayControllerName, + policy.Generation, + resolveErr, + ) + + continue + } + + // Set conditions for translation error if it got any + if err := t.translateEnvoyExtensionPolicyForGateway(policy, gateway, xdsIR, resources); err != nil { + status.SetTranslationErrorForPolicyAncestors(&policy.Status, + ancestorRefs, + t.GatewayControllerName, + policy.Generation, + status.Error2ConditionMsg(err), + ) + } + + // Set Accepted condition if it is unset + status.SetAcceptedForPolicyAncestors(&policy.Status, ancestorRefs, t.GatewayControllerName) + + // Check if this policy is overridden by other policies targeting at + // route level + if r, ok := gatewayRouteMap[gatewayNN.String()]; ok { + // Maintain order here to ensure status/string does not change with the same data + routes := r.UnsortedList() + sort.Strings(routes) + message := fmt.Sprintf("This policy is being overridden by other envoyExtensionPolicies for these routes: %v", routes) + + status.SetConditionForPolicyAncestors(&policy.Status, + ancestorRefs, + t.GatewayControllerName, + egv1a1.PolicyConditionOverridden, + metav1.ConditionTrue, + egv1a1.PolicyReasonOverridden, + message, + policy.Generation, + ) + } + } + } + + return res +} + +func resolveEEPolicyGatewayTargetRef(policy *egv1a1.EnvoyExtensionPolicy, gateways map[types.NamespacedName]*policyGatewayTargetContext) (*GatewayContext, *status.PolicyResolveError) { + targetNs := policy.Spec.TargetRef.Namespace + // If empty, default to namespace of policy + if targetNs == nil { + targetNs = ptr.To(gwv1b1.Namespace(policy.Namespace)) + } + + // Check if the gateway exists + key := types.NamespacedName{ + Name: string(policy.Spec.TargetRef.Name), + Namespace: string(*targetNs), + } + gateway, ok := gateways[key] + + // Gateway not found + if !ok { + return nil, nil + } + + // Ensure Policy and target are in the same namespace + if policy.Namespace != string(*targetNs) { + message := fmt.Sprintf("Namespace:%s TargetRef.Namespace:%s, EnvoyExtensionPolicy can only target a resource in the same namespace.", + policy.Namespace, *targetNs) + + return gateway.GatewayContext, &status.PolicyResolveError{ + Reason: gwv1a2.PolicyReasonInvalid, + Message: message, + } + } + + // Check if another policy targeting the same Gateway exists + if gateway.attached { + message := "Unable to target Gateway, another EnvoyExtensionPolicy has already attached to it" + + return gateway.GatewayContext, &status.PolicyResolveError{ + Reason: gwv1a2.PolicyReasonConflicted, + Message: message, + } + } + + // Set context and save + gateway.attached = true + gateways[key] = gateway + + return gateway.GatewayContext, nil +} + +func resolveEEPolicyRouteTargetRef(policy *egv1a1.EnvoyExtensionPolicy, routes map[policyTargetRouteKey]*policyRouteTargetContext) (RouteContext, *status.PolicyResolveError) { + targetNs := policy.Spec.TargetRef.Namespace + // If empty, default to namespace of policy + if targetNs == nil { + targetNs = ptr.To(gwv1b1.Namespace(policy.Namespace)) + } + + // Check if the route exists + key := policyTargetRouteKey{ + Kind: string(policy.Spec.TargetRef.Kind), + Name: string(policy.Spec.TargetRef.Name), + Namespace: string(*targetNs), + } + + route, ok := routes[key] + // Route not found + if !ok { + return nil, nil + } + + // Ensure Policy and target are in the same namespace + if policy.Namespace != string(*targetNs) { + message := fmt.Sprintf("Namespace:%s TargetRef.Namespace:%s, EnvoyExtensionPolicy can only target a resource in the same namespace.", + policy.Namespace, *targetNs) + + return route.RouteContext, &status.PolicyResolveError{ + Reason: gwv1a2.PolicyReasonInvalid, + Message: message, + } + } + + // Check if another policy targeting the same xRoute exists + if route.attached { + message := fmt.Sprintf("Unable to target %s, another EnvoyExtensionPolicy has already attached to it", + string(policy.Spec.TargetRef.Kind)) + + return route.RouteContext, &status.PolicyResolveError{ + Reason: gwv1a2.PolicyReasonConflicted, + Message: message, + } + } + + // Set context and save + route.attached = true + routes[key] = route + + return route.RouteContext, nil +} + +func (t *Translator) translateEnvoyExtensionPolicyForRoute(policy *egv1a1.EnvoyExtensionPolicy, route RouteContext, + xdsIR XdsIRMap, resources *Resources) error { + // Apply IR to all relevant routes + prefix := irRoutePrefix(route) + for _, ir := range xdsIR { + for _, http := range ir.HTTP { + for _, r := range http.Routes { + // Apply if there is a match + if strings.HasPrefix(r.Name, prefix) { + if extProcs, err := t.buildExtProcs(policy, resources); err == nil { + r.ExtProcs = extProcs + } else { + return err + } + } + } + } + } + + return nil +} + +func (t *Translator) buildExtProcs(policy *egv1a1.EnvoyExtensionPolicy, resources *Resources) ([]ir.ExtProc, error) { + var extProcIRList []ir.ExtProc + + if policy == nil { + return nil, nil + } + + if len(policy.Spec.ExtProc) > 0 { + for idx, ep := range policy.Spec.ExtProc { + name := irConfigNameForEEP(policy, idx) + extProcIR, err := t.buildExtProc(name, utils.NamespacedName(policy), ep, idx, resources) + if err != nil { + return nil, err + } + extProcIRList = append(extProcIRList, *extProcIR) + } + } + return extProcIRList, nil +} + +func (t *Translator) translateEnvoyExtensionPolicyForGateway(policy *egv1a1.EnvoyExtensionPolicy, + gateway *GatewayContext, xdsIR XdsIRMap, resources *Resources) error { + + irKey := t.getIRKey(gateway.Gateway) + // Should exist since we've validated this + ir := xdsIR[irKey] + + policyTarget := irStringKey( + string(ptr.Deref(policy.Spec.TargetRef.Namespace, gwv1a2.Namespace(policy.Namespace))), + string(policy.Spec.TargetRef.Name), + ) + + extProcs, err := t.buildExtProcs(policy, resources) + if err != nil { + return err + } + + for _, http := range ir.HTTP { + gatewayName := http.Name[0:strings.LastIndex(http.Name, "/")] + if t.MergeGateways && gatewayName != policyTarget { + continue + } + + // A Policy targeting the most specific scope(xRoute) wins over a policy + // targeting a lesser specific scope(Gateway). + for _, r := range http.Routes { + // if already set - there's a route level policy, so skip + if r.ExtProcs == nil { + r.ExtProcs = extProcs + } + } + } + + return nil +} + +func (t *Translator) buildExtProc( + name string, + policyNamespacedName types.NamespacedName, + extProc egv1a1.ExtProc, + extProcIdx int, + resources *Resources) (*ir.ExtProc, error) { + var ( + backendRef *gwapiv1.BackendObjectReference + ds *ir.DestinationSetting + authority string + err error + ) + + backendRef = &extProc.BackendRef.BackendObjectReference + + if err = t.validateExtServiceBackendReference( + backendRef, + policyNamespacedName.Namespace, + resources); err != nil { + return nil, err + } + + authority = fmt.Sprintf( + "%s.%s:%d", + backendRef.Name, + NamespaceDerefOr(backendRef.Namespace, policyNamespacedName.Namespace), + *backendRef.Port) + + if ds, err = t.processExtServiceDestination( + backendRef, + policyNamespacedName, + egv1a1.KindEnvoyExtensionPolicy, + ir.GRPC, + resources); err != nil { + return nil, err + } + + rd := ir.RouteDestination{ + Name: irIndexedExtServiceDestinationName(policyNamespacedName, egv1a1.KindEnvoyExtensionPolicy, + string(backendRef.Name), extProcIdx), + Settings: []*ir.DestinationSetting{ds}, + } + + extProcIR := &ir.ExtProc{ + Name: name, + Destination: rd, + Authority: authority, + } + + return extProcIR, err +} + +func irConfigNameForEEP(policy *egv1a1.EnvoyExtensionPolicy, idx int) string { + return fmt.Sprintf( + "%s/%s/%d", + strings.ToLower(egv1a1.KindEnvoyExtensionPolicy), + utils.NamespacedName(policy).String(), + idx) +} diff --git a/internal/gatewayapi/ext_service.go b/internal/gatewayapi/ext_service.go new file mode 100644 index 00000000000..a14e2b1258e --- /dev/null +++ b/internal/gatewayapi/ext_service.go @@ -0,0 +1,104 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +package gatewayapi + +import ( + "errors" + "fmt" + "strings" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" + gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" + egv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + + egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" + "github.com/envoyproxy/gateway/internal/ir" +) + +// TODO: zhaohuabing combine this function with the one in the route translator +func (t *Translator) processExtServiceDestination( + backendRef *gwapiv1.BackendObjectReference, + policyNamespacedName types.NamespacedName, + policyKind string, + protocol ir.AppProtocol, + resources *Resources) (*ir.DestinationSetting, error) { + var ( + endpoints []*ir.DestinationEndpoint + addrType *ir.DestinationAddressType + servicePort v1.ServicePort + backendTLS *ir.TLSUpstreamConfig + ) + + serviceNamespace := NamespaceDerefOr(backendRef.Namespace, policyNamespacedName.Namespace) + service := resources.GetService(serviceNamespace, string(backendRef.Name)) + for _, port := range service.Spec.Ports { + if port.Port == int32(*backendRef.Port) { + servicePort = port + break + } + } + + if servicePort.AppProtocol != nil && + *servicePort.AppProtocol == "kubernetes.io/h2c" { + protocol = ir.HTTP2 + } + + // Route to endpoints by default + if !t.EndpointRoutingDisabled { + endpointSlices := resources.GetEndpointSlicesForBackend( + serviceNamespace, string(backendRef.Name), KindService) + endpoints, addrType = getIREndpointsFromEndpointSlices( + endpointSlices, servicePort.Name, servicePort.Protocol) + } else { + // Fall back to Service ClusterIP routing + ep := ir.NewDestEndpoint( + service.Spec.ClusterIP, + uint32(*backendRef.Port)) + endpoints = append(endpoints, ep) + } + + // TODO: support mixed endpointslice address type for the same backendRef + if !t.EndpointRoutingDisabled && addrType != nil && *addrType == ir.MIXED { + return nil, errors.New( + "mixed endpointslice address type for the same backendRef is not supported") + } + + backendTLS = t.processBackendTLSPolicy( + *backendRef, + serviceNamespace, + // Gateway is not the appropriate parent reference here because the owner + // of the BackendRef is the policy, and there is no hierarchy + // relationship between the policy and a gateway. + // The owner policy of the BackendRef is used as the parent reference here. + egv1a2.ParentReference{ + Group: ptr.To(gwapiv1.Group(egv1a1.GroupName)), + Kind: ptr.To(gwapiv1.Kind(policyKind)), + Namespace: ptr.To(gwapiv1.Namespace(policyNamespacedName.Namespace)), + Name: gwapiv1.ObjectName(policyNamespacedName.Name), + }, + resources) + + return &ir.DestinationSetting{ + Weight: ptr.To(uint32(1)), + Protocol: protocol, + Endpoints: endpoints, + AddressType: addrType, + TLS: backendTLS, + }, nil +} + +// TODO: also refer to extension type, as WASM may also introduce destinations +func irIndexedExtServiceDestinationName(policyNamespacedName types.NamespacedName, policyKind, service string, idx int) string { + return strings.ToLower(fmt.Sprintf( + "%s/%s/%s/%d/%s", + policyKind, + policyNamespacedName.Namespace, + policyNamespacedName.Name, + idx, + service)) +} diff --git a/internal/gatewayapi/resource.go b/internal/gatewayapi/resource.go index 6a8f37e33b1..f930801c2bf 100644 --- a/internal/gatewayapi/resource.go +++ b/internal/gatewayapi/resource.go @@ -52,6 +52,7 @@ type Resources struct { BackendTrafficPolicies []*egv1a1.BackendTrafficPolicy `json:"backendTrafficPolicies,omitempty" yaml:"backendTrafficPolicies,omitempty"` SecurityPolicies []*egv1a1.SecurityPolicy `json:"securityPolicies,omitempty" yaml:"securityPolicies,omitempty"` BackendTLSPolicies []*gwapiv1a2.BackendTLSPolicy `json:"backendTLSPolicies,omitempty" yaml:"backendTLSPolicies,omitempty"` + EnvoyExtensionPolicies []*egv1a1.EnvoyExtensionPolicy `json:"envoyExtensionPolicies,omitempty" yaml:"envoyExtensionPolicies,omitempty"` } func NewResources() *Resources { @@ -72,6 +73,7 @@ func NewResources() *Resources { BackendTrafficPolicies: []*egv1a1.BackendTrafficPolicy{}, SecurityPolicies: []*egv1a1.SecurityPolicy{}, BackendTLSPolicies: []*gwapiv1a2.BackendTLSPolicy{}, + EnvoyExtensionPolicies: []*egv1a1.EnvoyExtensionPolicy{}, } } diff --git a/internal/gatewayapi/runner/runner.go b/internal/gatewayapi/runner/runner.go index abc1016aa83..4ef2d3d2702 100644 --- a/internal/gatewayapi/runner/runner.go +++ b/internal/gatewayapi/runner/runner.go @@ -196,6 +196,14 @@ func (r *Runner) subscribeAndTranslate(ctx context.Context) { } delete(statusesToDelete.SecurityPolicyStatusKeys, key) } + for _, envoyExtensionPolicy := range result.EnvoyExtensionPolicies { + envoyExtensionPolicy := envoyExtensionPolicy + key := utils.NamespacedName(envoyExtensionPolicy) + if !(reflect.ValueOf(envoyExtensionPolicy.Status).IsZero()) { + r.ProviderResources.EnvoyExtensionPolicyStatuses.Store(key, &envoyExtensionPolicy.Status) + } + delete(statusesToDelete.EnvoyExtensionPolicyStatusKeys, key) + } } // Delete IR keys @@ -233,6 +241,7 @@ type StatusesToDelete struct { ClientTrafficPolicyStatusKeys map[types.NamespacedName]bool BackendTrafficPolicyStatusKeys map[types.NamespacedName]bool SecurityPolicyStatusKeys map[types.NamespacedName]bool + EnvoyExtensionPolicyStatusKeys map[types.NamespacedName]bool } func (r *Runner) getAllStatuses() *StatusesToDelete { @@ -249,6 +258,7 @@ func (r *Runner) getAllStatuses() *StatusesToDelete { BackendTrafficPolicyStatusKeys: make(map[types.NamespacedName]bool), SecurityPolicyStatusKeys: make(map[types.NamespacedName]bool), BackendTLSPolicyStatusKeys: make(map[types.NamespacedName]bool), + EnvoyExtensionPolicyStatusKeys: make(map[types.NamespacedName]bool), } // Get current status keys @@ -283,7 +293,9 @@ func (r *Runner) getAllStatuses() *StatusesToDelete { for key := range r.ProviderResources.SecurityPolicyStatuses.LoadAll() { ds.SecurityPolicyStatusKeys[key] = true } - + for key := range r.ProviderResources.EnvoyExtensionPolicyStatuses.LoadAll() { + ds.EnvoyExtensionPolicyStatusKeys[key] = true + } return ds } @@ -329,6 +341,10 @@ func (r *Runner) deleteStatusKeys(ds *StatusesToDelete) { r.ProviderResources.BackendTLSPolicyStatuses.Delete(key) delete(ds.BackendTLSPolicyStatusKeys, key) } + for key := range ds.EnvoyExtensionPolicyStatusKeys { + r.ProviderResources.EnvoyExtensionPolicyStatuses.Delete(key) + delete(ds.EnvoyExtensionPolicyStatusKeys, key) + } } // deleteAllStatusKeys deletes all status keys stored by the subscriber. @@ -366,6 +382,9 @@ func (r *Runner) deleteAllStatusKeys() { for key := range r.ProviderResources.SecurityPolicyStatuses.LoadAll() { r.ProviderResources.SecurityPolicyStatuses.Delete(key) } + for key := range r.ProviderResources.EnvoyExtensionPolicyStatuses.LoadAll() { + r.ProviderResources.EnvoyExtensionPolicyStatuses.Delete(key) + } } // getIRKeysToDelete returns the list of IR keys to delete diff --git a/internal/gatewayapi/securitypolicy.go b/internal/gatewayapi/securitypolicy.go index 63d13b60777..b0c0976534d 100644 --- a/internal/gatewayapi/securitypolicy.go +++ b/internal/gatewayapi/securitypolicy.go @@ -805,9 +805,11 @@ func (t *Translator) buildExtAuth( NamespaceDerefOr(backendRef.Namespace, policy.Namespace), *backendRef.Port) + pnn := utils.NamespacedName(policy) if ds, err = t.processExtServiceDestination( backendRef, - policy, + pnn, + KindSecurityPolicy, protocol, resources); err != nil { return nil, err @@ -839,77 +841,6 @@ func (t *Translator) buildExtAuth( return extAuth, nil } -// TODO: zhaohuabing combine this function with the one in the route translator -func (t *Translator) processExtServiceDestination( - backendRef *gwapiv1.BackendObjectReference, - policy *egv1a1.SecurityPolicy, - protocol ir.AppProtocol, - resources *Resources) (*ir.DestinationSetting, error) { - var ( - endpoints []*ir.DestinationEndpoint - addrType *ir.DestinationAddressType - servicePort v1.ServicePort - backendTLS *ir.TLSUpstreamConfig - ) - - serviceNamespace := NamespaceDerefOr(backendRef.Namespace, policy.Namespace) - service := resources.GetService(serviceNamespace, string(backendRef.Name)) - for _, port := range service.Spec.Ports { - if port.Port == int32(*backendRef.Port) { - servicePort = port - break - } - } - - if servicePort.AppProtocol != nil && - *servicePort.AppProtocol == "kubernetes.io/h2c" { - protocol = ir.HTTP2 - } - - // Route to endpoints by default - if !t.EndpointRoutingDisabled { - endpointSlices := resources.GetEndpointSlicesForBackend( - serviceNamespace, string(backendRef.Name), KindService) - endpoints, addrType = getIREndpointsFromEndpointSlices( - endpointSlices, servicePort.Name, servicePort.Protocol) - } else { - // Fall back to Service ClusterIP routing - ep := ir.NewDestEndpoint( - service.Spec.ClusterIP, - uint32(*backendRef.Port)) - endpoints = append(endpoints, ep) - } - - // TODO: support mixed endpointslice address type for the same backendRef - if !t.EndpointRoutingDisabled && addrType != nil && *addrType == ir.MIXED { - return nil, errors.New( - "mixed endpointslice address type for the same backendRef is not supported") - } - - backendTLS = t.processBackendTLSPolicy( - *backendRef, - serviceNamespace, - // Gateway is not the appropriate parent reference here because the owner - // of the BackendRef is the security policy, and there is no hierarchy - // relationship between the security policy and a gateway. - // The owner security policy of the BackendRef is used as the parent reference here. - gwv1a2.ParentReference{ - Group: ptr.To(gwapiv1.Group(egv1a1.GroupName)), - Kind: ptr.To(gwapiv1.Kind(egv1a1.KindSecurityPolicy)), - Namespace: ptr.To(gwapiv1.Namespace(policy.Namespace)), - Name: gwapiv1.ObjectName(policy.Name), - }, - resources) - - return &ir.DestinationSetting{ - Weight: ptr.To(uint32(1)), - Protocol: protocol, - Endpoints: endpoints, - AddressType: addrType, - TLS: backendTLS, - }, nil -} - func irExtServiceDestinationName(policy *egv1a1.SecurityPolicy, backendRef *gwapiv1.BackendObjectReference) string { nn := types.NamespacedName{ Name: string(backendRef.Name), diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-invalid-cross-ns-ref.in.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-invalid-cross-ns-ref.in.yaml new file mode 100644 index 00000000000..0f6d26cf62c --- /dev/null +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-invalid-cross-ns-ref.in.yaml @@ -0,0 +1,31 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All +envoyExtensionPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + namespace: default + name: policy-for-gateway-1 + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + extProc: + - backendRef: + name: grpc-backend-4 + port: 4000 diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-invalid-cross-ns-ref.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-invalid-cross-ns-ref.out.yaml new file mode 100755 index 00000000000..983b95a6114 --- /dev/null +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-invalid-cross-ns-ref.out.yaml @@ -0,0 +1,104 @@ +envoyExtensionPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + creationTimestamp: null + name: policy-for-gateway-1 + namespace: default + spec: + extProc: + - backendRef: + name: grpc-backend-4 + port: 4000 + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + conditions: + - lastTransitionTime: null + message: Namespace:default TargetRef.Namespace:envoy-gateway, EnvoyExtensionPolicy + can only target a resource in the same namespace. + reason: Invalid + status: "False" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 0 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 +xdsIR: + envoy-gateway/gateway-1: + accessLog: + text: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-override-replace.in.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-override-replace.in.yaml new file mode 100644 index 00000000000..ce4ecde3766 --- /dev/null +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-override-replace.in.yaml @@ -0,0 +1,106 @@ +services: +- apiVersion: v1 + kind: Service + metadata: + namespace: envoy-gateway + name: grpc-backend + spec: + ports: + - port: 9000 + name: grpc + protocol: TCP +- apiVersion: v1 + kind: Service + metadata: + namespace: default + name: grpc-backend-2 + spec: + ports: + - port: 8000 + name: grpc + protocol: TCP +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/foo" + backendRefs: + - name: service-1 + port: 8080 +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-2 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/bar" + backendRefs: + - name: service-1 + port: 8080 +envoyExtensionPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + namespace: envoy-gateway + name: policy-for-gateway-1 + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + extProc: + - backendRef: + name: grpc-backend + port: 9000 +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + namespace: default + name: policy-for-route-1 + spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + namespace: default + extProc: + - backendRef: + name: grpc-backend-2 + port: 8000 diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-override-replace.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-override-replace.out.yaml new file mode 100755 index 00000000000..1adc83f74aa --- /dev/null +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-override-replace.out.yaml @@ -0,0 +1,272 @@ +envoyExtensionPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + creationTimestamp: null + name: policy-for-route-1 + namespace: default + spec: + extProc: + - backendRef: + name: grpc-backend-2 + port: 8000 + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + namespace: default + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + creationTimestamp: null + name: policy-for-gateway-1 + namespace: envoy-gateway + spec: + extProc: + - backendRef: + name: grpc-backend + port: 9000 + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: 'This policy is being overridden by other envoyExtensionPolicies + for these routes: [default/httproute-1]' + reason: Overridden + status: "True" + type: Overridden + controllerName: gateway.envoyproxy.io/gatewayclass-controller +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 2 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + value: /foo + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-2 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + value: /bar + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 +xdsIR: + envoy-gateway/gateway-1: + accessLog: + text: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - backendWeights: + invalid: 0 + valid: 0 + destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + extProc: + - authority: grpc-backend-2.default:8000 + destination: + name: envoyextensionpolicy/default/policy-for-route-1/0/grpc-backend-2 + settings: + - protocol: GRPC + weight: 1 + name: envoyextensionpolicy/default/policy-for-route-1/0 + hostname: gateway.envoyproxy.io + isHTTP2: false + name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: /foo + - backendWeights: + invalid: 0 + valid: 0 + destination: + name: httproute/default/httproute-2/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + extProc: + - authority: grpc-backend.envoy-gateway:9000 + destination: + name: envoyextensionpolicy/envoy-gateway/policy-for-gateway-1/0/grpc-backend + settings: + - protocol: GRPC + weight: 1 + name: envoyextensionpolicy/envoy-gateway/policy-for-gateway-1/0 + hostname: gateway.envoyproxy.io + isHTTP2: false + name: httproute/default/httproute-2/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: /bar diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-status-conditions.in.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-status-conditions.in.yaml new file mode 100644 index 00000000000..3e2c4c63e1a --- /dev/null +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-status-conditions.in.yaml @@ -0,0 +1,207 @@ +EnvoyExtensionPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + namespace: envoy-gateway + name: target-gateway-1 + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + namespace: envoy-gateway + name: target-gateway-1-as-well + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + namespace: envoy-gateway + name: target-unknown-gateway + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: unknown-gateway + namespace: envoy-gateway +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + namespace: envoy-gateway + name: not-same-namespace-gateway + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: not-same-namespace-gateway + namespace: another-namespace +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + namespace: envoy-gateway + name: target-httproute-in-gateway-1 + spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + namespace: envoy-gateway +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + namespace: envoy-gateway + name: also-target-httproute-in-gateway-1 + spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + namespace: envoy-gateway +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + namespace: envoy-gateway + name: target-grpcroute-in-gateway-2 + spec: + targetRef: + group: gateway.networking.k8s.io + kind: GRPCRoute + name: grpcroute-1 + namespace: envoy-gateway +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + namespace: envoy-gateway + name: target-unknown-httproute + spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: unknown-httproute + namespace: envoy-gateway +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + namespace: envoy-gateway + name: not-same-namespace-httproute + spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: not-same-namespace-httproute + namespace: another-namespace +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1beta1 + kind: HTTPRoute + metadata: + namespace: envoy-gateway + name: httproute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - matches: + - path: + value: "/" + backendRefs: + - name: service-1 + port: 8080 +- apiVersion: gateway.networking.k8s.io/v1beta1 + kind: HTTPRoute + metadata: + namespace: another-namespace + name: not-same-namespace-httproute + spec: + parentRefs: + - namespace: another-namespace + name: not-same-namespace-gateway + rules: + - matches: + - path: + value: "/" + backendRefs: + - name: service-1 + port: 8080 +grpcRoutes: +- apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: GRPCRoute + metadata: + namespace: envoy-gateway + name: grpcroute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-2 + rules: + - matches: + - headers: + - type: Exact + name: magic + value: foo + backendRefs: + - name: service-1 + port: 8080 +gateways: +- apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: Same +- apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-2 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: Same + - name: https + protocol: HTTPS + port: 443 + allowedRoutes: + namespaces: + from: Same + - name: tcp + protocol: TCP + port: 53 + allowedRoutes: + namespaces: + from: Same +- apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + namespace: another-namespace + name: not-same-namespace-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: Same diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-status-conditions.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-status-conditions.out.yaml new file mode 100755 index 00000000000..9d6ba198049 --- /dev/null +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-status-conditions.out.yaml @@ -0,0 +1,621 @@ +envoyExtensionPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + creationTimestamp: null + name: target-httproute-in-gateway-1 + namespace: envoy-gateway + spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + namespace: envoy-gateway + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + creationTimestamp: null + name: also-target-httproute-in-gateway-1 + namespace: envoy-gateway + spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + namespace: envoy-gateway + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + conditions: + - lastTransitionTime: null + message: Unable to target HTTPRoute, another EnvoyExtensionPolicy has already + attached to it + reason: Conflicted + status: "False" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + creationTimestamp: null + name: target-grpcroute-in-gateway-2 + namespace: envoy-gateway + spec: + targetRef: + group: gateway.networking.k8s.io + kind: GRPCRoute + name: grpcroute-1 + namespace: envoy-gateway + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-2 + namespace: envoy-gateway + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + creationTimestamp: null + name: target-unknown-httproute + namespace: envoy-gateway + spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: unknown-httproute + namespace: envoy-gateway + status: + ancestors: null +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + creationTimestamp: null + name: not-same-namespace-httproute + namespace: envoy-gateway + spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: not-same-namespace-httproute + namespace: another-namespace + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: not-same-namespace-gateway + namespace: another-namespace + conditions: + - lastTransitionTime: null + message: Namespace:envoy-gateway TargetRef.Namespace:another-namespace, EnvoyExtensionPolicy + can only target a resource in the same namespace. + reason: Invalid + status: "False" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + creationTimestamp: null + name: target-gateway-1 + namespace: envoy-gateway + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: 'This policy is being overridden by other envoyExtensionPolicies + for these routes: [envoy-gateway/httproute-1]' + reason: Overridden + status: "True" + type: Overridden + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + creationTimestamp: null + name: target-gateway-1-as-well + namespace: envoy-gateway + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + conditions: + - lastTransitionTime: null + message: Unable to target Gateway, another EnvoyExtensionPolicy has already + attached to it + reason: Conflicted + status: "False" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + creationTimestamp: null + name: target-unknown-gateway + namespace: envoy-gateway + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: unknown-gateway + namespace: envoy-gateway + status: + ancestors: null +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + creationTimestamp: null + name: not-same-namespace-gateway + namespace: envoy-gateway + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: not-same-namespace-gateway + namespace: another-namespace + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: not-same-namespace-gateway + namespace: another-namespace + conditions: + - lastTransitionTime: null + message: Namespace:envoy-gateway TargetRef.Namespace:another-namespace, EnvoyExtensionPolicy + can only target a resource in the same namespace. + reason: Invalid + status: "False" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +gateways: +- apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: Same + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +- apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-2 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: Same + name: http + port: 80 + protocol: HTTP + - allowedRoutes: + namespaces: + from: Same + name: https + port: 443 + protocol: HTTPS + - allowedRoutes: + namespaces: + from: Same + name: tcp + port: 53 + protocol: TCP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Listener must have TLS set when protocol is HTTPS. + reason: Invalid + status: "False" + type: Programmed + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: https + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute + - attachedRoutes: 0 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: tcp + supportedKinds: + - group: gateway.networking.k8s.io + kind: TCPRoute +- apiVersion: gateway.networking.k8s.io/v1beta1 + kind: Gateway + metadata: + creationTimestamp: null + name: not-same-namespace-gateway + namespace: another-namespace + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: Same + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 0 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +grpcRoutes: +- apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: GRPCRoute + metadata: + creationTimestamp: null + name: grpcroute-1 + namespace: envoy-gateway + spec: + parentRefs: + - name: gateway-2 + namespace: envoy-gateway + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - headers: + - name: magic + type: Exact + value: foo + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Service envoy-gateway/service-1 not found + reason: BackendNotFound + status: "False" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-2 + namespace: envoy-gateway +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1beta1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: envoy-gateway + spec: + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Service envoy-gateway/service-1 not found + reason: BackendNotFound + status: "False" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway +- apiVersion: gateway.networking.k8s.io/v1beta1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: not-same-namespace-httproute + namespace: another-namespace + spec: + parentRefs: + - name: not-same-namespace-gateway + namespace: another-namespace + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: No listeners included by this parent ref allowed this attachment. + reason: NotAllowedByListeners + status: "False" + type: Accepted + - lastTransitionTime: null + message: Service another-namespace/service-1 not found + reason: BackendNotFound + status: "False" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: not-same-namespace-gateway + namespace: another-namespace +infraIR: + another-namespace/not-same-namespace-gateway: + proxy: + listeners: + - address: null + name: another-namespace/not-same-namespace-gateway/http + ports: + - containerPort: 10080 + name: http + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: not-same-namespace-gateway + gateway.envoyproxy.io/owning-gateway-namespace: another-namespace + name: another-namespace/not-same-namespace-gateway + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 + envoy-gateway/gateway-2: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-2/http + ports: + - containerPort: 10080 + name: http + protocol: HTTP + servicePort: 80 + - address: null + name: envoy-gateway/gateway-2/tcp + ports: + - containerPort: 10053 + name: tcp + protocol: TCP + servicePort: 53 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-2 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-2 +xdsIR: + another-namespace/not-same-namespace-gateway: + accessLog: + text: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + name: another-namespace/not-same-namespace-gateway/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + envoy-gateway/gateway-1: + accessLog: + text: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - backendWeights: + invalid: 1 + valid: 0 + directResponse: + statusCode: 500 + hostname: '*' + isHTTP2: false + name: httproute/envoy-gateway/httproute-1/rule/0/match/0/* + pathMatch: + distinct: false + name: "" + prefix: / + envoy-gateway/gateway-2: + accessLog: + text: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: true + name: envoy-gateway/gateway-2/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - backendWeights: + invalid: 1 + valid: 0 + directResponse: + statusCode: 500 + headerMatches: + - distinct: false + exact: foo + name: magic + hostname: '*' + isHTTP2: true + name: grpcroute/envoy-gateway/grpcroute-1/rule/0/match/0/* diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-invalid-no-matching-port.in.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-invalid-no-matching-port.in.yaml new file mode 100644 index 00000000000..78989127328 --- /dev/null +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-invalid-no-matching-port.in.yaml @@ -0,0 +1,60 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: default + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - www.foo.com + parentRefs: + - namespace: default + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: /foo + backendRefs: + - name: service-1 + port: 8080 +services: +- apiVersion: v1 + kind: Service + metadata: + namespace: default + name: grpc-backend + spec: + ports: + - port: 9000 +envoyExtensionPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + namespace: default + name: policy-for-gateway + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: default + extProc: + - backendRef: + name: grpc-backend + port: 4000 diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-invalid-no-matching-port.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-invalid-no-matching-port.out.yaml new file mode 100755 index 00000000000..394ccff73d5 --- /dev/null +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-invalid-no-matching-port.out.yaml @@ -0,0 +1,162 @@ +envoyExtensionPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + creationTimestamp: null + name: policy-for-gateway + namespace: default + spec: + extProc: + - backendRef: + name: grpc-backend + port: 4000 + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: default + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: default + conditions: + - lastTransitionTime: null + message: TCP Port 4000 not found on service default/grpc-backend + reason: Invalid + status: "False" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: default + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - www.foo.com + parentRefs: + - name: gateway-1 + namespace: default + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + value: /foo + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: default + sectionName: http +infraIR: + default/gateway-1: + proxy: + listeners: + - address: null + name: default/gateway-1/http + ports: + - containerPort: 10080 + name: http + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: default + name: default/gateway-1 +xdsIR: + default/gateway-1: + accessLog: + text: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + name: default/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - backendWeights: + invalid: 0 + valid: 0 + destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: www.foo.com + isHTTP2: false + name: httproute/default/httproute-1/rule/0/match/0/www_foo_com + pathMatch: + distinct: false + name: "" + prefix: /foo diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-invalid-no-port.in.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-invalid-no-port.in.yaml new file mode 100644 index 00000000000..b2420abfa9d --- /dev/null +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-invalid-no-port.in.yaml @@ -0,0 +1,59 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: default + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - www.foo.com + parentRefs: + - namespace: default + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: /foo + backendRefs: + - name: service-1 + port: 8080 +services: +- apiVersion: v1 + kind: Service + metadata: + namespace: default + name: grpc-backend + spec: + ports: + - port: 9000 +envoyExtensionPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + namespace: default + name: policy-for-gateway + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: default + extProc: + - backendRef: + name: grpc-backend diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-invalid-no-port.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-invalid-no-port.out.yaml new file mode 100755 index 00000000000..ea8098edd07 --- /dev/null +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-invalid-no-port.out.yaml @@ -0,0 +1,162 @@ +envoyExtensionPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + creationTimestamp: null + name: policy-for-gateway + namespace: default + spec: + extProc: + - backendRef: + name: grpc-backend + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: default + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: default + conditions: + - lastTransitionTime: null + message: A valid port number corresponding to a port on the Service must be + specified + reason: Invalid + status: "False" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: default + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - www.foo.com + parentRefs: + - name: gateway-1 + namespace: default + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + value: /foo + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: default + sectionName: http +infraIR: + default/gateway-1: + proxy: + listeners: + - address: null + name: default/gateway-1/http + ports: + - containerPort: 10080 + name: http + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: default + name: default/gateway-1 +xdsIR: + default/gateway-1: + accessLog: + text: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + name: default/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - backendWeights: + invalid: 0 + valid: 0 + destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: www.foo.com + isHTTP2: false + name: httproute/default/httproute-1/rule/0/match/0/www_foo_com + pathMatch: + distinct: false + name: "" + prefix: /foo diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-invalid-no-reference-grant.in.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-invalid-no-reference-grant.in.yaml new file mode 100644 index 00000000000..6d3ec1fdd7e --- /dev/null +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-invalid-no-reference-grant.in.yaml @@ -0,0 +1,61 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: default + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - www.foo.com + parentRefs: + - namespace: default + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: /foo + backendRefs: + - name: service-1 + port: 8080 +services: +- apiVersion: v1 + kind: Service + metadata: + namespace: envoy-gateway + name: grpc-backend + spec: + ports: + - port: 9000 +envoyExtensionPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + namespace: default + name: policy-for-gateway + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: default + extProc: + - backendRef: + name: grpc-backend + namespace: envoy-gateway + port: 9000 diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-invalid-no-reference-grant.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-invalid-no-reference-grant.out.yaml new file mode 100755 index 00000000000..50535c1a392 --- /dev/null +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-invalid-no-reference-grant.out.yaml @@ -0,0 +1,164 @@ +envoyExtensionPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + creationTimestamp: null + name: policy-for-gateway + namespace: default + spec: + extProc: + - backendRef: + name: grpc-backend + namespace: envoy-gateway + port: 9000 + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: default + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: default + conditions: + - lastTransitionTime: null + message: Backend ref to Service envoy-gateway/grpc-backend not permitted by + any ReferenceGrant + reason: Invalid + status: "False" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: default + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - www.foo.com + parentRefs: + - name: gateway-1 + namespace: default + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + value: /foo + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: default + sectionName: http +infraIR: + default/gateway-1: + proxy: + listeners: + - address: null + name: default/gateway-1/http + ports: + - containerPort: 10080 + name: http + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: default + name: default/gateway-1 +xdsIR: + default/gateway-1: + accessLog: + text: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + name: default/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - backendWeights: + invalid: 0 + valid: 0 + destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: www.foo.com + isHTTP2: false + name: httproute/default/httproute-1/rule/0/match/0/www_foo_com + pathMatch: + distinct: false + name: "" + prefix: /foo diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-invalid-no-service.in.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-invalid-no-service.in.yaml new file mode 100644 index 00000000000..eff5a17efd2 --- /dev/null +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-invalid-no-service.in.yaml @@ -0,0 +1,52 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: default + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - www.foo.com + parentRefs: + - namespace: default + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: /foo + backendRefs: + - name: service-1 + port: 8080 +envoyExtensionPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + namespace: default + name: policy-for-gateway + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: default + extProc: + - backendRef: + name: grpc-backend + namespace: envoy-gateway + port: 9000 diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-invalid-no-service.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-invalid-no-service.out.yaml new file mode 100755 index 00000000000..27519cb0302 --- /dev/null +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-invalid-no-service.out.yaml @@ -0,0 +1,163 @@ +envoyExtensionPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + creationTimestamp: null + name: policy-for-gateway + namespace: default + spec: + extProc: + - backendRef: + name: grpc-backend + namespace: envoy-gateway + port: 9000 + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: default + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: default + conditions: + - lastTransitionTime: null + message: Service envoy-gateway/grpc-backend not found + reason: Invalid + status: "False" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: default + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - www.foo.com + parentRefs: + - name: gateway-1 + namespace: default + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + value: /foo + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: default + sectionName: http +infraIR: + default/gateway-1: + proxy: + listeners: + - address: null + name: default/gateway-1/http + ports: + - containerPort: 10080 + name: http + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: default + name: default/gateway-1 +xdsIR: + default/gateway-1: + accessLog: + text: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + name: default/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - backendWeights: + invalid: 0 + valid: 0 + destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: www.foo.com + isHTTP2: false + name: httproute/default/httproute-1/rule/0/match/0/www_foo_com + pathMatch: + distinct: false + name: "" + prefix: /foo diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-with-backendtlspolicy.in.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-with-backendtlspolicy.in.yaml new file mode 100644 index 00000000000..21c6fab4506 --- /dev/null +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-with-backendtlspolicy.in.yaml @@ -0,0 +1,222 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: default + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - www.foo.com + parentRefs: + - namespace: default + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: /foo + backendRefs: + - name: service-1 + port: 8080 +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-2 + spec: + hostnames: + - www.bar.com + parentRefs: + - namespace: default + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: /bar + backendRefs: + - name: service-1 + port: 8080 +services: +- apiVersion: v1 + kind: Service + metadata: + namespace: envoy-gateway + name: grpc-backend + spec: + ports: + - port: 8000 + name: grpc + protocol: TCP +- apiVersion: v1 + kind: Service + metadata: + namespace: default + name: grpc-backend-2 + spec: + ports: + - port: 9000 + name: grpc + protocol: TCP +endpointSlices: +- apiVersion: discovery.k8s.io/v1 + kind: EndpointSlice + metadata: + name: endpointslice-grpc-backend + namespace: envoy-gateway + labels: + kubernetes.io/service-name: grpc-backend + addressType: IPv4 + ports: + - name: http + protocol: TCP + port: 8000 + endpoints: + - addresses: + - 7.7.7.7 + conditions: + ready: true +- apiVersion: discovery.k8s.io/v1 + kind: EndpointSlice + metadata: + name: endpointslice-grpc-backend-2 + namespace: default + labels: + kubernetes.io/service-name: grpc-backend-2 + addressType: IPv4 + ports: + - name: grpc + protocol: TCP + port: 9000 + endpoints: + - addresses: + - 8.8.8.8 + conditions: + ready: true +referenceGrants: +- apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: ReferenceGrant + metadata: + namespace: envoy-gateway + name: referencegrant-1 + spec: + from: + - group: gateway.envoyproxy.io + kind: SecurityPolicy + namespace: default + - group: gateway.networking.k8s.io + kind: BackendTLSPolicy + namespace: default + to: + - group: '' + kind: Service +configMaps: +- apiVersion: v1 + kind: ConfigMap + metadata: + name: ca-cmap + namespace: default + data: + ca.crt: | + -----BEGIN CERTIFICATE----- + MIIDJzCCAg+gAwIBAgIUAl6UKIuKmzte81cllz5PfdN2IlIwDQYJKoZIhvcNAQEL + BQAwIzEQMA4GA1UEAwwHbXljaWVudDEPMA0GA1UECgwGa3ViZWRiMB4XDTIzMTAw + MjA1NDE1N1oXDTI0MTAwMTA1NDE1N1owIzEQMA4GA1UEAwwHbXljaWVudDEPMA0G + A1UECgwGa3ViZWRiMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwSTc + 1yj8HW62nynkFbXo4VXKv2jC0PM7dPVky87FweZcTKLoWQVPQE2p2kLDK6OEszmM + yyr+xxWtyiveremrWqnKkNTYhLfYPhgQkczib7eUalmFjUbhWdLvHakbEgCodn3b + kz57mInX2VpiDOKg4kyHfiuXWpiBqrCx0KNLpxo3DEQcFcsQTeTHzh4752GV04RU + Ti/GEWyzIsl4Rg7tGtAwmcIPgUNUfY2Q390FGqdH4ahn+mw/6aFbW31W63d9YJVq + ioyOVcaMIpM5B/c7Qc8SuhCI1YGhUyg4cRHLEw5VtikioyE3X04kna3jQAj54YbR + bpEhc35apKLB21HOUQIDAQABo1MwUTAdBgNVHQ4EFgQUyvl0VI5vJVSuYFXu7B48 + 6PbMEAowHwYDVR0jBBgwFoAUyvl0VI5vJVSuYFXu7B486PbMEAowDwYDVR0TAQH/ + BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAMLxrgFVMuNRq2wAwcBt7SnNR5Cfz + 2MvXq5EUmuawIUi9kaYjwdViDREGSjk7JW17vl576HjDkdfRwi4E28SydRInZf6J + i8HZcZ7caH6DxR335fgHVzLi5NiTce/OjNBQzQ2MJXVDd8DBmG5fyatJiOJQ4bWE + A7FlP0RdP3CO3GWE0M5iXOB2m1qWkE2eyO4UHvwTqNQLdrdAXgDQlbam9e4BG3Gg + d/6thAkWDbt/QNT+EJHDCvhDRKh1RuGHyg+Y+/nebTWWrFWsktRrbOoHCZiCpXI1 + 3eXE6nt0YkgtDxG22KqnhpAg9gUSs2hlhoxyvkzyF0mu6NhPlwAgnq7+/Q== + -----END CERTIFICATE----- +backendTLSPolicies: +- apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: BackendTLSPolicy + metadata: + name: policy-btls-grpc + namespace: default + spec: + targetRef: + group: '' + kind: Service + name: grpc-backend + namespace: envoy-gateway + sectionName: "8000" + tls: + caCertRefs: + - name: ca-cmap + group: '' + kind: ConfigMap + hostname: grpc-backend +- apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: BackendTLSPolicy + metadata: + name: policy-btls-grpc-2 + namespace: default + spec: + targetRef: + group: '' + kind: Service + name: grpc-backend-2 + sectionName: "9000" + tls: + caCertRefs: + - name: ca-cmap + group: '' + kind: ConfigMap + hostname: grpc-backend-2 +envoyExtensionPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + namespace: default + name: policy-for-gateway + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: default + extProc: + - backendRef: + Name: grpc-backend + Namespace: envoy-gateway + Port: 8000 +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + namespace: default + name: policy-for-http-route + spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + namespace: default + extProc: + - backendRef: + Name: grpc-backend-2 + Port: 9000 diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-with-backendtlspolicy.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-with-backendtlspolicy.out.yaml new file mode 100755 index 00000000000..66063672452 --- /dev/null +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-with-backendtlspolicy.out.yaml @@ -0,0 +1,354 @@ +backendTLSPolicies: +- apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: BackendTLSPolicy + metadata: + creationTimestamp: null + name: policy-btls-grpc + namespace: default + spec: + targetRef: + group: "" + kind: Service + name: grpc-backend + namespace: envoy-gateway + sectionName: "8000" + tls: + caCertRefs: + - group: "" + kind: ConfigMap + name: ca-cmap + hostname: grpc-backend + status: + ancestors: + - ancestorRef: + group: gateway.envoyproxy.io + kind: EnvoyExtensionPolicy + name: policy-for-gateway + namespace: default + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: BackendTLSPolicy + metadata: + creationTimestamp: null + name: policy-btls-grpc-2 + namespace: default + spec: + targetRef: + group: "" + kind: Service + name: grpc-backend-2 + sectionName: "9000" + tls: + caCertRefs: + - group: "" + kind: ConfigMap + name: ca-cmap + hostname: grpc-backend-2 + status: + ancestors: + - ancestorRef: + group: gateway.envoyproxy.io + kind: EnvoyExtensionPolicy + name: policy-for-http-route + namespace: default + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +envoyExtensionPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + creationTimestamp: null + name: policy-for-http-route + namespace: default + spec: + extProc: + - backendRef: + name: grpc-backend-2 + port: 9000 + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + namespace: default + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: default + sectionName: http + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + creationTimestamp: null + name: policy-for-gateway + namespace: default + spec: + extProc: + - backendRef: + name: grpc-backend + namespace: envoy-gateway + port: 8000 + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: default + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: default + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: 'This policy is being overridden by other envoyExtensionPolicies + for these routes: [default/httproute-1]' + reason: Overridden + status: "True" + type: Overridden + controllerName: gateway.envoyproxy.io/gatewayclass-controller +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: default + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 2 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - www.foo.com + parentRefs: + - name: gateway-1 + namespace: default + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + value: /foo + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: default + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-2 + namespace: default + spec: + hostnames: + - www.bar.com + parentRefs: + - name: gateway-1 + namespace: default + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + value: /bar + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: default + sectionName: http +infraIR: + default/gateway-1: + proxy: + listeners: + - address: null + name: default/gateway-1/http + ports: + - containerPort: 10080 + name: http + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: default + name: default/gateway-1 +xdsIR: + default/gateway-1: + accessLog: + text: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + name: default/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - backendWeights: + invalid: 0 + valid: 0 + destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + extProc: + - authority: grpc-backend-2.default:9000 + destination: + name: envoyextensionpolicy/default/policy-for-http-route/0/grpc-backend-2 + settings: + - addressType: IP + endpoints: + - host: 8.8.8.8 + port: 9000 + protocol: GRPC + tls: + caCertificate: + certificate: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURKekNDQWcrZ0F3SUJBZ0lVQWw2VUtJdUttenRlODFjbGx6NVBmZE4ySWxJd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0l6RVFNQTRHQTFVRUF3d0hiWGxqYVdWdWRERVBNQTBHQTFVRUNnd0dhM1ZpWldSaU1CNFhEVEl6TVRBdwpNakExTkRFMU4xb1hEVEkwTVRBd01UQTFOREUxTjFvd0l6RVFNQTRHQTFVRUF3d0hiWGxqYVdWdWRERVBNQTBHCkExVUVDZ3dHYTNWaVpXUmlNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXdTVGMKMXlqOEhXNjJueW5rRmJYbzRWWEt2MmpDMFBNN2RQVmt5ODdGd2VaY1RLTG9XUVZQUUUycDJrTERLNk9Fc3ptTQp5eXIreHhXdHlpdmVyZW1yV3FuS2tOVFloTGZZUGhnUWtjemliN2VVYWxtRmpVYmhXZEx2SGFrYkVnQ29kbjNiCmt6NTdtSW5YMlZwaURPS2c0a3lIZml1WFdwaUJxckN4MEtOTHB4bzNERVFjRmNzUVRlVEh6aDQ3NTJHVjA0UlUKVGkvR0VXeXpJc2w0Umc3dEd0QXdtY0lQZ1VOVWZZMlEzOTBGR3FkSDRhaG4rbXcvNmFGYlczMVc2M2Q5WUpWcQppb3lPVmNhTUlwTTVCL2M3UWM4U3VoQ0kxWUdoVXlnNGNSSExFdzVWdGlraW95RTNYMDRrbmEzalFBajU0WWJSCmJwRWhjMzVhcEtMQjIxSE9VUUlEQVFBQm8xTXdVVEFkQmdOVkhRNEVGZ1FVeXZsMFZJNXZKVlN1WUZYdTdCNDgKNlBiTUVBb3dId1lEVlIwakJCZ3dGb0FVeXZsMFZJNXZKVlN1WUZYdTdCNDg2UGJNRUFvd0R3WURWUjBUQVFILwpCQVV3QXdFQi96QU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFNTHhyZ0ZWTXVOUnEyd0F3Y0J0N1NuTlI1Q2Z6CjJNdlhxNUVVbXVhd0lVaTlrYVlqd2RWaURSRUdTams3SlcxN3ZsNTc2SGpEa2RmUndpNEUyOFN5ZFJJblpmNkoKaThIWmNaN2NhSDZEeFIzMzVmZ0hWekxpNU5pVGNlL09qTkJRelEyTUpYVkRkOERCbUc1ZnlhdEppT0pRNGJXRQpBN0ZsUDBSZFAzQ08zR1dFME01aVhPQjJtMXFXa0UyZXlPNFVIdndUcU5RTGRyZEFYZ0RRbGJhbTllNEJHM0dnCmQvNnRoQWtXRGJ0L1FOVCtFSkhEQ3ZoRFJLaDFSdUdIeWcrWSsvbmViVFdXckZXc2t0UnJiT29IQ1ppQ3BYSTEKM2VYRTZudDBZa2d0RHhHMjJLcW5ocEFnOWdVU3MyaGxob3h5dmt6eUYwbXU2TmhQbHdBZ25xNysvUT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + name: policy-btls-grpc-2/default-ca + sni: grpc-backend-2 + weight: 1 + name: envoyextensionpolicy/default/policy-for-http-route/0 + hostname: www.foo.com + isHTTP2: false + name: httproute/default/httproute-1/rule/0/match/0/www_foo_com + pathMatch: + distinct: false + name: "" + prefix: /foo + - backendWeights: + invalid: 0 + valid: 0 + destination: + name: httproute/default/httproute-2/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + extProc: + - authority: grpc-backend.envoy-gateway:8000 + destination: + name: envoyextensionpolicy/default/policy-for-gateway/0/grpc-backend + settings: + - addressType: IP + protocol: GRPC + tls: + caCertificate: + certificate: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURKekNDQWcrZ0F3SUJBZ0lVQWw2VUtJdUttenRlODFjbGx6NVBmZE4ySWxJd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0l6RVFNQTRHQTFVRUF3d0hiWGxqYVdWdWRERVBNQTBHQTFVRUNnd0dhM1ZpWldSaU1CNFhEVEl6TVRBdwpNakExTkRFMU4xb1hEVEkwTVRBd01UQTFOREUxTjFvd0l6RVFNQTRHQTFVRUF3d0hiWGxqYVdWdWRERVBNQTBHCkExVUVDZ3dHYTNWaVpXUmlNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXdTVGMKMXlqOEhXNjJueW5rRmJYbzRWWEt2MmpDMFBNN2RQVmt5ODdGd2VaY1RLTG9XUVZQUUUycDJrTERLNk9Fc3ptTQp5eXIreHhXdHlpdmVyZW1yV3FuS2tOVFloTGZZUGhnUWtjemliN2VVYWxtRmpVYmhXZEx2SGFrYkVnQ29kbjNiCmt6NTdtSW5YMlZwaURPS2c0a3lIZml1WFdwaUJxckN4MEtOTHB4bzNERVFjRmNzUVRlVEh6aDQ3NTJHVjA0UlUKVGkvR0VXeXpJc2w0Umc3dEd0QXdtY0lQZ1VOVWZZMlEzOTBGR3FkSDRhaG4rbXcvNmFGYlczMVc2M2Q5WUpWcQppb3lPVmNhTUlwTTVCL2M3UWM4U3VoQ0kxWUdoVXlnNGNSSExFdzVWdGlraW95RTNYMDRrbmEzalFBajU0WWJSCmJwRWhjMzVhcEtMQjIxSE9VUUlEQVFBQm8xTXdVVEFkQmdOVkhRNEVGZ1FVeXZsMFZJNXZKVlN1WUZYdTdCNDgKNlBiTUVBb3dId1lEVlIwakJCZ3dGb0FVeXZsMFZJNXZKVlN1WUZYdTdCNDg2UGJNRUFvd0R3WURWUjBUQVFILwpCQVV3QXdFQi96QU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFNTHhyZ0ZWTXVOUnEyd0F3Y0J0N1NuTlI1Q2Z6CjJNdlhxNUVVbXVhd0lVaTlrYVlqd2RWaURSRUdTams3SlcxN3ZsNTc2SGpEa2RmUndpNEUyOFN5ZFJJblpmNkoKaThIWmNaN2NhSDZEeFIzMzVmZ0hWekxpNU5pVGNlL09qTkJRelEyTUpYVkRkOERCbUc1ZnlhdEppT0pRNGJXRQpBN0ZsUDBSZFAzQ08zR1dFME01aVhPQjJtMXFXa0UyZXlPNFVIdndUcU5RTGRyZEFYZ0RRbGJhbTllNEJHM0dnCmQvNnRoQWtXRGJ0L1FOVCtFSkhEQ3ZoRFJLaDFSdUdIeWcrWSsvbmViVFdXckZXc2t0UnJiT29IQ1ppQ3BYSTEKM2VYRTZudDBZa2d0RHhHMjJLcW5ocEFnOWdVU3MyaGxob3h5dmt6eUYwbXU2TmhQbHdBZ25xNysvUT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + name: policy-btls-grpc/default-ca + sni: grpc-backend + weight: 1 + name: envoyextensionpolicy/default/policy-for-gateway/0 + hostname: www.bar.com + isHTTP2: false + name: httproute/default/httproute-2/rule/0/match/0/www_bar_com + pathMatch: + distinct: false + name: "" + prefix: /bar diff --git a/internal/gatewayapi/translator.go b/internal/gatewayapi/translator.go index 12a5cf81743..9dc93532479 100644 --- a/internal/gatewayapi/translator.go +++ b/internal/gatewayapi/translator.go @@ -114,6 +114,7 @@ func newTranslateResult(gateways []*GatewayContext, backendTrafficPolicies []*egv1a1.BackendTrafficPolicy, securityPolicies []*egv1a1.SecurityPolicy, backendTLSPolicies []*egv1a2.BackendTLSPolicy, + envoyExtensionPolicies []*egv1a1.EnvoyExtensionPolicy, xdsIR XdsIRMap, infraIR InfraIRMap) *TranslateResult { translateResult := &TranslateResult{ XdsIR: xdsIR, @@ -143,6 +144,7 @@ func newTranslateResult(gateways []*GatewayContext, translateResult.BackendTrafficPolicies = append(translateResult.BackendTrafficPolicies, backendTrafficPolicies...) translateResult.SecurityPolicies = append(translateResult.SecurityPolicies, securityPolicies...) translateResult.BackendTLSPolicies = append(translateResult.BackendTLSPolicies, backendTLSPolicies...) + translateResult.EnvoyExtensionPolicies = append(translateResult.EnvoyExtensionPolicies, envoyExtensionPolicies...) return translateResult } @@ -207,12 +209,17 @@ func (t *Translator) Translate(resources *Resources) *TranslateResult { securityPolicies := t.ProcessSecurityPolicies( resources.SecurityPolicies, gateways, routes, resources, xdsIR) + // Process EnvoyExtensionPolicies + envoyExtensionPolicies := t.ProcessEnvoyExtensionPolicies( + resources.EnvoyExtensionPolicies, gateways, routes, resources, xdsIR) + // Sort xdsIR based on the Gateway API spec sortXdsIRMap(xdsIR) return newTranslateResult(gateways, httpRoutes, grpcRoutes, tlsRoutes, tcpRoutes, udpRoutes, clientTrafficPolicies, backendTrafficPolicies, - securityPolicies, resources.BackendTLSPolicies, xdsIR, infraIR) + securityPolicies, resources.BackendTLSPolicies, envoyExtensionPolicies, + xdsIR, infraIR) } diff --git a/internal/gatewayapi/zz_generated.deepcopy.go b/internal/gatewayapi/zz_generated.deepcopy.go index 30c6c2938b1..336498b2214 100644 --- a/internal/gatewayapi/zz_generated.deepcopy.go +++ b/internal/gatewayapi/zz_generated.deepcopy.go @@ -238,6 +238,17 @@ func (in *Resources) DeepCopyInto(out *Resources) { } } } + if in.EnvoyExtensionPolicies != nil { + in, out := &in.EnvoyExtensionPolicies, &out.EnvoyExtensionPolicies + *out = make([]*apiv1alpha1.EnvoyExtensionPolicy, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(apiv1alpha1.EnvoyExtensionPolicy) + (*in).DeepCopyInto(*out) + } + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Resources. diff --git a/internal/ir/xds.go b/internal/ir/xds.go index 33d241d2247..fbcd82be4da 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -493,6 +493,8 @@ type HTTPRoute struct { TCPKeepalive *TCPKeepalive `json:"tcpKeepalive,omitempty" yaml:"tcpKeepalive,omitempty"` // Retry settings Retry *Retry `json:"retry,omitempty" yaml:"retry,omitempty"` + // External Processing extensions + ExtProcs []ExtProc `json:"extProc,omitempty" yaml:"extProc,omitempty"` } // UnstructuredRef holds unstructured data for an arbitrary k8s resource introduced by an extension @@ -1835,3 +1837,17 @@ type ConnectionLimit struct { // once the limit value is reached. CloseDelay *metav1.Duration `json:"closeDelay,omitempty" yaml:"closeDelay,omitempty"` } + +// ExtProc holds the information associated with the ExtProc extensions. +// +k8s:deepcopy-gen=true +type ExtProc struct { + // Name is a unique name for an ExtProc configuration. + // The xds translator only generates one ExtProc filter for each unique name. + Name string `json:"name" yaml:"name"` + + // Destination defines the destination for the gRPC External Processing service. + Destination RouteDestination `json:"destination"` + + // Authority is the hostname:port of the HTTP External Processing service. + Authority string `json:"authority"` +} diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go index e957d8b2cc4..bb536077dd5 100644 --- a/internal/ir/zz_generated.deepcopy.go +++ b/internal/ir/zz_generated.deepcopy.go @@ -523,6 +523,22 @@ func (in *ExtAuth) DeepCopy() *ExtAuth { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExtProc) DeepCopyInto(out *ExtProc) { + *out = *in + in.Destination.DeepCopyInto(&out.Destination) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtProc. +func (in *ExtProc) DeepCopy() *ExtProc { + if in == nil { + return nil + } + out := new(ExtProc) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FaultInjection) DeepCopyInto(out *FaultInjection) { *out = *in @@ -1021,6 +1037,13 @@ func (in *HTTPRoute) DeepCopyInto(out *HTTPRoute) { *out = new(Retry) (*in).DeepCopyInto(*out) } + if in.ExtProcs != nil { + in, out := &in.ExtProcs, &out.ExtProcs + *out = make([]ExtProc, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRoute. diff --git a/internal/message/types.go b/internal/message/types.go index 2ce936442a6..9db1b824f81 100644 --- a/internal/message/types.go +++ b/internal/message/types.go @@ -94,6 +94,7 @@ type PolicyStatuses struct { EnvoyPatchPolicyStatuses watchable.Map[types.NamespacedName, *gwapiv1a2.PolicyStatus] SecurityPolicyStatuses watchable.Map[types.NamespacedName, *gwapiv1a2.PolicyStatus] BackendTLSPolicyStatuses watchable.Map[types.NamespacedName, *gwapiv1a2.PolicyStatus] + EnvoyExtensionPolicyStatuses watchable.Map[types.NamespacedName, *gwapiv1a2.PolicyStatus] } func (p *PolicyStatuses) Close() { @@ -101,6 +102,7 @@ func (p *PolicyStatuses) Close() { p.SecurityPolicyStatuses.Close() p.EnvoyPatchPolicyStatuses.Close() p.BackendTLSPolicyStatuses.Close() + p.EnvoyExtensionPolicyStatuses.Close() } // XdsIR message diff --git a/internal/provider/kubernetes/controller.go b/internal/provider/kubernetes/controller.go index c3ea9daab3d..83a31795fd1 100644 --- a/internal/provider/kubernetes/controller.go +++ b/internal/provider/kubernetes/controller.go @@ -199,6 +199,11 @@ func (r *gatewayAPIReconciler) Reconcile(ctx context.Context, _ reconcile.Reques return reconcile.Result{}, err } + // Add all EnvoyExtensionPolicies and their referenced resources to the resourceTree + if err = r.processEnvoyExtensionPolicies(ctx, gwcResource, resourceMappings); err != nil { + return reconcile.Result{}, err + } + // Add the referenced services, ServiceImports, and EndpointSlices in // the collected BackendRefs to the resourceTree. // BackendRefs are referred by various Route objects and the ExtAuth in SecurityPolicies. @@ -1234,6 +1239,24 @@ func (r *gatewayAPIReconciler) watchResources(ctx context.Context, mgr manager.M return err } + // Watch EnvoyExtensionPolicy + eepPredicates := []predicate.Predicate{predicate.GenerationChangedPredicate{}} + if r.namespaceLabel != nil { + eepPredicates = append(eepPredicates, predicate.NewPredicateFuncs(r.hasMatchingNamespaceLabels)) + } + + // Watch EnvoyExtensionPolicy CRUDs + if err := c.Watch( + source.Kind(mgr.GetCache(), &egv1a1.EnvoyExtensionPolicy{}), + handler.EnqueueRequestsFromMapFunc(r.enqueueClass), + eepPredicates..., + ); err != nil { + return err + } + if err := addEnvoyExtensionPolicyIndexers(ctx, mgr); err != nil { + return err + } + r.log.Info("Watching gatewayAPI related objects") // Watch any additional GVKs from the registered extension. @@ -1400,3 +1423,78 @@ func (r *gatewayAPIReconciler) processBackendTLSPolicyConfigMapRefs(ctx context. } } } + +// processEnvoyExtensionPolicies adds EnvoyExtensionPolicies and their referenced resources to the resourceTree +func (r *gatewayAPIReconciler) processEnvoyExtensionPolicies( + ctx context.Context, resourceTree *gatewayapi.Resources, resourceMap *resourceMappings) error { + envoyExtensionPolicies := egv1a1.EnvoyExtensionPolicyList{} + if err := r.client.List(ctx, &envoyExtensionPolicies); err != nil { + return fmt.Errorf("error listing EnvoyExtensionPolicies: %w", err) + } + + for _, policy := range envoyExtensionPolicies.Items { + policy := policy + // Discard Status to reduce memory consumption in watchable + // It will be recomputed by the gateway-api layer + policy.Status = gwapiv1a2.PolicyStatus{} + resourceTree.EnvoyExtensionPolicies = append(resourceTree.EnvoyExtensionPolicies, &policy) + } + + // Add the referenced Resources in EnvoyExtensionPolicies to the resourceTree + r.processEnvoyExtensionPolicyObjectRefs(ctx, resourceTree, resourceMap) + + return nil +} + +// processEnvoyExtensionPolicyObjectRefs adds the referenced resources in EnvoyExtensionPolicies +// to the resourceTree +// - BackendRefs for ExtProcs +func (r *gatewayAPIReconciler) processEnvoyExtensionPolicyObjectRefs( + ctx context.Context, resourceTree *gatewayapi.Resources, resourceMap *resourceMappings) { + // we don't return errors from this method, because we want to continue reconciling + // the rest of the EnvoyExtensionPolicies despite that one reference is invalid. This + // allows Envoy Gateway to continue serving traffic even if some EnvoyExtensionPolicies + // are invalid. + // + // This EnvoyExtensionPolicy will be marked as invalid in its status when translating + // to IR because the referenced service can't be found. + for _, policy := range resourceTree.EnvoyExtensionPolicies { + // Add the referenced BackendRefs and ReferenceGrants in ExtAuth to Maps for later processing + for _, ep := range policy.Spec.ExtProc { + backendRef := ep.BackendRef.BackendObjectReference + + backendNamespace := gatewayapi.NamespaceDerefOr(backendRef.Namespace, policy.Namespace) + resourceMap.allAssociatedBackendRefs[gwapiv1.BackendObjectReference{ + Group: backendRef.Group, + Kind: backendRef.Kind, + Namespace: gatewayapi.NamespacePtrV1Alpha2(backendNamespace), + Name: backendRef.Name, + }] = struct{}{} + + if backendNamespace != policy.Namespace { + from := ObjectKindNamespacedName{ + kind: gatewayapi.KindHTTPRoute, + namespace: policy.Namespace, + name: policy.Name, + } + to := ObjectKindNamespacedName{ + kind: gatewayapi.KindDerefOr(backendRef.Kind, gatewayapi.KindService), + namespace: backendNamespace, + name: string(backendRef.Name), + } + refGrant, err := r.findReferenceGrant(ctx, from, to) + switch { + case err != nil: + r.log.Error(err, "failed to find ReferenceGrant") + case refGrant == nil: + r.log.Info("no matching ReferenceGrants found", "from", from.kind, + "from namespace", from.namespace, "target", to.kind, "target namespace", to.namespace) + default: + resourceTree.ReferenceGrants = append(resourceTree.ReferenceGrants, refGrant) + r.log.Info("added ReferenceGrant to resource map", "namespace", refGrant.Namespace, + "name", refGrant.Name) + } + } + } + } +} diff --git a/internal/provider/kubernetes/indexers.go b/internal/provider/kubernetes/indexers.go index 3e638a17aae..ac1132b31d5 100644 --- a/internal/provider/kubernetes/indexers.go +++ b/internal/provider/kubernetes/indexers.go @@ -20,24 +20,25 @@ import ( ) const ( - classGatewayIndex = "classGatewayIndex" - gatewayTLSRouteIndex = "gatewayTLSRouteIndex" - gatewayHTTPRouteIndex = "gatewayHTTPRouteIndex" - gatewayGRPCRouteIndex = "gatewayGRPCRouteIndex" - gatewayTCPRouteIndex = "gatewayTCPRouteIndex" - gatewayUDPRouteIndex = "gatewayUDPRouteIndex" - secretGatewayIndex = "secretGatewayIndex" - targetRefGrantRouteIndex = "targetRefGrantRouteIndex" - backendHTTPRouteIndex = "backendHTTPRouteIndex" - backendGRPCRouteIndex = "backendGRPCRouteIndex" - backendTLSRouteIndex = "backendTLSRouteIndex" - backendTCPRouteIndex = "backendTCPRouteIndex" - backendUDPRouteIndex = "backendUDPRouteIndex" - secretSecurityPolicyIndex = "secretSecurityPolicyIndex" - backendSecurityPolicyIndex = "backendSecurityPolicyIndex" - configMapCtpIndex = "configMapCtpIndex" - secretCtpIndex = "secretCtpIndex" - configMapBtlsIndex = "configMapBtlsIndex" + classGatewayIndex = "classGatewayIndex" + gatewayTLSRouteIndex = "gatewayTLSRouteIndex" + gatewayHTTPRouteIndex = "gatewayHTTPRouteIndex" + gatewayGRPCRouteIndex = "gatewayGRPCRouteIndex" + gatewayTCPRouteIndex = "gatewayTCPRouteIndex" + gatewayUDPRouteIndex = "gatewayUDPRouteIndex" + secretGatewayIndex = "secretGatewayIndex" + targetRefGrantRouteIndex = "targetRefGrantRouteIndex" + backendHTTPRouteIndex = "backendHTTPRouteIndex" + backendGRPCRouteIndex = "backendGRPCRouteIndex" + backendTLSRouteIndex = "backendTLSRouteIndex" + backendTCPRouteIndex = "backendTCPRouteIndex" + backendUDPRouteIndex = "backendUDPRouteIndex" + secretSecurityPolicyIndex = "secretSecurityPolicyIndex" + backendSecurityPolicyIndex = "backendSecurityPolicyIndex" + configMapCtpIndex = "configMapCtpIndex" + secretCtpIndex = "secretCtpIndex" + configMapBtlsIndex = "configMapBtlsIndex" + backendEnvoyExtensionPolicyIndex = "backendSecurityPolicyIndex" ) func addReferenceGrantIndexers(ctx context.Context, mgr manager.Manager) error { @@ -511,3 +512,36 @@ func configMapBtlsIndexFunc(rawObj client.Object) []string { } return configMapReferences } + +// addEnvoyExtensionPolicyIndexers adds indexing on EnvoyExtensionPolicy. +// - For Service objects that are referenced in EnvoyExtensionPolicy objects via +// `.spec.extProc.[*].service.backendObjectReference`. This helps in querying for +// EnvoyExtensionPolicy that are affected by a particular Service CRUD. +func addEnvoyExtensionPolicyIndexers(ctx context.Context, mgr manager.Manager) error { + var err error + + if err = mgr.GetFieldIndexer().IndexField( + ctx, &v1alpha1.EnvoyExtensionPolicy{}, backendEnvoyExtensionPolicyIndex, + backendEnvoyExtensionPolicyIndexFunc); err != nil { + return err + } + + return nil +} + +func backendEnvoyExtensionPolicyIndexFunc(rawObj client.Object) []string { + envoyExtensionPolicy := rawObj.(*v1alpha1.EnvoyExtensionPolicy) + + var ret []string + + for _, ep := range envoyExtensionPolicy.Spec.ExtProc { + backendRef := ep.BackendRef.BackendObjectReference + ret = append(ret, + types.NamespacedName{ + Namespace: gatewayapi.NamespaceDerefOr(backendRef.Namespace, envoyExtensionPolicy.Namespace), + Name: string(backendRef.Name), + }.String()) + } + + return ret +} diff --git a/internal/provider/kubernetes/predicates.go b/internal/provider/kubernetes/predicates.go index 3585a2913ae..f25c0092326 100644 --- a/internal/provider/kubernetes/predicates.go +++ b/internal/provider/kubernetes/predicates.go @@ -251,7 +251,11 @@ func (r *gatewayAPIReconciler) validateServiceForReconcile(obj client.Object) bo return true } - return r.isSecurityPolicyReferencingBackend(&nsName) + if r.isSecurityPolicyReferencingBackend(&nsName) { + return true + } + + return r.isEnvoyExtensionPolicyReferencingBackend(&nsName) } func (r *gatewayAPIReconciler) isSecurityPolicyReferencingBackend(nsName *types.NamespacedName) bool { @@ -363,7 +367,11 @@ func (r *gatewayAPIReconciler) validateEndpointSliceForReconcile(obj client.Obje return true } - return r.isSecurityPolicyReferencingBackend(&nsName) + if r.isSecurityPolicyReferencingBackend(&nsName) { + return true + } + + return r.isEnvoyExtensionPolicyReferencingBackend(&nsName) } // validateDeploymentForReconcile tries finding the owning Gateway of the Deployment @@ -533,3 +541,15 @@ func (r *gatewayAPIReconciler) validateConfigMapForReconcile(obj client.Object) return true } + +func (r *gatewayAPIReconciler) isEnvoyExtensionPolicyReferencingBackend(nsName *types.NamespacedName) bool { + spList := &egv1a1.EnvoyExtensionPolicyList{} + if err := r.client.List(context.Background(), spList, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(backendEnvoyExtensionPolicyIndex, nsName.String()), + }); err != nil { + r.log.Error(err, "unable to find associated EnvoyExtensionPolicies") + return false + } + + return len(spList.Items) > 0 +} diff --git a/internal/provider/kubernetes/predicates_test.go b/internal/provider/kubernetes/predicates_test.go index f923eef8a26..e9f24d6e51f 100644 --- a/internal/provider/kubernetes/predicates_test.go +++ b/internal/provider/kubernetes/predicates_test.go @@ -514,6 +514,64 @@ func TestValidateServiceForReconcile(t *testing.T) { service: test.GetService(types.NamespacedName{Name: "ext-auth-http-service"}, nil, nil), expect: true, }, + { + name: "service referenced by EnvoyExtensionPolicy ExtPrc GRPC service", + configs: []client.Object{ + &v1alpha1.EnvoyExtensionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ext-proc", + }, + Spec: v1alpha1.EnvoyExtensionPolicySpec{ + TargetRef: gwapiv1a2.PolicyTargetReferenceWithSectionName{ + PolicyTargetReference: gwapiv1a2.PolicyTargetReference{ + Kind: "Gateway", + Name: "scheduled-status-test", + }, + }, + ExtProc: []v1alpha1.ExtProc{ + { + BackendRef: v1alpha1.ExtProcBackendRef{ + BackendObjectReference: gwapiv1.BackendObjectReference{ + Name: "ext-proc-service", + }, + }, + }, + }, + }, + }, + }, + service: test.GetService(types.NamespacedName{Name: "ext-proc-service"}, nil, nil), + expect: true, + }, + { + name: "service referenced by EnvoyExtensionPolicy ExtPrc GRPC service unrelated", + configs: []client.Object{ + &v1alpha1.EnvoyExtensionPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ext-proc", + }, + Spec: v1alpha1.EnvoyExtensionPolicySpec{ + TargetRef: gwapiv1a2.PolicyTargetReferenceWithSectionName{ + PolicyTargetReference: gwapiv1a2.PolicyTargetReference{ + Kind: "Gateway", + Name: "scheduled-status-test", + }, + }, + ExtProc: []v1alpha1.ExtProc{ + { + BackendRef: v1alpha1.ExtProcBackendRef{ + BackendObjectReference: gwapiv1.BackendObjectReference{ + Name: "ext-proc-service", + }, + }, + }, + }, + }, + }, + }, + service: test.GetService(types.NamespacedName{Name: "ext-proc-service-unrelated"}, nil, nil), + expect: false, + }, { name: "update status of all gateways under gatewayclass when MergeGateways enabled", configs: []client.Object{ @@ -571,6 +629,7 @@ func TestValidateServiceForReconcile(t *testing.T) { WithIndex(&gwapiv1a2.TCPRoute{}, backendTCPRouteIndex, backendTCPRouteIndexFunc). WithIndex(&gwapiv1a2.UDPRoute{}, backendUDPRouteIndex, backendUDPRouteIndexFunc). WithIndex(&v1alpha1.SecurityPolicy{}, backendSecurityPolicyIndex, backendSecurityPolicyIndexFunc). + WithIndex(&v1alpha1.EnvoyExtensionPolicy{}, backendEnvoyExtensionPolicyIndex, backendEnvoyExtensionPolicyIndexFunc). Build() t.Run(tc.name, func(t *testing.T) { res := r.validateServiceForReconcile(tc.service) diff --git a/internal/provider/kubernetes/status.go b/internal/provider/kubernetes/status.go index 2a90165097b..5aa8af4fa57 100644 --- a/internal/provider/kubernetes/status.go +++ b/internal/provider/kubernetes/status.go @@ -364,6 +364,38 @@ func (r *gatewayAPIReconciler) subscribeAndUpdateStatus(ctx context.Context) { ) r.log.Info("backendTlsPolicy status subscriber shutting down") }() + + // EnvoyExtensionPolicy object status updater + go func() { + message.HandleSubscription( + message.Metadata{Runner: string(v1alpha1.LogComponentProviderRunner), Message: "envoyextensionpolicy-status"}, + r.resources.EnvoyExtensionPolicyStatuses.Subscribe(ctx), + func(update message.Update[types.NamespacedName, *gwapiv1a2.PolicyStatus], errChan chan error) { + // skip delete updates. + if update.Delete { + return + } + key := update.Key + val := update.Value + r.statusUpdater.Send(status.Update{ + NamespacedName: key, + Resource: new(v1alpha1.EnvoyExtensionPolicy), + Mutator: status.MutatorFunc(func(obj client.Object) client.Object { + t, ok := obj.(*v1alpha1.EnvoyExtensionPolicy) + if !ok { + err := fmt.Errorf("unsupported object type %T", obj) + errChan <- err + panic(err) + } + tCopy := t.DeepCopy() + tCopy.Status = *val + return tCopy + }), + }) + }, + ) + r.log.Info("envoyExtensionPolicy status subscriber shutting down") + }() } func (r *gatewayAPIReconciler) updateStatusForGateway(ctx context.Context, gtw *gwapiv1.Gateway) { diff --git a/internal/status/status.go b/internal/status/status.go index 4f945e2ca9c..ce27858a325 100644 --- a/internal/status/status.go +++ b/internal/status/status.go @@ -246,7 +246,12 @@ func isStatusEqual(objA, objB interface{}) bool { return true } } + case *egv1a1.EnvoyExtensionPolicy: + if b, ok := objB.(*egv1a1.EnvoyExtensionPolicy); ok { + if cmp.Equal(a.Status, b.Status, opts) { + return true + } + } } - return false } diff --git a/internal/xds/translator/extauth.go b/internal/xds/translator/extauth.go index 466ec502355..eb79779f8e9 100644 --- a/internal/xds/translator/extauth.go +++ b/internal/xds/translator/extauth.go @@ -238,40 +238,6 @@ func (*extAuth) patchResources(tCtx *types.ResourceVersionTable, return errs } -func createExtServiceXDSCluster(rd *ir.RouteDestination, tCtx *types.ResourceVersionTable) error { - var ( - endpointType EndpointType - tSocket *corev3.TransportSocket - err error - ) - - // Get the address type from the first setting. - // This is safe because no mixed address types in the settings. - addrTypeState := rd.Settings[0].AddressType - if addrTypeState != nil && *addrTypeState == ir.FQDN { - endpointType = EndpointTypeDNS - } else { - endpointType = EndpointTypeStatic - } - - if rd.Settings[0].TLS != nil { - tSocket, err = processTLSSocket(rd.Settings[0].TLS, tCtx) - if err != nil { - return err - } - } - - if err = addXdsCluster(tCtx, &xdsClusterArgs{ - name: rd.Name, - settings: rd.Settings, - tSocket: tSocket, - endpointType: endpointType, - }); err != nil && !errors.Is(err, ErrXdsClusterExists) { - return err - } - return nil -} - // patchRoute patches the provided route with the extAuth config if applicable. // Note: this method enables the corresponding extAuth filter for the provided route. func (*extAuth) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute) error { diff --git a/internal/xds/translator/extproc.go b/internal/xds/translator/extproc.go new file mode 100644 index 00000000000..19dd8480753 --- /dev/null +++ b/internal/xds/translator/extproc.go @@ -0,0 +1,175 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +package translator + +import ( + "errors" + + corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + routev3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" + extprocv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/ext_proc/v3" + hcmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" + "github.com/golang/protobuf/ptypes/duration" + "google.golang.org/protobuf/types/known/anypb" + + "github.com/envoyproxy/gateway/internal/ir" + "github.com/envoyproxy/gateway/internal/xds/types" +) + +const ( + extProcFilter = "envoy.filters.http.ext_proc" +) + +func init() { + registerHTTPFilter(&extProc{}) +} + +type extProc struct { +} + +var _ httpFilter = &extProc{} + +// patchHCM builds and appends the ext_authz Filters to the HTTP Connection Manager +// if applicable, and it does not already exist. +// Note: this method creates an ext_authz filter for each route that contains an ExtAuthz config. +// The filter is disabled by default. It is enabled on the route level. +func (*extProc) patchHCM(mgr *hcmv3.HttpConnectionManager, irListener *ir.HTTPListener) error { + var errs error + + if mgr == nil { + return errors.New("hcm is nil") + } + + if irListener == nil { + return errors.New("ir listener is nil") + } + + for _, route := range irListener.Routes { + if !routeContainsExtProc(route) { + continue + } + + for _, ep := range route.ExtProcs { + if hcmContainsFilter(mgr, extProcFilterName(ep)) { + continue + } + + filter, err := buildHCMExtProcFilter(ep) + if err != nil { + errs = errors.Join(errs, err) + continue + } + + mgr.HttpFilters = append(mgr.HttpFilters, filter) + } + } + + return errs +} + +// buildHCMExtProcFilter returns an ext_authp HTTP filter from the provided IR HTTPRoute. +func buildHCMExtProcFilter(extProc ir.ExtProc) (*hcmv3.HttpFilter, error) { + extAuthProto := extProcConfig(extProc) + if err := extAuthProto.ValidateAll(); err != nil { + return nil, err + } + + extAuthAny, err := anypb.New(extAuthProto) + if err != nil { + return nil, err + } + + // All extproc filters for all Routes are aggregated on HCM and disabled by default + // Per-route config is used to enable the relevant filters on appropriate routes + return &hcmv3.HttpFilter{ + Name: extProcFilterName(extProc), + Disabled: true, + ConfigType: &hcmv3.HttpFilter_TypedConfig{ + TypedConfig: extAuthAny, + }, + }, nil +} + +func extProcFilterName(extProc ir.ExtProc) string { + return perRouteFilterName(extProcFilter, extProc.Name) +} + +func extProcConfig(extProc ir.ExtProc) *extprocv3.ExternalProcessor { + config := &extprocv3.ExternalProcessor{ + GrpcService: &corev3.GrpcService{ + TargetSpecifier: &corev3.GrpcService_EnvoyGrpc_{ + EnvoyGrpc: grpcExtProcService(extProc), + }, + Timeout: &duration.Duration{ + Seconds: defaultExtServiceRequestTimeout, + }, + }, + } + + return config +} + +func grpcExtProcService(extProc ir.ExtProc) *corev3.GrpcService_EnvoyGrpc { + return &corev3.GrpcService_EnvoyGrpc{ + ClusterName: extProc.Destination.Name, + Authority: extProc.Authority, + } +} + +// routeContainsExtProc returns true if ExtProcs exists for the provided route. +func routeContainsExtProc(irRoute *ir.HTTPRoute) bool { + if irRoute == nil { + return false + } + + return len(irRoute.ExtProcs) > 0 +} + +// patchResources patches the cluster resources for the external auth services. +func (*extProc) patchResources(tCtx *types.ResourceVersionTable, + routes []*ir.HTTPRoute) error { + if tCtx == nil || tCtx.XdsResources == nil { + return errors.New("xds resource table is nil") + } + + var errs error + for _, route := range routes { + if !routeContainsExtProc(route) { + continue + } + + for i := range route.ExtProcs { + ep := route.ExtProcs[i] + if err := createExtServiceXDSCluster( + &ep.Destination, tCtx); err != nil && !errors.Is( + err, ErrXdsClusterExists) { + errs = errors.Join(errs, err) + } + } + + } + + return errs +} + +// patchRoute patches the provided route with the extProc config if applicable. +// Note: this method enables the corresponding extProc filter for the provided route. +func (*extProc) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute) error { + if route == nil { + return errors.New("xds route is nil") + } + if irRoute == nil { + return errors.New("ir route is nil") + } + + for _, ep := range irRoute.ExtProcs { + filterName := extProcFilterName(ep) + if err := enableFilterOnRoute(route, filterName); err != nil { + return err + } + } + return nil +} diff --git a/internal/xds/translator/httpfilters.go b/internal/xds/translator/httpfilters.go index 6515ec9770d..ca4e6bb64c3 100644 --- a/internal/xds/translator/httpfilters.go +++ b/internal/xds/translator/httpfilters.go @@ -103,10 +103,12 @@ func newOrderedHTTPFilter(filter *hcmv3.HttpFilter) *OrderedHTTPFilter { order = 5 case filter.Name == jwtAuthn: order = 6 - case filter.Name == localRateLimitFilter: + case filter.Name == extProcFilter: order = 7 - case filter.Name == wellknown.HTTPRateLimit: + case filter.Name == localRateLimitFilter: order = 8 + case filter.Name == wellknown.HTTPRateLimit: + order = 9 case filter.Name == wellknown.Router: order = 100 } diff --git a/internal/xds/translator/testdata/in/xds-ir/ext-proc.yaml b/internal/xds/translator/testdata/in/xds-ir/ext-proc.yaml new file mode 100644 index 00000000000..170f1a3f2ea --- /dev/null +++ b/internal/xds/translator/testdata/in/xds-ir/ext-proc.yaml @@ -0,0 +1,81 @@ +http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - backendWeights: + invalid: 0 + valid: 0 + destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + envoyExtensionFeatures: + extProc: + - name: envoyextensionpolicy/default/policy-for-route-2/0 + authority: grpc-backend-4.default:4000 + destination: + name: envoyextensionpolicy/default/policy-for-route-2/0/grpc-backend-4 + settings: + - protocol: GRPC + weight: 1 + - name: envoyextensionpolicy/default/policy-for-route-1/0 + authority: grpc-backend-2.default:8000 + destination: + name: envoyextensionpolicy/default/policy-for-route-1/0/grpc-backend-2 + settings: + - protocol: GRPC + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: /foo + - backendWeights: + invalid: 0 + valid: 0 + destination: + name: httproute/default/httproute-2/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + envoyExtensionFeatures: + extProc: + - name: envoyextensionpolicy/envoy-gateway/policy-for-gateway-2/0 + authority: grpc-backend-3.envoy-gateway:3000 + destination: + name: envoyextensionpolicy/envoy-gateway/policy-for-gateway-2/0/grpc-backend-3 + settings: + - protocol: GRPC + weight: 1 + - name: envoyextensionpolicy/envoy-gateway/policy-for-gateway-1/0 + authority: grpc-backend.envoy-gateway:9000 + destination: + name: envoyextensionpolicy/envoy-gateway/policy-for-gateway-1/0/grpc-backend + settings: + - protocol: GRPC + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + name: httproute/default/httproute-2/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: /bar diff --git a/internal/xds/translator/testdata/out/xds-ir/ext-proc.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/ext-proc.clusters.yaml new file mode 100755 index 00000000000..6a277bb94f6 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/ext-proc.clusters.yaml @@ -0,0 +1,34 @@ +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_ONLY + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: httproute/default/httproute-1/rule/0 + lbPolicy: LEAST_REQUEST + name: httproute/default/httproute-1/rule/0 + outlierDetection: {} + perConnectionBufferLimitBytes: 32768 + type: EDS +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_ONLY + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: httproute/default/httproute-2/rule/0 + lbPolicy: LEAST_REQUEST + name: httproute/default/httproute-2/rule/0 + outlierDetection: {} + perConnectionBufferLimitBytes: 32768 + type: EDS diff --git a/internal/xds/translator/testdata/out/xds-ir/ext-proc.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/ext-proc.endpoints.yaml new file mode 100755 index 00000000000..05442a9a15b --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/ext-proc.endpoints.yaml @@ -0,0 +1,24 @@ +- clusterName: httproute/default/httproute-1/rule/0 + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 7.7.7.7 + portValue: 8080 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: httproute/default/httproute-1/rule/0/backend/0 +- clusterName: httproute/default/httproute-2/rule/0 + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 7.7.7.7 + portValue: 8080 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: httproute/default/httproute-2/rule/0/backend/0 diff --git a/internal/xds/translator/testdata/out/xds-ir/ext-proc.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/ext-proc.listeners.yaml new file mode 100755 index 00000000000..a593a50af36 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/ext-proc.listeners.yaml @@ -0,0 +1,34 @@ +- address: + socketAddress: + address: 0.0.0.0 + portValue: 10080 + defaultFilterChain: + filters: + - name: envoy.filters.network.http_connection_manager + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + commonHttpProtocolOptions: + headersWithUnderscoresAction: REJECT_REQUEST + http2ProtocolOptions: + initialConnectionWindowSize: 1048576 + initialStreamWindowSize: 65536 + maxConcurrentStreams: 100 + httpFilters: + - name: envoy.filters.http.router + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + suppressEnvoyHeaders: true + mergeSlashes: true + normalizePath: true + pathWithEscapedSlashesAction: UNESCAPE_AND_REDIRECT + rds: + configSource: + ads: {} + resourceApiVersion: V3 + routeConfigName: envoy-gateway/gateway-1/http + serverHeaderTransformation: PASS_THROUGH + statPrefix: http + useRemoteAddress: true + drainType: MODIFY_ONLY + name: envoy-gateway/gateway-1/http + perConnectionBufferLimitBytes: 32768 diff --git a/internal/xds/translator/testdata/out/xds-ir/ext-proc.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/ext-proc.routes.yaml new file mode 100755 index 00000000000..a7002b63f87 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/ext-proc.routes.yaml @@ -0,0 +1,21 @@ +- ignorePortInHostMatching: true + name: envoy-gateway/gateway-1/http + virtualHosts: + - domains: + - gateway.envoyproxy.io + name: envoy-gateway/gateway-1/http/gateway_envoyproxy_io + routes: + - match: + pathSeparatedPrefix: /foo + name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io + route: + cluster: httproute/default/httproute-1/rule/0 + upgradeConfigs: + - upgradeType: websocket + - match: + pathSeparatedPrefix: /bar + name: httproute/default/httproute-2/rule/0/match/0/gateway_envoyproxy_io + route: + cluster: httproute/default/httproute-2/rule/0 + upgradeConfigs: + - upgradeType: websocket diff --git a/internal/xds/translator/translator_test.go b/internal/xds/translator/translator_test.go index 88030fc15c9..20baaba101b 100644 --- a/internal/xds/translator/translator_test.go +++ b/internal/xds/translator/translator_test.go @@ -304,6 +304,9 @@ func TestTranslateXds(t *testing.T) { { name: "listener-connection-limit", }, + { + name: "ext-proc", + }, } for _, tc := range testCases { diff --git a/internal/xds/translator/utils.go b/internal/xds/translator/utils.go index 64f946373b7..e3002d9a8a3 100644 --- a/internal/xds/translator/utils.go +++ b/internal/xds/translator/utils.go @@ -13,8 +13,13 @@ import ( "strconv" "strings" + "github.com/envoyproxy/gateway/internal/ir" + "github.com/envoyproxy/gateway/internal/xds/types" + + corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" routev3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" hcmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" + "google.golang.org/protobuf/types/known/anypb" ) @@ -122,3 +127,37 @@ func hcmContainsFilter(mgr *hcmv3.HttpConnectionManager, filterName string) bool } return false } + +func createExtServiceXDSCluster(rd *ir.RouteDestination, tCtx *types.ResourceVersionTable) error { + var ( + endpointType EndpointType + tSocket *corev3.TransportSocket + err error + ) + + // Get the address type from the first setting. + // This is safe because no mixed address types in the settings. + addrTypeState := rd.Settings[0].AddressType + if addrTypeState != nil && *addrTypeState == ir.FQDN { + endpointType = EndpointTypeDNS + } else { + endpointType = EndpointTypeStatic + } + + if rd.Settings[0].TLS != nil { + tSocket, err = processTLSSocket(rd.Settings[0].TLS, tCtx) + if err != nil { + return err + } + } + + if err = addXdsCluster(tCtx, &xdsClusterArgs{ + name: rd.Name, + settings: rd.Settings, + tSocket: tSocket, + endpointType: endpointType, + }); err != nil && !errors.Is(err, ErrXdsClusterExists) { + return err + } + return nil +} diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index 63ef6b2f381..48eaf709334 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -542,8 +542,8 @@ _Appears in:_ | Field | Type | Required | Description | | --- | --- | --- | --- | | `targetRef` | _[PolicyTargetReferenceWithSectionName](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1alpha2.PolicyTargetReferenceWithSectionName)_ | true | TargetRef is the name of the Gateway resource this policy
is being attached to.
This Policy and the TargetRef MUST be in the same namespace
for this Policy to have effect and be applied to the Gateway.
TargetRef | -| `priority` | _integer_ | false | Priority of the EnvoyExtensionPolicy.
If multiple EnvoyExtensionPolices are applied to the same
TargetRef, extensions will execute in the ascending order of
the priority i.e. int32.min has the highest priority and
int32.max has the lowest priority.
Defaults to 0. | | `wasm` | _[Wasm](#wasm) array_ | false | WASM is a list of Wasm extensions to be loaded by the Gateway.
Order matters, as the extensions will be loaded in the order they are
defined in this list. | +| `extProc` | _[ExtProc](#extproc) array_ | true | ExtProc is an ordered list of external processing filters
that should added to the envoy filter chain | #### EnvoyGateway @@ -1006,6 +1006,40 @@ _Appears in:_ | `failOpen` | _boolean_ | false | FailOpen is a switch used to control the behavior when a response from the External Authorization service cannot be obtained.
If FailOpen is set to true, the system allows the traffic to pass through.
Otherwise, if it is set to false or not set (defaulting to false),
the system blocks the traffic and returns a HTTP 5xx error, reflecting a fail-closed approach.
This setting determines whether to prioritize accessibility over strict security in case of authorization service failure. | +#### ExtProc + + + +ExtProc defines the configuration for External Processing filter. + +_Appears in:_ +- [EnvoyExtensionPolicySpec](#envoyextensionpolicyspec) + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `backendRef` | _[ExtProcBackendRef](#extprocbackendref)_ | true | Service defines the configuration of the external processing service | + + +#### ExtProcBackendRef + + + +ExtProcService defines the gRPC External Processing service using the envoy grpc client +The processing request and response messages are defined in +https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/ext_proc/v3/external_processor.proto + +_Appears in:_ +- [ExtProc](#extproc) + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `group` | _[Group](#group)_ | false | Group is the group of the referent. For example, "gateway.networking.k8s.io".
When unspecified or empty string, core API group is inferred. | +| `kind` | _[Kind](#kind)_ | false | Kind is the Kubernetes resource kind of the referent. For example
"Service".

Defaults to "Service" when not specified.

ExternalName services can refer to CNAME DNS records that may live
outside of the cluster and as such are difficult to reason about in
terms of conformance. They also may not be safe to forward to (see
CVE-2021-25740 for more information). Implementations SHOULD NOT
support ExternalName Services.

Support: Core (Services with a type other than ExternalName)

Support: Implementation-specific (Services with type ExternalName) | +| `name` | _[ObjectName](#objectname)_ | true | Name is the name of the referent. | +| `namespace` | _[Namespace](#namespace)_ | false | Namespace is the namespace of the backend. When unspecified, the local
namespace is inferred.

Note that when a namespace different than the local namespace is specified,
a ReferenceGrant object is required in the referent namespace to allow that
namespace's owner to accept the reference. See the ReferenceGrant
documentation for details.

Support: Core | +| `port` | _[PortNumber](#portnumber)_ | false | Port specifies the destination port number to use for this resource.
Port is required when the referent is a Kubernetes Service. In this
case, the port number is the service port number, not the target port.
For other resources, destination port might be derived from the referent
resource or this field. | + + #### ExtensionAPISettings @@ -1019,7 +1053,6 @@ _Appears in:_ | Field | Type | Required | Description | | --- | --- | --- | --- | | `enableEnvoyPatchPolicy` | _boolean_ | true | EnableEnvoyPatchPolicy enables Envoy Gateway to
reconcile and implement the EnvoyPatchPolicy resources. | -| `enableEnvoyExtensionPolicy` | _boolean_ | true | EnableEnvoyExtensionPolicy enables Envoy Gateway to
reconcile and implement the EnvoyExtensionPolicy resources. | #### ExtensionHooks diff --git a/site/content/en/latest/contributions/design/envoy-extension-policy.md b/site/content/en/latest/contributions/design/envoy-extension-policy.md index fe6d3d895f2..ffd5c2751bb 100644 --- a/site/content/en/latest/contributions/design/envoy-extension-policy.md +++ b/site/content/en/latest/contributions/design/envoy-extension-policy.md @@ -127,7 +127,6 @@ Here is a list of features that can be included in this API * Extensions that only support HTTP extensibility (Ext-Proc, LUA) can only be attached to HTTP/GRPC Routes. * A user-defined extension that is added to the request processing flow can have a significant impact on security, resilience and performance of the proxy. Gateway Operators can restrict access to the extensibility policy using K8s RBAC. -* Extensibility will be disabled by default, and can be enabled using [Envoy Gateway][] API. * Users may need to customize the order of extension and built-in filters. This will be addressed in a separate issue. * Gateway operators may need to include multiple extensions (e.g. WASM modules developed by different teams and distributed separately). This API will support attachment of multiple policies. Extension will execute in an order defined by the priority field. @@ -159,7 +158,6 @@ Here is a list of features that can be included in this API [Policy Attachment]: https://gateway-api.sigs.k8s.io/references/policy-attachment [Gateway API]: https://gateway-api.sigs.k8s.io/ [Gateway API Route Filters]: https://gateway-api.sigs.k8s.io/api-types/httproute/#filters-optional -[Envoy Gateway]: ../../api/extension_types#envoygateway [Envoy Patch Policy]: ../../api/extension_types#envoypatchpolicy [Envoy Extension Manager]: ./extending-envoy-gateway [Alternatives]: #Alternatives diff --git a/test/e2e/testdata/ext-proc-envoyextensionpolicy.yaml b/test/e2e/testdata/ext-proc-envoyextensionpolicy.yaml new file mode 100644 index 00000000000..5119ac22c1f --- /dev/null +++ b/test/e2e/testdata/ext-proc-envoyextensionpolicy.yaml @@ -0,0 +1,70 @@ +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: http-with-ext-proc + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: same-namespace + hostnames: ["www.example.com"] + rules: + - matches: + - path: + type: PathPrefix + value: /processor + backendRefs: + - name: infra-backend-v1 + port: 8080 +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: http-without-ext-proc + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: same-namespace + hostnames: ["www.example.com"] + rules: + - matches: + - path: + type: PathPrefix + value: /no-processor + backendRefs: + - name: infra-backend-v1 + port: 8080 +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: EnvoyExtensionPolicy +metadata: + name: ext-proc-test + namespace: gateway-conformance-infra +spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: http-with-ext-proc + extProc: + - backendRef: + name: grpc-ext-proc + namespace: gateway-conformance-infra + port: 9002 +--- +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: BackendTLSPolicy +metadata: + name: grpc-ext-proc-btls + namespace: gateway-conformance-infra +spec: + targetRef: + group: '' + kind: Service + name: grpc-ext-proc + sectionName: "9002" + tls: + caCertRefs: + - name: grpc-ext-proc-ca + group: '' + kind: ConfigMap + hostname: grpc-ext-proc diff --git a/test/e2e/testdata/ext-proc-service.yaml b/test/e2e/testdata/ext-proc-service.yaml new file mode 100644 index 00000000000..594cf14edef --- /dev/null +++ b/test/e2e/testdata/ext-proc-service.yaml @@ -0,0 +1,391 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: grpc-ext-proc + namespace: gateway-conformance-infra +data: + go.mod: | + module github.com/envoyproxy/gateway + + go 1.21 + + require ( + github.com/envoyproxy/go-control-plane v0.12.0 + google.golang.org/grpc v1.62.1 + ) + + require ( + github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa // indirect + github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect + github.com/golang/protobuf v1.5.3 // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect + google.golang.org/protobuf v1.32.0 // indirect + ) + go.sum: | + 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/envoyproxy/go-control-plane v0.12.0 h1:4X+VP1GHd1Mhj6IB5mMeGbLCleqxjletLK6K0rbxyZI= + github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0= + 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/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= + github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= + github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= + github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= + github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= + github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= + golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= + golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= + golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= + golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= + golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= + golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= + golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= + google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM= + google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= + google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= + google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= + google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= + google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= + google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= + google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= + main.go: | + package main + + import ( + "context" + "crypto/tls" + "crypto/x509" + "flag" + "fmt" + "google.golang.org/grpc/credentials" + "io" + "log" + "net" + "net/http" + "os" + "strings" + + envoy_api_v3_core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + envoy_service_proc_v3 "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + ) + + type extProcServer struct{} + + var ( + port int + certPath string + ) + + func main() { + flag.IntVar(&port, "port", 9002, "gRPC port") + flag.StringVar(&certPath, "certPath", "", "path to extProcServer certificate and private key") + flag.Parse() + + lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) + if err != nil { + log.Fatalf("failed to listen: %v", err) + } + + creds, err := loadTLSCredentials(certPath) + if err != nil { + log.Fatalf("Failed to load TLS credentials: %v", err) + } + gs := grpc.NewServer(grpc.Creds(creds)) + + envoy_service_proc_v3.RegisterExternalProcessorServer(gs, &extProcServer{}) + + + go func() { + err = gs.Serve(lis) + if err != nil { + log.Fatalf("failed to serve: %v", err) + } + }() + + http.HandleFunc("/healthz", healthCheckHandler) + err = http.ListenAndServe(":8080", nil) + if err != nil { + log.Fatalf("failed to serve: %v", err) + } + } + + // used by k8s readiness probes + // makes a processing request to check if the processor service is healthy + func healthCheckHandler(w http.ResponseWriter, r *http.Request) { + certPool, err := loadCA(certPath) + if err != nil { + log.Fatalf("Could not load CA certificate: %v", err) + } + + // Create TLS configuration + tlsConfig := &tls.Config{ + RootCAs: certPool, + } + + // Create gRPC dial options + opts := []grpc.DialOption{ + grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)), + } + + conn, err := grpc.Dial("localhost:9002", opts...) + if err != nil { + log.Fatalf("Could not connect: %v", err) + } + client := envoy_service_proc_v3.NewExternalProcessorClient(conn) + + processor, err := client.Process(context.Background()) + if err != nil { + log.Fatalf("Could not check: %v", err) + } + + err = processor.Send(&envoy_service_proc_v3.ProcessingRequest{ + Request: &envoy_service_proc_v3.ProcessingRequest_RequestHeaders{ + RequestHeaders: &envoy_service_proc_v3.HttpHeaders{}, + }, + }) + if err != nil { + log.Fatalf("Could not check: %v", err) + } + + response, err := processor.Recv() + if err != nil { + log.Fatalf("Could not check: %v", err) + } + + if response != nil && response.GetRequestHeaders().Response.Status == envoy_service_proc_v3.CommonResponse_CONTINUE { + w.WriteHeader(http.StatusOK) + } else { + w.WriteHeader(http.StatusServiceUnavailable) + } + } + + func loadTLSCredentials(certPath string) (credentials.TransportCredentials, error) { + // Load extProcServer's certificate and private key + crt := "server.crt" + key := "server.key" + + if certPath != "" { + if !strings.HasSuffix(certPath, "/") { + certPath = fmt.Sprintf("%s/", certPath) + } + crt = fmt.Sprintf("%s%s", certPath, crt) + key = fmt.Sprintf("%s%s", certPath, key) + } + certificate, err := tls.LoadX509KeyPair(crt, key) + if err != nil { + return nil, fmt.Errorf("could not load extProcServer key pair: %s", err) + } + + // Create a new credentials object + creds := credentials.NewTLS(&tls.Config{Certificates: []tls.Certificate{certificate}}) + + return creds, nil + } + + func loadCA(caPath string) (*x509.CertPool, error) { + ca := x509.NewCertPool() + caCertPath := "server.crt" + if caPath != "" { + if !strings.HasSuffix(caPath, "/") { + caPath = fmt.Sprintf("%s/", caPath) + } + caCertPath = fmt.Sprintf("%s%s", caPath, caCertPath) + } + caCert, err := os.ReadFile(caCertPath) + if err != nil { + return nil, fmt.Errorf("could not read ca certificate: %s", err) + } + ca.AppendCertsFromPEM(caCert) + return ca, nil + } + + func (s *extProcServer) Process(srv envoy_service_proc_v3.ExternalProcessor_ProcessServer) error { + ctx := srv.Context() + for { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + req, err := srv.Recv() + if err == io.EOF { + return nil + } + if err != nil { + return status.Errorf(codes.Unknown, "cannot receive stream request: %v", err) + } + + resp := &envoy_service_proc_v3.ProcessingResponse{} + switch v := req.Request.(type) { + case *envoy_service_proc_v3.ProcessingRequest_RequestHeaders: + rhq := &envoy_service_proc_v3.HeadersResponse{ + Response: &envoy_service_proc_v3.CommonResponse{ + HeaderMutation: &envoy_service_proc_v3.HeaderMutation{ + SetHeaders: []*envoy_api_v3_core.HeaderValueOption{ + { + Header: &envoy_api_v3_core.HeaderValue{ + Key: "x-request-ext-processed", + RawValue: []byte("true"), + }, + }, + }, + }, + }, + } + + resp = &envoy_service_proc_v3.ProcessingResponse{ + Response: &envoy_service_proc_v3.ProcessingResponse_RequestHeaders{ + RequestHeaders: rhq, + }, + } + break + case *envoy_service_proc_v3.ProcessingRequest_ResponseHeaders: + rhq := &envoy_service_proc_v3.HeadersResponse{ + Response: &envoy_service_proc_v3.CommonResponse{ + HeaderMutation: &envoy_service_proc_v3.HeaderMutation{ + SetHeaders: []*envoy_api_v3_core.HeaderValueOption{ + { + Header: &envoy_api_v3_core.HeaderValue{ + Key: "x-response-ext-processed", + RawValue: []byte("true"), + }, + }, + }, + }, + }, + } + resp = &envoy_service_proc_v3.ProcessingResponse{ + Response: &envoy_service_proc_v3.ProcessingResponse_ResponseHeaders{ + ResponseHeaders: rhq, + }, + } + break + default: + log.Printf("Unknown Request type %v\n", v) + } + if err := srv.Send(resp); err != nil { + log.Printf("send error %v", err) + } + } + } + + +--- +apiVersion: v1 +kind: Secret +metadata: + name: grpc-ext-proc-secret + namespace: gateway-conformance-infra +type: kubernetes.io/tls +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZxekNDQTVPZ0F3SUJBZ0lVVnV6VUJrakZOeGxOdlorTVB5UjFBQzdUcWI4d0RRWUpLb1pJaHZjTkFRRUwKQlFBd0dERVdNQlFHQTFVRUF3d05aM0p3WXkxbGVIUXRZWFYwYURBZUZ3MHlOREF6TURrd016VXpNVGRhRncwegpOREF6TURjd016VXpNVGRhTUJneEZqQVVCZ05WQkFNTURXZHljR010WlhoMExXRjFkR2d3Z2dJaU1BMEdDU3FHClNJYjNEUUVCQVFVQUE0SUNEd0F3Z2dJS0FvSUNBUUNabmplR2xaYkRWZW50MHZFdkZRWllMUjhYL0ZlTU45TzgKenhGSVp1OXdHQkVIazNTd24vWnhvOG1hTk5CMUw3UjEvTnMydVQwdUdXdS9YSHVVeVJyOG5zeDNGS21uTkxINwp0WFNsbGxFV1NXM05UTnQ2T2lNVXFReWdCcE5seUhETDRXRHpNWG53S200bFFhRFlqcGdzUVZPM3pJWERWRVUyCjRGRllONVJSZGkyOVBLMlRTTWxWYWt0RExic2ltWFM0WXIwQlBkbTZHRTczajFzU2d6WHd5RnZ6a24rQWNIVFYKdTBkN2diT1MwUjBjRTFUK0JSSVExVENCMWJvRndDNW5BNjNySUMrb0lzZUFJS2s4OHYyT3prV0dQeDM5KzlFTQowVEVqbUZCdG9ZcXRzbXhGVlB6YkdhbytieGZKR0g3cG5FSWN0V1h1WHhheEVkb25tMFpVSWJqQlpsUTlVaHJHCnFQWnA3ZHB4YytsR2FmTlRWcngwb1hsNExLelZUTnVKZnFJdXZwVlRTd3hOWTJoZE8weHdqbDBWYlovb2pzNVoKVXVLU3AxNktNaitpN2drMmN5ckxuQlRER2FpWnEyVXUwZ21QVjczTUtjOExFcW9JN2c4Ymk2b3BBYjkzaGxpbApzSkNtWWtneTZCdytIM3J0THpZeCtFcENRZjVyWno2Q3hBZCtML1pIQURGY0d1VFNSRE9DNnd1RGZpNFFDSWJPCjdyNmdzbytzem5xbVJDZDhCMXZSVC9ORjZUOElhU1k2aGJwZkZCKzdrWDFyQysrVjdOZlZ4ODFXS2pUUHNJU2kKODBrb2JWdkM4cWp2di82bENESHZMNWZiWmI2YnUwSG9FN3kzK1lrYU9YaEtOcHdHaWZQT2tobTM4TzhHd280MQp3TTZtVW5HdHZ3SURBUUFCbzRIc01JSHBNQjBHQTFVZERnUVdCQlFGd2E2bkkyZk5iRmkvZ0Jwb0dXemFpR2JhCnp6QWZCZ05WSFNNRUdEQVdnQlFGd2E2bkkyZk5iRmkvZ0Jwb0dXemFpR2JhenpBSkJnTlZIUk1FQWpBQU1Bc0cKQTFVZER3UUVBd0lGNERBVEJnTlZIU1VFRERBS0JnZ3JCZ0VGQlFjREFUQk1CZ05WSFJFRVJUQkRnZzFuY25CagpMV1Y0ZEMxaGRYUm9naWRuY25CakxXVjRkQzFoZFhSb0xtZGhkR1YzWVhrdFkyOXVabTl5YldGdVkyVXRhVzVtCmNtR0NDV3h2WTJGc2FHOXpkREFzQmdsZ2hrZ0JodmhDQVEwRUh4WWRUM0JsYmxOVFRDQkhaVzVsY21GMFpXUWcKUTJWeWRHbG1hV05oZEdVd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dJQkFKSXpTb0M5UFEvUjhmMDJwKzREV3ZUegpXNzh2S0pJeGlMa283b25SMXF0MEgyT0x2NUtjNGF0blQvanh0N1ZaV3k0VUprZmowYlZxVHVXVTRXeWFobWxICmIxUUt3V2lYM2Jqditzd2JvOC93WkoyMnNIdzBib3FuMEdWcmdyUVgwaEViaDZUNDdlWUNjQnR2Z1ZWbUNLbnIKaXNzbVUwSGhwb3g2cm9UM3dhbjhsOWRGRDR4bzlpaHE0ckh1b3JCbElNQ2d2RWhkSVVIVDB3eVgyejRLWFJTWgpiZ0U4ZXpVZ295dWVPamdvRTZhZ0xidEs4S1VVUVdmTExxZ0ZRT3M4ckE3SGZ2blF4Qjd3aUpkdXZJZGV5ZitpCnRuN2ZRVkNxcFd6c0h1R2Z2WTNpdmpuQWNRYjlUb3ErUTRJKy9YdHExN0doMzlnbzYrMW5tL1Yvb0pQRWFnRWcKWEwrT3pjT0Y2Y09NRDdaeW92M1BXVmJKbVJGc3F2aTIvaWpmOHZ0Z201ZkdVRlJJY0pLWmFrN2Y0QzlENUNpagorM3l5aThQaG9RSHlxQzZxK0dNRWF4czJGQ1hXQW1vMXhXVTY3cENDWU9NZ2VnS2NtWGFoR2hWRHB3VHV1RHNICmUxUXdUTGZNQUNrczB2UVd0OWxMMHUxN090cXpROTR6TnRMRTlkU3VMYVp2U1hxaTBQaklWcXVNdXFVQnU5djgKMDFaMVRWQmZGd1VOTzB0Z1VBaU1STWNWbGZqS2ozZkUweE5aZUIvbVhodmFpeTVoWmE2dlVxSXJFYzl5eHJJdwp1Q28zQWNnZmY5YUYrM0FVQlg0b1dpYURtUDBaTDVWMHJEMGRWU1dlQW1qYWdXVXRUc1ZGelk4Y2J5T0c2aFd4CmlGSTFVZkxRL0N1T3ROc0RUYmkwCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRd0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQ1Mwd2dna3BBZ0VBQW9JQ0FRQ1puamVHbFpiRFZlbnQKMHZFdkZRWllMUjhYL0ZlTU45Tzh6eEZJWnU5d0dCRUhrM1N3bi9aeG84bWFOTkIxTDdSMS9OczJ1VDB1R1d1LwpYSHVVeVJyOG5zeDNGS21uTkxIN3RYU2xsbEVXU1czTlROdDZPaU1VcVF5Z0JwTmx5SERMNFdEek1YbndLbTRsClFhRFlqcGdzUVZPM3pJWERWRVUyNEZGWU41UlJkaTI5UEsyVFNNbFZha3RETGJzaW1YUzRZcjBCUGRtNkdFNzMKajFzU2d6WHd5RnZ6a24rQWNIVFZ1MGQ3Z2JPUzBSMGNFMVQrQlJJUTFUQ0IxYm9Gd0M1bkE2M3JJQytvSXNlQQpJS2s4OHYyT3prV0dQeDM5KzlFTTBURWptRkJ0b1lxdHNteEZWUHpiR2FvK2J4ZkpHSDdwbkVJY3RXWHVYeGF4CkVkb25tMFpVSWJqQlpsUTlVaHJHcVBacDdkcHhjK2xHYWZOVFZyeDBvWGw0TEt6VlROdUpmcUl1dnBWVFN3eE4KWTJoZE8weHdqbDBWYlovb2pzNVpVdUtTcDE2S01qK2k3Z2syY3lyTG5CVERHYWlacTJVdTBnbVBWNzNNS2M4TApFcW9JN2c4Ymk2b3BBYjkzaGxpbHNKQ21Za2d5NkJ3K0gzcnRMell4K0VwQ1FmNXJaejZDeEFkK0wvWkhBREZjCkd1VFNSRE9DNnd1RGZpNFFDSWJPN3I2Z3NvK3N6bnFtUkNkOEIxdlJUL05GNlQ4SWFTWTZoYnBmRkIrN2tYMXIKQysrVjdOZlZ4ODFXS2pUUHNJU2k4MGtvYlZ2QzhxanZ2LzZsQ0RIdkw1ZmJaYjZidTBIb0U3eTMrWWthT1hoSwpOcHdHaWZQT2tobTM4TzhHd280MXdNNm1Vbkd0dndJREFRQUJBb0lDQUJ4bml2RFJ2QnpHU0FqM2xpMFVnQ1hSCndnd1hWc0RRbWRBeG9ZcDNyaEpXQU9BYnZkbmkyaGpOSmp2alJDQkcvK0ZKTGVlQ2ZQT0hNOHNnZUtGY1JpY3IKM2JhdkZXZWJjTVdRR2M5OGFlWHJFZWlDSzVzQUlQaHpBYWlkVHFmbFZpWDh1SVovUVlMTTlhemg0N0huTy9BQwo3RTN4L1ZIT3hUV09hTHdkR3NtdFJtdlZTbXNQYkZyazJxSERWUFRpMXhnNCthVy9JQUV1K1hzQkFOLzlidjNrCnJrdnRiTEg5R1QxajhhVytwOHVmNnZnRUF4VXRMdGY1ODR3dVRzVTljZGNPY1J3bXlXa1hkVGdWMGZVNUlQVkUKNHNvZDVaZk85aXFlaTYxL1BtL25ETk50U0dQUmdTZXFLVForS0RIQTI4YXFZL2NXKzVBRitSWW9yT1BoN0REYwo3REIxQjQ2SVNyZDVZRzZadjQxaVA4Ui85WWpwYXFwMk13bXhsNWlWUVBWd0psbGZPV3p3VHEwa3BNS1lpWDlBCmE4ZWpMc3RXdEM3c0lCVHdKeldWK3RVNnVIckhyZ0hEdlJYaFZHRHN0eXVIUUhxczA0ZXVrZmlhVHJVdlR1WVgKbUp3SFprVVNTb1U0WGFMTURGYWFwN0tyaEZaTEZsTzJZdVhna25oNTFlc0x2RVB3ekVPUFNWMmJGc0hZS0pxbwo5Q1IyeDdBNzZPcXU5cGhSTVRacXVwMCt6R0hsU2VHNlo0clZKKzNYWExLS1pubjV4UXJjWlY4Y0d3SXo2dnZPCnJvU0tIaC9tV0VvQll0UEJEMkhYT010THUwMHdZUlF6ckdOa3VOcmtlQlQxZkNndG1za3VxOGNEZHo0bWs1c3AKYkR2UFhKNW5URWlsOEo2VXFhNGhBb0lCQVFESVZXWkhlOVhnUk9ZU2tEUmNGNFEwYUZrRkxGN2ZMOVY5V0hyeQpmVGVvdnFuMjVFQlVaTCtKMmhDbURMOVZ5MUR6YWhSd083TVZRYjRHT09iejhncFVqckxzb2RycGFydjlQNFhiClV6VTRvTTM5ZjU1OFhkTGtBRHk0VUxtTTZVNHBJait3YzdhSFhMMmRHNE5zNkplN0lLclh1c3pnQTl1S3c4UFcKRXYweVlZN2lIOXUydlM2MXZxQlBzQkRNbUltL2I0ekx2UEFwTnR5MkIrNWh3anFoTjRWSEpEUFVQRVVQWXJ6cwpmZEkwL2krWTZtaURweHptSXk1VWh4TkJvVitBblZ5L3BiU0tOc1Q3bHdPekQ2REJaREQ3MEFsenRUNlo5OXh3CkFseWhBU2t2ZmxneFd2TWJ3MEVKN25HUWNhTTR6YW5HNE00SGRCZXZJSnozMmpFSEFvSUJBUURFVGJ5R3AvNVQKcnhoNzNoT05xT3NZOGpxdFI0eVg3REpJcFB2eVNocUVoTUVXQVpYb1ZjRTQvSXd2c1JDVXdxald3VkN5ampJUQpKSUh4TmNEU1Rtd1JPcEQ5OGY2S01zamx0bWRtd3pLblBiZnhleEhwZTRMQmh0UFc5bE9GRHpYblpXWnNWSk1PCm9DV3pwL0N2cldCM3dzM1FsTElzTXhUTDhhTW1KOVBiUFcrUEcyWXphM05rMWV1bm4zWm81SGtkeHJFaDBCVngKMjMxRjA2ZGdoNmZBcDZVZXg0WXdiSUlRU1hGRkI4cmJ3V3Frb0RZYTNWVlZTQ29Dbk9VNHBRb0pudHVkb0cyOQpwTnJaQzlHVWFwUGdycDJoV3Iya0tCY3lYVTBQRksrM1hWSE80aHJLY1FVU3M1c2dTd1NiS3F3eGQ0eEs2djB6CllHeXhuYmR4cDhlSkFvSUJBUURETnFTaUI4UVQ5RStWeXp1YWViZjBNYko5WGcxY3d3bndTT1lWb1hzNVRnSGwKZWVwTjBwYnF4N250ZFFLRm9jZlNTbzU4QjFDczZCRTVrdjFLdlpMZmJ1WmZ2Q0RMejV3OFhVZ2N2dXBmc2lMSQpZVEdZMHZ5TC9NY3VmRXN6U3ExRlhBQmYrNEhrU2JUamdVb0NPR3lTaG5TMEgyMUE1Y0ZyYVBST2lOWjkzNThTCkxpVTEzd2ZEUm15RStuYUVTQ2dDaWJyVFZkdFk3Z01JeHBXK1lUd3NtU09nZldDYjhkY0I5UjlQL0JONFhERVoKZTJJNDJBRkxLUUVla1Rsc2ZNbkpWSTVxbWhoaGpwbEk2SkZNVFhCQ3cxVVFMRnJwaTdYaTV5ckJZeXZNSUl0MwpvbEJpVjlRS082d0c5M2xtWGJYRnhuTW9QeXZGQzVXQXEvRUpmRzdGQW9JQkFRQ1d2R3FMcSt3ckxrVEt1TmlpCjZwYThiU1NKY211UExSdmZsSEN5dUJ4c3JkUG1wZ2tLZ3U4QVowenVRalROUmp5SHk2Ry91bGpPOUhtalV2ZGgKaGo2TmJEOXlBS1RJVWY3YUJacVkya0xIRVNseUVHTE11cjdKQkZNZXViK2dhUEduWWNHb1pia1dmZnIxWFh3QQpLazN0S0hVS09XUW5kSUgvcU9qeW41cWF1eTR5NFlNMDhNbUhJSXo0QmdiU3ZMNFVFMEpwQ0hPdkhpK3ZzcnJQCjhOcnJvTSsyTnRmZnp6S0FkeVMzTVNpZ1hvRVpNTHpiSENJdWZsOWo2ajVKcE5GMFdidWg2bExhVVFDTHNmdVkKejg0RnRZL3RHdFNNZlF4eTdCb0Qvb3AvYnZVbXU1Qis2eEpPTGdSc1k2NkJ4OTY1aldiNUVFQ2xkdUYvOGUrdgpJbnFoQW9JQkFFS2tza29mRE5lKzZTeWpsTFBzUVVBbkljdVdBOWttMlQ1OFRrSGF3emlJWmFMVjN4eFpCcUhXCjJQek9OUE40dlZzNzNNLzJJb1JjSmZYdmZlVnF1Rkk4QVdDbzh5b0Z2cFVFYnJKOHVwV2ZGVExVTjZ6UFRMdkgKSTBDVU5Gb1A0TWI1ZGNWUXBwT3pWY3d3UWUvM2FuN3hrZHBpdkFMTWZwYU5YaHg5dG5rc3JQVXJiQjBuNzVZbQo1WGxvVTZPNTY3cmN4c0g4b2VLaUpGZUdxMnJRQ1d5K2NJREk0T3cvNlJmcWd2TEdjb2JleDMwY1ZLT29iRWVVCmNnWnZNM2VvczZ5TCtRdGY2bUhldnN0VkRkaldsaUpoUGlBUkVTeVZwdy9XVE1aU1pORnpjZ1R6M2p3TklHMUwKQndaaVNnMTkxMzdlTlFjalIxdUxHYnVWSWtXVGN0bz0KLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo= +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: grpc-ext-proc-ca + namespace: gateway-conformance-infra +data: + ca.crt: | + -----BEGIN CERTIFICATE----- + MIIFqzCCA5OgAwIBAgIUVuzUBkjFNxlNvZ+MPyR1AC7Tqb8wDQYJKoZIhvcNAQEL + BQAwGDEWMBQGA1UEAwwNZ3JwYy1leHQtYXV0aDAeFw0yNDAzMDkwMzUzMTdaFw0z + NDAzMDcwMzUzMTdaMBgxFjAUBgNVBAMMDWdycGMtZXh0LWF1dGgwggIiMA0GCSqG + SIb3DQEBAQUAA4ICDwAwggIKAoICAQCZnjeGlZbDVent0vEvFQZYLR8X/FeMN9O8 + zxFIZu9wGBEHk3Swn/Zxo8maNNB1L7R1/Ns2uT0uGWu/XHuUyRr8nsx3FKmnNLH7 + tXSlllEWSW3NTNt6OiMUqQygBpNlyHDL4WDzMXnwKm4lQaDYjpgsQVO3zIXDVEU2 + 4FFYN5RRdi29PK2TSMlVaktDLbsimXS4Yr0BPdm6GE73j1sSgzXwyFvzkn+AcHTV + u0d7gbOS0R0cE1T+BRIQ1TCB1boFwC5nA63rIC+oIseAIKk88v2OzkWGPx39+9EM + 0TEjmFBtoYqtsmxFVPzbGao+bxfJGH7pnEIctWXuXxaxEdonm0ZUIbjBZlQ9UhrG + qPZp7dpxc+lGafNTVrx0oXl4LKzVTNuJfqIuvpVTSwxNY2hdO0xwjl0VbZ/ojs5Z + UuKSp16KMj+i7gk2cyrLnBTDGaiZq2Uu0gmPV73MKc8LEqoI7g8bi6opAb93hlil + sJCmYkgy6Bw+H3rtLzYx+EpCQf5rZz6CxAd+L/ZHADFcGuTSRDOC6wuDfi4QCIbO + 7r6gso+sznqmRCd8B1vRT/NF6T8IaSY6hbpfFB+7kX1rC++V7NfVx81WKjTPsISi + 80kobVvC8qjvv/6lCDHvL5fbZb6bu0HoE7y3+YkaOXhKNpwGifPOkhm38O8Gwo41 + wM6mUnGtvwIDAQABo4HsMIHpMB0GA1UdDgQWBBQFwa6nI2fNbFi/gBpoGWzaiGba + zzAfBgNVHSMEGDAWgBQFwa6nI2fNbFi/gBpoGWzaiGbazzAJBgNVHRMEAjAAMAsG + A1UdDwQEAwIF4DATBgNVHSUEDDAKBggrBgEFBQcDATBMBgNVHREERTBDgg1ncnBj + LWV4dC1hdXRogidncnBjLWV4dC1hdXRoLmdhdGV3YXktY29uZm9ybWFuY2UtaW5m + cmGCCWxvY2FsaG9zdDAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQg + Q2VydGlmaWNhdGUwDQYJKoZIhvcNAQELBQADggIBAJIzSoC9PQ/R8f02p+4DWvTz + W78vKJIxiLko7onR1qt0H2OLv5Kc4atnT/jxt7VZWy4UJkfj0bVqTuWU4WyahmlH + b1QKwWiX3bjv+swbo8/wZJ22sHw0boqn0GVrgrQX0hEbh6T47eYCcBtvgVVmCKnr + issmU0Hhpox6roT3wan8l9dFD4xo9ihq4rHuorBlIMCgvEhdIUHT0wyX2z4KXRSZ + bgE8ezUgoyueOjgoE6agLbtK8KUUQWfLLqgFQOs8rA7HfvnQxB7wiJduvIdeyf+i + tn7fQVCqpWzsHuGfvY3ivjnAcQb9Toq+Q4I+/Xtq17Gh39go6+1nm/V/oJPEagEg + XL+OzcOF6cOMD7Zyov3PWVbJmRFsqvi2/ijf8vtgm5fGUFRIcJKZak7f4C9D5Cij + +3yyi8PhoQHyqC6q+GMEaxs2FCXWAmo1xWU67pCCYOMgegKcmXahGhVDpwTuuDsH + e1QwTLfMACks0vQWt9lL0u17OtqzQ94zNtLE9dSuLaZvSXqi0PjIVquMuqUBu9v8 + 01Z1TVBfFwUNO0tgUAiMRMcVlfjKj3fE0xNZeB/mXhvaiy5hZa6vUqIrEc9yxrIw + uCo3Acgff9aF+3AUBX4oWiaDmP0ZL5V0rD0dVSWeAmjagWUtTsVFzY8cbyOG6hWx + iFI1UfLQ/CuOtNsDTbi0 + -----END CERTIFICATE----- +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: grpc-ext-proc + namespace: gateway-conformance-infra +spec: + replicas: 1 + selector: + matchLabels: + app: grpc-ext-proc + template: + metadata: + labels: + app: grpc-ext-proc + spec: + containers: + - name: golang-app-container + command: + - sh + - "-c" + - "cp -a /app /app-live && cd /app-live && go run . --certPath=/app-live/certs/ " + image: golang:1.21.3-alpine + ports: + - containerPort: 8000 + volumeMounts: + - name: grpc-ext-proc + mountPath: /app + - name: grpc-ext-proc-secret + mountPath: /app/certs + readinessProbe: + httpGet: + path: /healthz + port: 8080 + volumes: + - name: grpc-ext-proc + configMap: + name: grpc-ext-proc + - name: grpc-ext-proc-secret + secret: + secretName: grpc-ext-proc-secret + items: + - key: tls.crt + path: server.crt + - key: tls.key + path: server.key +--- +apiVersion: v1 +kind: Service +metadata: + name: grpc-ext-proc + namespace: gateway-conformance-infra +spec: + selector: + app: grpc-ext-proc + ports: + - protocol: TCP + port: 9002 + targetPort: 9002 diff --git a/test/e2e/tests/ext_proc.go b/test/e2e/tests/ext_proc.go new file mode 100644 index 00000000000..aa9978e92ea --- /dev/null +++ b/test/e2e/tests/ext_proc.go @@ -0,0 +1,124 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +//go:build e2e +// +build e2e + +package tests + +import ( + "testing" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + gwv1 "sigs.k8s.io/gateway-api/apis/v1" + gwv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + "sigs.k8s.io/gateway-api/conformance/utils/http" + "sigs.k8s.io/gateway-api/conformance/utils/kubernetes" + "sigs.k8s.io/gateway-api/conformance/utils/suite" + + "github.com/envoyproxy/gateway/internal/gatewayapi" +) + +func init() { + ConformanceTests = append(ConformanceTests, ExtProcTest) +} + +// ExtProcTest tests ExtProcs authentication for an http route with ExtProcs configured. +var ExtProcTest = suite.ConformanceTest{ + ShortName: "ExtProcs", + Description: "Test ExtProcs service that adds request and response headers", + Manifests: []string{"testdata/ext-proc-service.yaml", "testdata/ext-proc-envoyextensionpolicy.yaml"}, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + t.Run("http route with ext proc", func(t *testing.T) { + ns := "gateway-conformance-infra" + routeNN := types.NamespacedName{Name: "http-with-ext-proc", Namespace: ns} + gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} + gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) + + ancestorRef := gwv1a2.ParentReference{ + Group: gatewayapi.GroupPtr(gwv1.GroupName), + Kind: gatewayapi.KindPtr(gatewayapi.KindGateway), + Namespace: gatewayapi.NamespacePtr(gwNN.Namespace), + Name: gwv1.ObjectName(gwNN.Name), + } + EnvoyExtensionPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "ext-proc-test", Namespace: ns}, suite.ControllerName, ancestorRef) + + podReady := corev1.PodCondition{Type: corev1.PodReady, Status: corev1.ConditionTrue} + + // Wait for the grpc ext auth service pod to be ready + WaitForPods(t, suite.Client, ns, map[string]string{"app": "grpc-ext-proc"}, corev1.PodRunning, podReady) + + expectedResponse := http.ExpectedResponse{ + Request: http.Request{ + Host: "www.example.com", + Path: "/processor", + Headers: map[string]string{ + "x-request-ext-processed": "true", // header added by ext-processor to request + }, + }, + Response: http.Response{ + StatusCode: 200, + Headers: map[string]string{ + "x-response-ext-processed": "true", // header added by ext-processor to request + }, + }, + Namespace: ns, + } + + req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http") + cReq, cResp, err := suite.RoundTripper.CaptureRoundTrip(req) + if err != nil { + t.Errorf("failed to get expected response: %v", err) + } + + if err := http.CompareRequest(t, &req, cReq, cResp, expectedResponse); err != nil { + t.Errorf("failed to compare request and response: %v", err) + } + }) + + t.Run("http route without ext proc", func(t *testing.T) { + ns := "gateway-conformance-infra" + routeNN := types.NamespacedName{Name: "http-with-ext-proc", Namespace: ns} + gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} + gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) + + ancestorRef := gwv1a2.ParentReference{ + Group: gatewayapi.GroupPtr(gwv1.GroupName), + Kind: gatewayapi.KindPtr(gatewayapi.KindGateway), + Namespace: gatewayapi.NamespacePtr(gwNN.Namespace), + Name: gwv1.ObjectName(gwNN.Name), + } + EnvoyExtensionPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "ext-proc-test", Namespace: ns}, suite.ControllerName, ancestorRef) + + podReady := corev1.PodCondition{Type: corev1.PodReady, Status: corev1.ConditionTrue} + + // Wait for the grpc ext auth service pod to be ready + WaitForPods(t, suite.Client, ns, map[string]string{"app": "grpc-ext-proc"}, corev1.PodRunning, podReady) + + expectedResponse := http.ExpectedResponse{ + Request: http.Request{ + Host: "www.example.com", + Path: "/no-processor", + }, + Response: http.Response{ + StatusCode: 200, + AbsentHeaders: []string{"x-response-ext-processed"}, + }, + Namespace: ns, + } + + req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http") + cReq, cResp, err := suite.RoundTripper.CaptureRoundTrip(req) + if err != nil { + t.Errorf("failed to get expected response: %v", err) + } + + if err := http.CompareRequest(t, &req, cReq, cResp, expectedResponse); err != nil { + t.Errorf("failed to compare request and response: %v", err) + } + }) + }, +} diff --git a/test/e2e/tests/utils.go b/test/e2e/tests/utils.go index a8e632b886a..6903534558b 100644 --- a/test/e2e/tests/utils.go +++ b/test/e2e/tests/utils.go @@ -203,3 +203,25 @@ func policyAcceptedByAncestor(ancestors []gwv1a2.PolicyAncestorStatus, controlle } return false } + +// EnvoyExtensionPolicyMustBeAccepted waits for the specified EnvoyExtensionPolicy to be accepted. +func EnvoyExtensionPolicyMustBeAccepted(t *testing.T, client client.Client, policyName types.NamespacedName, controllerName string, ancestorRef gwv1a2.ParentReference) { + t.Helper() + + waitErr := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, 60*time.Second, true, func(ctx context.Context) (bool, error) { + policy := &egv1a1.EnvoyExtensionPolicy{} + err := client.Get(ctx, policyName, policy) + if err != nil { + return false, fmt.Errorf("error fetching EnvoyExtensionPolicy: %w", err) + } + + if policyAcceptedByAncestor(policy.Status.Ancestors, controllerName, ancestorRef) { + return true, nil + } + + t.Logf("EnvoyExtensionPolicy not yet accepted: %v", policy) + return false, nil + }) + + require.NoErrorf(t, waitErr, "error waiting for EnvoyExtensionPolicy to be accepted") +} diff --git a/test/helm/default.yaml b/test/helm/default.yaml index 3baba6b5d70..4b2976a62ce 100644 --- a/test/helm/default.yaml +++ b/test/helm/default.yaml @@ -110,6 +110,7 @@ rules: - clienttrafficpolicies - backendtrafficpolicies - securitypolicies + - envoyextensionpolicies verbs: - get - list @@ -121,6 +122,7 @@ rules: - clienttrafficpolicies/status - backendtrafficpolicies/status - securitypolicies/status + - envoyextensionpolicies/status verbs: - update - apiGroups: