diff --git a/internal/gatewayapi/envoyextensionpolicy.go b/internal/gatewayapi/envoyextensionpolicy.go index b03451309b..4778e6366a 100644 --- a/internal/gatewayapi/envoyextensionpolicy.go +++ b/internal/gatewayapi/envoyextensionpolicy.go @@ -667,7 +667,17 @@ func (t *Translator) translateEnvoyExtensionPolicyForGateway( policyTarget := irStringKey(policy.Namespace, string(target.Name)) - routesWithDirectResponse := sets.New[string]() + failed := luaError != nil + if wasmError != nil { + failed = failed || !wasmFailOpen + } + if extProcError != nil { + failed = failed || !extProcFailOpen + } + if dynamicModuleError != nil { + failed = true + } + for _, http := range x.HTTP { gatewayName := extractGatewayNameFromListener(http.Name) if t.MergeGateways && gatewayName != policyTarget { @@ -678,6 +688,21 @@ func (t *Translator) translateEnvoyExtensionPolicyForGateway( continue } + // if already set - there's a specific level policy, so skip + if http.EnvoyExtensions != nil { + continue + } + + // TODO: move other extensions to listener level. + // Only attach listener-level Lua when the policy succeeds; a fail-closed error in + // any other extension makes every route return a 500, so the Lua filter must not + // run on those synthetic error responses. + if len(luas) > 0 && !failed { + http.EnvoyExtensions = &ir.EnvoyExtensionFeatures{ + Luas: luas, + } + } + // A Policy targeting the specific scope(xRoute rule, xRoute, Gateway listener) wins over a policy // targeting a lesser specific scope(Gateway). for _, r := range http.Routes { @@ -686,43 +711,20 @@ func (t *Translator) translateEnvoyExtensionPolicyForGateway( continue } - failRoute := false - // Lua extension doesn't have a fail open option, so fail the route if there is a lua error - // TODO: we may also add fail open option for Lua extension to align with other extensions - if luaError != nil { - failRoute = true - } - if wasmError != nil { - failRoute = failRoute || !wasmFailOpen - } - if extProcError != nil { - failRoute = failRoute || !extProcFailOpen - } - if dynamicModuleError != nil { - failRoute = true - } - if failRoute { + if failed { r.DirectResponse = &ir.CustomResponse{ StatusCode: new(uint32(500)), } - routesWithDirectResponse.Insert(r.Name) } else { r.EnvoyExtensions = &ir.EnvoyExtensionFeatures{ - ExtProcs: extProcs, - Wasms: wasms, - Luas: luas, - DynamicModules: dynamicModules, + ExtProcs: extProcs, + Wasms: wasms, + DynamicModules: dynamicModules, + FromGatewayPolicy: new(true), } } } } - if len(routesWithDirectResponse) > 0 { - t.Logger.Info("setting 500 direct response in routes due to errors in EnvoyExtensionPolicy", - "policy", fmt.Sprintf("%s/%s", policy.Namespace, policy.Name), - "routes", sets.List(routesWithDirectResponse), - "error", errs, - ) - } return errs } diff --git a/internal/gatewayapi/testdata/custom-filter-order.out.yaml b/internal/gatewayapi/testdata/custom-filter-order.out.yaml index 358921f346..122246db68 100644 --- a/internal/gatewayapi/testdata/custom-filter-order.out.yaml +++ b/internal/gatewayapi/testdata/custom-filter-order.out.yaml @@ -291,6 +291,7 @@ xdsIR: protocol: HTTP weight: 1 envoyExtensions: + fromGatewayPolicy: true wasms: - config: parameter1: diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-override-replace.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-override-replace.out.yaml index 1a5c92bfdf..cec0d86359 100644 --- a/internal/gatewayapi/testdata/envoyextensionpolicy-override-replace.out.yaml +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-override-replace.out.yaml @@ -410,6 +410,7 @@ xdsIR: protocol: GRPC weight: 1 name: envoyextensionpolicy/envoy-gateway/policy-for-gateway-1/extproc/0 + fromGatewayPolicy: true hostname: gateway.envoyproxy.io isHTTP2: false metadata: diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-section-name-override.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-section-name-override.out.yaml index 2312844481..f5500f2deb 100644 --- a/internal/gatewayapi/testdata/envoyextensionpolicy-section-name-override.out.yaml +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-section-name-override.out.yaml @@ -525,6 +525,7 @@ xdsIR: protocol: GRPC weight: 1 name: envoyextensionpolicy/envoy-gateway/policy-for-gateway-1-listener-1/extproc/0 + fromGatewayPolicy: true hostname: gateway.envoyproxy.io isHTTP2: false metadata: @@ -595,6 +596,7 @@ xdsIR: protocol: GRPC weight: 1 name: envoyextensionpolicy/envoy-gateway/policy-for-gateway-1/extproc/0 + fromGatewayPolicy: true hostname: gateway.envoyproxy.io isHTTP2: false metadata: diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-dynamicmodule.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-dynamicmodule.out.yaml index 1eede11cad..e5e0faaae3 100644 --- a/internal/gatewayapi/testdata/envoyextensionpolicy-with-dynamicmodule.out.yaml +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-dynamicmodule.out.yaml @@ -434,6 +434,7 @@ xdsIR: sha256: abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789 url: https://modules.example.com/libremote_auth.so terminalFilter: false + fromGatewayPolicy: true hostname: www.example.com isHTTP2: false metadata: diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-with-backendtlspolicy.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-with-backendtlspolicy.out.yaml index adf02bfc24..1632284912 100644 --- a/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-with-backendtlspolicy.out.yaml +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-extproc-with-backendtlspolicy.out.yaml @@ -409,6 +409,7 @@ xdsIR: - connection.requested_server_name responseBodyProcessingMode: Streamed responseHeaderProcessing: true + fromGatewayPolicy: true hostname: www.foo.com isHTTP2: false metadata: @@ -484,6 +485,7 @@ xdsIR: - connection.requested_server_name responseBodyProcessingMode: Streamed responseHeaderProcessing: true + fromGatewayPolicy: true hostname: www.bar.com isHTTP2: false metadata: diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua-validation-disabled.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua-validation-disabled.out.yaml index 68b4e397ba..0c418ae76a 100644 --- a/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua-validation-disabled.out.yaml +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua-validation-disabled.out.yaml @@ -208,15 +208,14 @@ xdsIR: weight: 1 envoyExtensions: luas: - - Code: | + - code: | function envoy_on_response(response_handle) local value = 10 if value > 5 print("Value is greater than 5") end end - FilterContext: null - Name: envoyextensionpolicy/default/policy-for-http-route/lua/0 + name: envoyextensionpolicy/default/policy-for-http-route/lua/0 hostname: www.example.com isHTTP2: false metadata: diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua-validation-syntax.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua-validation-syntax.out.yaml index b79ae5ac67..577c10e453 100644 --- a/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua-validation-syntax.out.yaml +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua-validation-syntax.out.yaml @@ -298,7 +298,7 @@ xdsIR: weight: 1 envoyExtensions: luas: - - Code: | + - code: | local json = require("json") function envoy_on_response(response_handle) local content_type = response_handle:headers():get("content-type") @@ -317,8 +317,7 @@ xdsIR: end return envoy.lua.ResponseStatus.Continue end - FilterContext: null - Name: envoyextensionpolicy/default/policy-for-http-route/lua/0 + name: envoyextensionpolicy/default/policy-for-http-route/lua/0 hostname: www.example.com isHTTP2: false metadata: diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua.out.yaml index 8c9e4d42ab..81c75376eb 100644 --- a/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua.out.yaml +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua.out.yaml @@ -253,6 +253,11 @@ xdsIR: protocol: TCP http: - address: 0.0.0.0 + envoyExtensions: + luas: + - code: function envoy_on_request(request_handle) request_handle:logInfo('Goodbye.') + end + name: envoyextensionpolicy/envoy-gateway/policy-for-gateway/lua/0 externalPort: 80 hostnames: - '*' @@ -289,11 +294,7 @@ xdsIR: directResponse: statusCode: 500 envoyExtensions: - luas: - - Code: function envoy_on_request(request_handle) request_handle:logInfo('Goodbye.') - end - FilterContext: null - Name: envoyextensionpolicy/envoy-gateway/policy-for-gateway/lua/0 + fromGatewayPolicy: true hostname: www.example.com isHTTP2: false metadata: @@ -325,11 +326,7 @@ xdsIR: protocol: HTTP weight: 1 envoyExtensions: - luas: - - Code: function envoy_on_request(request_handle) request_handle:logInfo('Goodbye.') - end - FilterContext: null - Name: envoyextensionpolicy/envoy-gateway/policy-for-gateway/lua/0 + fromGatewayPolicy: true hostname: www.example.com isHTTP2: false metadata: diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua-configmap.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua-configmap.out.yaml index df57f5706f..800eb241ed 100644 --- a/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua-configmap.out.yaml +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua-configmap.out.yaml @@ -425,6 +425,13 @@ xdsIR: protocol: TCP http: - address: 0.0.0.0 + envoyExtensions: + luas: + - code: | + function envoy_on_request(request_handle) + request_handle:logInfo('Goodbye.') + end + name: envoyextensionpolicy/envoy-gateway/policy-for-gateway/lua/0 externalPort: 80 hostnames: - '*' @@ -461,13 +468,7 @@ xdsIR: directResponse: statusCode: 500 envoyExtensions: - luas: - - Code: | - function envoy_on_request(request_handle) - request_handle:logInfo('Goodbye.') - end - FilterContext: null - Name: envoyextensionpolicy/envoy-gateway/policy-for-gateway/lua/0 + fromGatewayPolicy: true hostname: www.example.com isHTTP2: false metadata: @@ -501,13 +502,7 @@ xdsIR: directResponse: statusCode: 500 envoyExtensions: - luas: - - Code: | - function envoy_on_request(request_handle) - request_handle:logInfo('Goodbye.') - end - FilterContext: null - Name: envoyextensionpolicy/envoy-gateway/policy-for-gateway/lua/0 + fromGatewayPolicy: true hostname: www.example.com isHTTP2: false metadata: @@ -541,13 +536,7 @@ xdsIR: directResponse: statusCode: 500 envoyExtensions: - luas: - - Code: | - function envoy_on_request(request_handle) - request_handle:logInfo('Goodbye.') - end - FilterContext: null - Name: envoyextensionpolicy/envoy-gateway/policy-for-gateway/lua/0 + fromGatewayPolicy: true hostname: www.example.com isHTTP2: false metadata: @@ -580,13 +569,13 @@ xdsIR: weight: 1 envoyExtensions: luas: - - Code: | + - code: | function envoy_on_response(response_handle) response_handle:logWarn('Goodbye.') end - FilterContext: + filterContext: token_header: x-session-token - Name: envoyextensionpolicy/default/policy-for-http-route/lua/0 + name: envoyextensionpolicy/default/policy-for-http-route/lua/0 hostname: www.example.com isHTTP2: false metadata: @@ -618,13 +607,7 @@ xdsIR: protocol: HTTP weight: 1 envoyExtensions: - luas: - - Code: | - function envoy_on_request(request_handle) - request_handle:logInfo('Goodbye.') - end - FilterContext: null - Name: envoyextensionpolicy/envoy-gateway/policy-for-gateway/lua/0 + fromGatewayPolicy: true hostname: www.example.com isHTTP2: false metadata: diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua.in.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua.in.yaml index 763e770bfb..68bed58b38 100644 --- a/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua.in.yaml +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua.in.yaml @@ -13,6 +13,29 @@ gateways: allowedRoutes: namespaces: from: All +# two listeners on the same port, each with a distinct Lua policy. +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-2 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http-a + protocol: HTTP + port: 80 + hostname: "app-a.example.com" + allowedRoutes: + namespaces: + from: All + - name: http-b + protocol: HTTP + port: 80 + hostname: "app-b.example.com" + allowedRoutes: + namespaces: + from: All httpRoutes: - apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute @@ -52,6 +75,46 @@ httpRoutes: backendRefs: - name: service-1 port: 8080 +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-a + spec: + hostnames: + - app-a.example.com + parentRefs: + - namespace: envoy-gateway + name: gateway-2 + sectionName: http-a + rules: + - matches: + - path: + type: PathPrefix + value: "/" + backendRefs: + - name: service-1 + port: 8080 +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-b + spec: + hostnames: + - app-b.example.com + parentRefs: + - namespace: envoy-gateway + name: gateway-2 + sectionName: http-b + rules: + - matches: + - path: + type: PathPrefix + value: "/" + backendRefs: + - name: service-1 + port: 8080 envoyextensionpolicies: - apiVersion: gateway.envoyproxy.io/v1alpha1 kind: EnvoyExtensionPolicy @@ -88,3 +151,37 @@ envoyextensionpolicies: filterContext: token_header: x-api-key mode: strict +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + namespace: envoy-gateway + name: policy-for-gateway-a + generation: 30 + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-2 + sectionName: http-a + lua: + - type: Inline + inline: "function envoy_on_request(request_handle) + request_handle:logInfo('tenant-A script.') + end" +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + namespace: envoy-gateway + name: policy-for-gateway-b + generation: 30 + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-2 + sectionName: http-b + lua: + - type: Inline + inline: "function envoy_on_request(request_handle) + request_handle:logInfo('tenant-B script.') + end" diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua.out.yaml index 29bfd408e5..589c26ff13 100644 --- a/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua.out.yaml +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua.out.yaml @@ -39,6 +39,82 @@ envoyExtensionPolicies: status: "True" type: Warning controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + generation: 30 + name: policy-for-gateway-a + namespace: envoy-gateway + spec: + lua: + - inline: function envoy_on_request(request_handle) request_handle:logInfo('tenant-A + script.') end + type: Inline + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-2 + sectionName: http-a + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-2 + namespace: envoy-gateway + sectionName: http-a + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + observedGeneration: 30 + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: spec.targetRef is deprecated, use spec.targetRefs instead + observedGeneration: 30 + reason: DeprecatedField + status: "True" + type: Warning + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + generation: 30 + name: policy-for-gateway-b + namespace: envoy-gateway + spec: + lua: + - inline: function envoy_on_request(request_handle) request_handle:logInfo('tenant-B + script.') end + type: Inline + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-2 + sectionName: http-b + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-2 + namespace: envoy-gateway + sectionName: http-b + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + observedGeneration: 30 + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: spec.targetRef is deprecated, use spec.targetRefs instead + observedGeneration: 30 + reason: DeprecatedField + status: "True" + type: Warning + controllerName: gateway.envoyproxy.io/gatewayclass-controller - apiVersion: gateway.envoyproxy.io/v1alpha1 kind: EnvoyExtensionPolicy metadata: @@ -122,6 +198,76 @@ gateways: kind: HTTPRoute - group: gateway.networking.k8s.io kind: GRPCRoute +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + name: gateway-2 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + hostname: app-a.example.com + name: http-a + port: 80 + protocol: HTTP + - allowedRoutes: + namespaces: + from: All + hostname: app-b.example.com + name: http-b + 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-a + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute + - 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-b + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute httpRoutes: - apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute @@ -197,6 +343,82 @@ httpRoutes: name: gateway-1 namespace: envoy-gateway sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: httproute-a + namespace: default + spec: + hostnames: + - app-a.example.com + parentRefs: + - name: gateway-2 + namespace: envoy-gateway + sectionName: http-a + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + type: PathPrefix + value: / + 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-2 + namespace: envoy-gateway + sectionName: http-a +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: httproute-b + namespace: default + spec: + hostnames: + - app-b.example.com + parentRefs: + - name: gateway-2 + namespace: envoy-gateway + sectionName: http-b + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + type: PathPrefix + value: / + 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-2 + namespace: envoy-gateway + sectionName: http-b infraIR: envoy-gateway/gateway-1: proxy: @@ -216,6 +438,24 @@ infraIR: name: envoy-gateway-class name: envoy-gateway/gateway-1 namespace: envoy-gateway-system + envoy-gateway/gateway-2: + proxy: + listeners: + - name: envoy-gateway/gateway-2/http-a + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-2 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + ownerReference: + kind: GatewayClass + name: envoy-gateway-class + name: envoy-gateway/gateway-2 + namespace: envoy-gateway-system xdsIR: envoy-gateway/gateway-1: accessLog: @@ -244,6 +484,11 @@ xdsIR: protocol: TCP http: - address: 0.0.0.0 + envoyExtensions: + luas: + - code: function envoy_on_request(request_handle) request_handle:logInfo('Goodbye.') + end + name: envoyextensionpolicy/envoy-gateway/policy-for-gateway/lua/0 externalPort: 80 hostnames: - '*' @@ -279,12 +524,12 @@ xdsIR: weight: 1 envoyExtensions: luas: - - Code: function envoy_on_response(response_handle) response_handle:logWarn('Goodbye.') + - code: function envoy_on_response(response_handle) response_handle:logWarn('Goodbye.') end - FilterContext: + filterContext: mode: strict token_header: x-api-key - Name: envoyextensionpolicy/default/policy-for-http-route/lua/0 + name: envoyextensionpolicy/default/policy-for-http-route/lua/0 hostname: www.example.com isHTTP2: false metadata: @@ -316,11 +561,7 @@ xdsIR: protocol: HTTP weight: 1 envoyExtensions: - luas: - - Code: function envoy_on_request(request_handle) request_handle:logInfo('Goodbye.') - end - FilterContext: null - Name: envoyextensionpolicy/envoy-gateway/policy-for-gateway/lua/0 + fromGatewayPolicy: true hostname: www.example.com isHTTP2: false metadata: @@ -337,3 +578,138 @@ xdsIR: ipFamily: IPv4 path: /ready port: 19003 + envoy-gateway/gateway-2: + accessLog: + json: + - path: /dev/stdout + globalResources: + proxyServiceCluster: + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-2-4a0e4eb9 + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway-2 + settings: + - addressType: IP + endpoints: + - host: 7.6.5.4 + port: 8080 + zone: zone1 + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-2-4a0e4eb9 + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway-2 + protocol: TCP + http: + - address: 0.0.0.0 + envoyExtensions: + luas: + - code: function envoy_on_request(request_handle) request_handle:logInfo('tenant-A + script.') end + name: envoyextensionpolicy/envoy-gateway/policy-for-gateway-a/lua/0 + externalPort: 80 + hostnames: + - app-a.example.com + metadata: + kind: Gateway + name: gateway-2 + namespace: envoy-gateway + sectionName: http-a + name: envoy-gateway/gateway-2/http-a + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + metadata: + kind: HTTPRoute + name: httproute-a + namespace: default + name: httproute/default/httproute-a/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + metadata: + kind: Service + name: service-1 + namespace: default + sectionName: "8080" + name: httproute/default/httproute-a/rule/0/backend/0 + protocol: HTTP + weight: 1 + envoyExtensions: + fromGatewayPolicy: true + hostname: app-a.example.com + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-a + namespace: default + name: httproute/default/httproute-a/rule/0/match/0/app-a_example_com + pathMatch: + distinct: false + name: "" + prefix: / + - address: 0.0.0.0 + envoyExtensions: + luas: + - code: function envoy_on_request(request_handle) request_handle:logInfo('tenant-B + script.') end + name: envoyextensionpolicy/envoy-gateway/policy-for-gateway-b/lua/0 + externalPort: 80 + hostnames: + - app-b.example.com + metadata: + kind: Gateway + name: gateway-2 + namespace: envoy-gateway + sectionName: http-b + name: envoy-gateway/gateway-2/http-b + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + metadata: + kind: HTTPRoute + name: httproute-b + namespace: default + name: httproute/default/httproute-b/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + metadata: + kind: Service + name: service-1 + namespace: default + sectionName: "8080" + name: httproute/default/httproute-b/rule/0/backend/0 + protocol: HTTP + weight: 1 + envoyExtensions: + fromGatewayPolicy: true + hostname: app-b.example.com + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-b + namespace: default + name: httproute/default/httproute-b/rule/0/match/0/app-b_example_com + pathMatch: + distinct: false + name: "" + prefix: / + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-wasm-env-vars.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-wasm-env-vars.out.yaml index 73f2f7e136..fcd2c1a9da 100644 --- a/internal/gatewayapi/testdata/envoyextensionpolicy-with-wasm-env-vars.out.yaml +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-wasm-env-vars.out.yaml @@ -348,6 +348,7 @@ xdsIR: protocol: HTTP weight: 1 envoyExtensions: + fromGatewayPolicy: true wasms: - config: null failOpen: false diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-wasm-invalid-configuration.in.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-wasm-invalid-configuration.in.yaml index 11f01273bc..3fdf7e8033 100644 --- a/internal/gatewayapi/testdata/envoyextensionpolicy-with-wasm-invalid-configuration.in.yaml +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-wasm-invalid-configuration.in.yaml @@ -316,3 +316,6 @@ envoyExtensionPolicies: pullSecretRef: name: my-pull-secret-1 sha256: 314100af781b98a8ca175d5bf90a8bf76576e20a2f397a88223404edc6ebfd46 + lua: + - type: Inline + inline: "function envoy_on_request(request_handle) end" diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-wasm-invalid-configuration.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-wasm-invalid-configuration.out.yaml index dcfca230a6..d1aadf0593 100644 --- a/internal/gatewayapi/testdata/envoyextensionpolicy-with-wasm-invalid-configuration.out.yaml +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-wasm-invalid-configuration.out.yaml @@ -228,6 +228,9 @@ envoyExtensionPolicies: name: policy-for-gateway-3 namespace: default spec: + lua: + - inline: function envoy_on_request(request_handle) end + type: Inline targetRef: group: gateway.networking.k8s.io kind: Gateway @@ -928,7 +931,8 @@ xdsIR: name: httproute/default/httproute-5/rule/0/backend/0 protocol: HTTP weight: 1 - envoyExtensions: {} + envoyExtensions: + fromGatewayPolicy: true hostname: www.foo.com isHTTP2: false metadata: @@ -959,7 +963,8 @@ xdsIR: name: httproute/default/httproute-6/rule/0/backend/0 protocol: HTTP weight: 1 - envoyExtensions: {} + envoyExtensions: + fromGatewayPolicy: true hostname: www.bar.com isHTTP2: false metadata: diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-wasm-targetrefs.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-wasm-targetrefs.out.yaml index 3c9077b3a7..33b1fe86fb 100644 --- a/internal/gatewayapi/testdata/envoyextensionpolicy-with-wasm-targetrefs.out.yaml +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-wasm-targetrefs.out.yaml @@ -320,6 +320,7 @@ xdsIR: protocol: HTTP weight: 1 envoyExtensions: + fromGatewayPolicy: true wasms: - config: parameter1: diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-wasm.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-wasm.out.yaml index cf73e95f12..1d6291159f 100644 --- a/internal/gatewayapi/testdata/envoyextensionpolicy-with-wasm.out.yaml +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-wasm.out.yaml @@ -352,6 +352,7 @@ xdsIR: protocol: HTTP weight: 1 envoyExtensions: + fromGatewayPolicy: true wasms: - config: parameter1: diff --git a/internal/ir/xds.go b/internal/ir/xds.go index 5987fb8706..a9c62d2e06 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -341,6 +341,8 @@ type HTTPListener struct { MatchBackendScheme bool `json:"matchBackendScheme,omitempty" yaml:"matchBackendScheme,omitempty"` // RequestID defines configuration for the UUID request ID extension. RequestID *RequestIDExtensionAction `json:"requestID,omitempty" yaml:"requestID,omitempty"` + // EnvoyExtension holds the features associated with EnvoyExtensionPolicy + EnvoyExtensions *EnvoyExtensionFeatures `json:"envoyExtensions,omitempty" yaml:"envoyExtensions,omitempty"` } // Validate the fields within the HTTPListener structure @@ -1154,6 +1156,8 @@ type EnvoyExtensionFeatures struct { Luas []Lua `json:"luas,omitempty" yaml:"luas,omitempty"` // Dynamic Module extensions DynamicModules []DynamicModule `json:"dynamicModules,omitempty" yaml:"dynamicModules,omitempty"` + // TODO: remove this after we moved all the extensions(e.g. extProc, Wasm, DYM) to use listener-level and route-level. + FromGatewayPolicy *bool `json:"fromGatewayPolicy,omitempty" yaml:"fromGatewayPolicy,omitempty"` } // UnstructuredRef holds unstructured data for an arbitrary k8s resource introduced by an extension @@ -3715,12 +3719,12 @@ type ExtProc struct { // +k8s:deepcopy-gen=true type Lua struct { // Name is a unique name for the Lua configuration. - Name string + Name string `json:"name" yaml:"name"` // Code is the Lua source code - Code *string + Code *string `json:"code,omitempty" yaml:"code,omitempty"` // FilterContext is the filter context configuration for the Lua script. // This is a JSON object passed to the Lua script via request_handle:filterContext(). - FilterContext *apiextensionsv1.JSON + FilterContext *apiextensionsv1.JSON `json:"filterContext,omitempty" yaml:"filterContext,omitempty"` } // Wasm holds the information associated with the Wasm extensions. diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go index b9650fb876..7e841ba7c3 100644 --- a/internal/ir/zz_generated.deepcopy.go +++ b/internal/ir/zz_generated.deepcopy.go @@ -1475,6 +1475,11 @@ func (in *EnvoyExtensionFeatures) DeepCopyInto(out *EnvoyExtensionFeatures) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.FromGatewayPolicy != nil { + in, out := &in.FromGatewayPolicy, &out.FromGatewayPolicy + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvoyExtensionFeatures. @@ -2352,6 +2357,11 @@ func (in *HTTPListener) DeepCopyInto(out *HTTPListener) { *out = new(RequestIDExtensionAction) **out = **in } + if in.EnvoyExtensions != nil { + in, out := &in.EnvoyExtensions, &out.EnvoyExtensions + *out = new(EnvoyExtensionFeatures) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPListener. diff --git a/internal/xds/translator/api_key_auth.go b/internal/xds/translator/api_key_auth.go index cfbaf29152..bf545088b7 100644 --- a/internal/xds/translator/api_key_auth.go +++ b/internal/xds/translator/api_key_auth.go @@ -176,3 +176,7 @@ func buildAPIKeyAuthFilterPerRouteConfig(apiKeyAuth *ir.APIKeyAuth) *apikeyauthv Forwarding: apiKeyAuthProto.Forwarding, } } + +func (*apiKeyAuth) patchVirtualHost(_ *routev3.VirtualHost, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/authorization.go b/internal/xds/translator/authorization.go index 3119dab922..e5b342590e 100644 --- a/internal/xds/translator/authorization.go +++ b/internal/xds/translator/authorization.go @@ -855,3 +855,7 @@ func wrapPredicateWithNot(predicate *matcherv3.Matcher_MatcherList_Predicate, in }, } } + +func (*rbac) patchVirtualHost(_ *routev3.VirtualHost, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/bandwidth_limit.go b/internal/xds/translator/bandwidth_limit.go index 02e5d63bf8..1a7120c9ba 100644 --- a/internal/xds/translator/bandwidth_limit.go +++ b/internal/xds/translator/bandwidth_limit.go @@ -182,3 +182,7 @@ func buildBandwidthLimitResponseProto(cfg *ir.BandwidthLimitConfig) *bwlimitv3.B } return proto } + +func (*bandwidthLimit) patchVirtualHost(_ *routev3.VirtualHost, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/basicauth.go b/internal/xds/translator/basicauth.go index bbf4b3f511..25799c8af5 100644 --- a/internal/xds/translator/basicauth.go +++ b/internal/xds/translator/basicauth.go @@ -163,3 +163,7 @@ func basicAuthPerRouteConfig(basicAuth *ir.BasicAuth) *basicauthv3.BasicAuthPerR }, } } + +func (*basicAuth) patchVirtualHost(_ *routev3.VirtualHost, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/compressor.go b/internal/xds/translator/compressor.go index ddc35d6d6a..3f447d0bf5 100644 --- a/internal/xds/translator/compressor.go +++ b/internal/xds/translator/compressor.go @@ -195,3 +195,7 @@ func compressorPerRouteConfig() *compressorv3.CompressorPerRoute { }, } } + +func (*compressor) patchVirtualHost(_ *routev3.VirtualHost, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/cors.go b/internal/xds/translator/cors.go index 5aff0a80e9..4c41bead94 100644 --- a/internal/xds/translator/cors.go +++ b/internal/xds/translator/cors.go @@ -202,3 +202,7 @@ func hasWildcard(array []string) bool { func (c *cors) patchResources(*types.ResourceVersionTable, []*ir.HTTPRoute) error { return nil } + +func (*cors) patchVirtualHost(_ *routev3.VirtualHost, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/credentialInjector.go b/internal/xds/translator/credentialInjector.go index d647e7f1a4..d14ddef9a2 100644 --- a/internal/xds/translator/credentialInjector.go +++ b/internal/xds/translator/credentialInjector.go @@ -160,3 +160,7 @@ func (*credentialInjector) patchRoute(route *routev3.Route, irRoute *ir.HTTPRout } return nil } + +func (*credentialInjector) patchVirtualHost(_ *routev3.VirtualHost, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/custom_response.go b/internal/xds/translator/custom_response.go index 65977b6791..dec7020da7 100644 --- a/internal/xds/translator/custom_response.go +++ b/internal/xds/translator/custom_response.go @@ -524,3 +524,7 @@ func (c *customResponse) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute, } return nil } + +func (*customResponse) patchVirtualHost(_ *routev3.VirtualHost, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/dynamic_forward_proxy.go b/internal/xds/translator/dynamic_forward_proxy.go index fe598d19b3..fb419f9018 100644 --- a/internal/xds/translator/dynamic_forward_proxy.go +++ b/internal/xds/translator/dynamic_forward_proxy.go @@ -234,3 +234,7 @@ func buildDFPLoopbackRBACPerRoute(irRoute *ir.HTTPRoute) (*rbacv3.RBACPerRoute, }, }, nil } + +func (*dynamicForwardProxy) patchVirtualHost(_ *routev3.VirtualHost, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/dynamicmodule.go b/internal/xds/translator/dynamicmodule.go index c117d29054..1d4d56d4ed 100644 --- a/internal/xds/translator/dynamicmodule.go +++ b/internal/xds/translator/dynamicmodule.go @@ -210,3 +210,7 @@ func (*dynamicModule) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute, _ } return nil } + +func (*dynamicModule) patchVirtualHost(_ *routev3.VirtualHost, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/extauth.go b/internal/xds/translator/extauth.go index 2405ba505b..b9fbf3a1cd 100644 --- a/internal/xds/translator/extauth.go +++ b/internal/xds/translator/extauth.go @@ -329,3 +329,7 @@ func convertContextExtensions(irCtxExts []*ir.ContextExtention) map[string]strin return ctxExts } + +func (*extAuth) patchVirtualHost(_ *routev3.VirtualHost, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/extproc.go b/internal/xds/translator/extproc.go index b31df8c60f..c4182b24be 100644 --- a/internal/xds/translator/extproc.go +++ b/internal/xds/translator/extproc.go @@ -265,3 +265,7 @@ func translateExtProcBodyProcessingMode(mode *ir.ExtProcBodyProcessingMode) extp } return extprocv3.ProcessingMode_NONE } + +func (*extProc) patchVirtualHost(_ *routev3.VirtualHost, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/fault.go b/internal/xds/translator/fault.go index 142fd58ea1..83dd9c4f2d 100644 --- a/internal/xds/translator/fault.go +++ b/internal/xds/translator/fault.go @@ -174,3 +174,7 @@ func (*fault) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute, _ *ir.HTTP return nil } + +func (*fault) patchVirtualHost(_ *routev3.VirtualHost, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/geoip.go b/internal/xds/translator/geoip.go index fffdf7c887..71a9281f30 100644 --- a/internal/xds/translator/geoip.go +++ b/internal/xds/translator/geoip.go @@ -293,3 +293,7 @@ func (*geoip) patchRoute(*routev3.Route, *ir.HTTPRoute, *ir.HTTPListener) error func (*geoip) patchResources(*types.ResourceVersionTable, []*ir.HTTPRoute) error { return nil } + +func (*geoip) patchVirtualHost(_ *routev3.VirtualHost, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/header_mutation.go b/internal/xds/translator/header_mutation.go index 7281c3f645..ddc9d970b9 100644 --- a/internal/xds/translator/header_mutation.go +++ b/internal/xds/translator/header_mutation.go @@ -162,3 +162,7 @@ func buildHeaderMutationRules(addHeaders []ir.AddHeader, removeHeaders []string, return mutationRules } + +func (*headerMutation) patchVirtualHost(_ *routev3.VirtualHost, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/healthcheck.go b/internal/xds/translator/healthcheck.go index 6551aa61fa..3db72fc2a1 100644 --- a/internal/xds/translator/healthcheck.go +++ b/internal/xds/translator/healthcheck.go @@ -101,3 +101,7 @@ func (*healthCheck) patchResources(*types.ResourceVersionTable, []*ir.HTTPRoute) func (*healthCheck) patchRoute(_ *routev3.Route, _ *ir.HTTPRoute, _ *ir.HTTPListener) error { return nil } + +func (*healthCheck) patchVirtualHost(_ *routev3.VirtualHost, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/httpfilters.go b/internal/xds/translator/httpfilters.go index d365dcd866..60640be8c2 100644 --- a/internal/xds/translator/httpfilters.go +++ b/internal/xds/translator/httpfilters.go @@ -58,6 +58,11 @@ type httpFilter interface { // patchRoute patches the provide Route with a filter's Route level configuration. patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute, httpListener *ir.HTTPListener) error + // patchVirtualHost patches the provided VirtualHost with a filter's VirtualHost level configuration. + // Note: this method may be called multiple times for the same VirtualHost when multiple IR listeners + // share the same RouteConfiguration (cleartext listeners on the same port). + patchVirtualHost(vh *routev3.VirtualHost, httpListener *ir.HTTPListener) error + // patchResources adds all the other needed resources referenced by this // filter to the resource version table. // for example: @@ -124,7 +129,12 @@ func newOrderedHTTPFilter(filter *hcmv3.HttpFilter) *OrderedHTTPFilter { case isFilterType(filter, egv1a1.EnvoyFilterBuffer): order = 11 case isFilterType(filter, egv1a1.EnvoyFilterLua): - order = 12 + mustGetFilterIndex(filter.Name) + if strings.Contains(filter.Name, "/listener/") { + // Listener-level Lua runs before route-level Lua (12+idx vs 62+idx). + order = 12 + mustGetFilterIndex(filter.Name) + } else { + order = 62 + mustGetFilterIndex(filter.Name) + } case isFilterType(filter, egv1a1.EnvoyFilterExtProc): order = 100 + mustGetFilterIndex(filter.Name) case isFilterType(filter, egv1a1.EnvoyFilterWasm): @@ -339,6 +349,16 @@ func patchRouteWithPerRouteConfig(route *routev3.Route, irRoute *ir.HTTPRoute, h return nil } +// patchVirtualHost calls each filter's patchVirtualHost to apply VirtualHost-level configuration. +func patchVirtualHost(vh *routev3.VirtualHost, httpListener *ir.HTTPListener) error { + for _, filter := range httpFilters { + if err := filter.patchVirtualHost(vh, httpListener); err != nil { + return err + } + } + return nil +} + // isFilterType returns true if the filter is the provided filter type. func isFilterType(filter *hcmv3.HttpFilter, filterType egv1a1.EnvoyFilter) bool { // Multiple filters of the same types are added to the HCM filter chain, one for each diff --git a/internal/xds/translator/jwt.go b/internal/xds/translator/jwt.go index dbfea8e021..13d3cdf674 100644 --- a/internal/xds/translator/jwt.go +++ b/internal/xds/translator/jwt.go @@ -389,3 +389,7 @@ func buildJwtFromHeaders(headers []egv1a1.JWTHeaderExtractor) []*jwtauthnv3.JwtH return jwtHeaders } + +func (*jwt) patchVirtualHost(_ *routev3.VirtualHost, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/listener.go b/internal/xds/translator/listener.go index 78d82ccdb7..94e31f4bcc 100644 --- a/internal/xds/translator/listener.go +++ b/internal/xds/translator/listener.go @@ -1331,3 +1331,5 @@ func buildRequestIDExtension(requestID *ir.RequestIDExtensionAction) *hcmv3.Requ TypedConfig: requestIDConfig, } } + +const FilterChainFilterNamePrefixForEEP = "envoy.filters.http.filter_chain.eep." diff --git a/internal/xds/translator/local_ratelimit.go b/internal/xds/translator/local_ratelimit.go index f527740123..62391c303e 100644 --- a/internal/xds/translator/local_ratelimit.go +++ b/internal/xds/translator/local_ratelimit.go @@ -483,3 +483,7 @@ func buildQueryParamMatchLocalRateLimitActions( *descriptorEntries = append(*descriptorEntries, entry) } } + +func (*localRateLimit) patchVirtualHost(_ *routev3.VirtualHost, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/lua.go b/internal/xds/translator/lua.go index 34c273c20b..d6ad511dfd 100644 --- a/internal/xds/translator/lua.go +++ b/internal/xds/translator/lua.go @@ -12,11 +12,13 @@ import ( corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" routev3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" + filterchainv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/filter_chain/v3" luafilterv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/lua/v3" hcmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/structpb" + "k8s.io/utils/ptr" egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" "github.com/envoyproxy/gateway/internal/ir" @@ -55,35 +57,85 @@ func (*lua) patchHCM(mgr *hcmv3.HttpConnectionManager, irListener *ir.HTTPListen return errors.New("ir listener is nil") } - maxLuaCount := 0 + hasListenerLua := irListener.EnvoyExtensions != nil && len(irListener.EnvoyExtensions.Luas) > 0 + maxRouteLuaCount := 0 for _, route := range irListener.Routes { if !routeContainsLua(route) { continue } - if count := len(route.EnvoyExtensions.Luas); count > maxLuaCount { - maxLuaCount = count - } + maxRouteLuaCount = max(len(route.EnvoyExtensions.Luas), maxRouteLuaCount) + } + if maxRouteLuaCount == 0 && !hasListenerLua { + // There's no Lua enabled, we didn't add place holder for per route lua filters + return nil } var errs error - for idx := range maxLuaCount { - filterName := luaFilterName(idx) - if hcmContainsFilter(mgr, filterName) { - continue - } - filter, err := buildHCMLuaFilter(filterName) - if err != nil { - errs = errors.Join(errs, err) - continue + // add place holder http filter for per listener lua filter + if irListener.EnvoyExtensions != nil { + for i := range irListener.EnvoyExtensions.Luas { + filterName := luaListenerFilterName(i) + if hcmContainsFilter(mgr, filterName) { + continue + } + luaFilter, err := buildHCMLuaFilter(filterName, false) + if err != nil { + errs = errors.Join(errs, err) + continue + } + + mgr.HttpFilters = append(mgr.HttpFilters, luaFilter) } - mgr.HttpFilters = append(mgr.HttpFilters, filter) } + + // add place holder filters for route Lua — disabled by default so they only execute + // on routes that explicitly enable them via LuaPerRoute in TypedPerFilterConfig. + filterName := luaFCFilterName() + if hcmContainsFilter(mgr, filterName) { + return nil + } + filter, err := buildHCMFilterChainFilter(filterName) + if err != nil { + return err + } + mgr.HttpFilters = append(mgr.HttpFilters, filter) + return errs } -// buildHCMLuaFilter returns a disabled Lua filter for HCM with empty default source code. -// The actual Lua script for each route is provided via LuaPerRoute in the route's TypedPerFilterConfig. -func buildHCMLuaFilter(filterName string) (*hcmv3.HttpFilter, error) { +func luaFCFilterName() string { + return FilterChainFilterNamePrefixForEEP + "lua" +} + +func buildHCMFilterChainFilter(filterName string) (*hcmv3.HttpFilter, error) { + var ( + fcProto *filterchainv3.FilterChainConfig + fcAny *anypb.Any + err error + ) + fcProto = &filterchainv3.FilterChainConfig{} + + if err = fcProto.ValidateAll(); err != nil { + return nil, err + } + if fcAny, err = anypb.New(fcProto); err != nil { + return nil, err + } + + return &hcmv3.HttpFilter{ + Name: filterName, + Disabled: true, + ConfigType: &hcmv3.HttpFilter_TypedConfig{ + TypedConfig: fcAny, + }, + }, nil +} + +// buildHCMLuaFilter builds a Lua HTTP filter for the HCM filter chain. +// Pass disabled=true for route-level slots so they only run on routes that supply +// a LuaPerRoute override; pass disabled=false for listener-level slots that run +// on every route by default and are disabled per-route where needed. +func buildHCMLuaFilter(filterName string, disabled bool) (*hcmv3.HttpFilter, error) { var ( luaProto *luafilterv3.Lua luaAny *anypb.Any @@ -91,9 +143,7 @@ func buildHCMLuaFilter(filterName string) (*hcmv3.HttpFilter, error) { ) luaProto = &luafilterv3.Lua{ DefaultSourceCode: &corev3.DataSource{ - Specifier: &corev3.DataSource_InlineString{ - InlineString: "", - }, + Specifier: &corev3.DataSource_InlineString{}, }, } if err = luaProto.ValidateAll(); err != nil { @@ -105,20 +155,27 @@ func buildHCMLuaFilter(filterName string) (*hcmv3.HttpFilter, error) { return &hcmv3.HttpFilter{ Name: filterName, - Disabled: true, + Disabled: disabled, ConfigType: &hcmv3.HttpFilter_TypedConfig{ TypedConfig: luaAny, }, }, nil } -// luaFilterName returns the stable top-level filter name for the Lua list index. +// luaFilterName returns the stable top-level filter name for the per-route Lua slot index. // The index is the execution slot within the ordered EnvoyExtensionPolicy Lua // list, so route 0th scripts always bind to the same listener-level filter. func luaFilterName(idx int) string { return perRouteFilterName(egv1a1.EnvoyFilterLua, strconv.Itoa(idx)) } +// luaListenerFilterName returns the stable HCM-level filter name for a listener-level +// Lua slot. Using the envoy.filters.http.lua prefix (instead of the raw policy name) +// ensures sortHTTPFilters assigns it the correct order relative to route-level slots. +func luaListenerFilterName(idx int) string { + return fmt.Sprintf("%s/listener/%d", egv1a1.EnvoyFilterLua, idx) +} + // routeContainsLua returns true if the route has any Lua extensions. func routeContainsLua(irRoute *ir.HTTPRoute) bool { if irRoute == nil { @@ -133,7 +190,10 @@ func (*lua) patchResources(_ *types.ResourceVersionTable, _ []*ir.HTTPRoute) err } // patchRoute patches the provided route with LuaPerRoute so the Lua filter runs with the route's script. -func (*lua) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute, _ *ir.HTTPListener) error { +// Routes with no Lua entries fall back to the listener-level Lua inherited from the virtual host. +// Only routes with their own Lua entries disable the inherited listener-level Lua and install +// their own scripts in its place. +func (*lua) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute, irListener *ir.HTTPListener) error { if route == nil { return errors.New("xds route is nil") } @@ -144,8 +204,103 @@ func (*lua) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute, _ *ir.HTTPLi return nil } + // Disable the inherited listener-level Lua whenever EnvoyExtensions was set by a + // more-specific route policy (FromGatewayPolicy is false/nil). The extension count + // is intentionally not checked here: an empty result (e.g. fail-open invalid Wasm) + // still represents a more-specific policy that owns this route and must suppress the + // lower-scope Lua. When FromGatewayPolicy is true the route-level extensions come + // from the same gateway/listener policy that also installed the listener Lua, so both coexist. + disableListenerLevelFilter := !ptr.Deref(irRoute.EnvoyExtensions.FromGatewayPolicy, false) + + // Route has its own Lua entries — disable the inherited listener-level Lua and + // install the route's scripts instead. + if irListener != nil && irListener.EnvoyExtensions != nil && disableListenerLevelFilter { + for i := range irListener.EnvoyExtensions.Luas { + luaPerRoute := &luafilterv3.LuaPerRoute{ + Override: &luafilterv3.LuaPerRoute_Disabled{ + Disabled: true, + }, + } + if err := enableFilterOnRoute(route, luaListenerFilterName(i), luaPerRoute); err != nil { + return err + } + } + } + + if route.TypedPerFilterConfig == nil { + route.TypedPerFilterConfig = make(map[string]*anypb.Any) + } + filterChainConfigPerRoute := &filterchainv3.FilterChainConfigPerRoute{ + FilterChain: &filterchainv3.FilterChain{}, + } for idx, ep := range irRoute.EnvoyExtensions.Luas { filterName := luaFilterName(idx) + luaOnFCFilter := &luafilterv3.Lua{ + DefaultSourceCode: &corev3.DataSource{ + Specifier: &corev3.DataSource_InlineString{ + InlineString: *ep.Code, + }, + }, + } + + // TODO: support filterContext in Lua filter make this simpler + if ep.FilterContext != nil && ep.FilterContext.Raw != nil { + luaPerRoute := &luafilterv3.LuaPerRoute{} + filterCtx := &structpb.Struct{} + if err := protojson.Unmarshal(ep.FilterContext.Raw, filterCtx); err != nil { + return err + } + luaPerRoute.FilterContext = filterCtx + luaPerRouteAny, err := anypb.New(luaPerRoute) + if err != nil { + return err + } + route.TypedPerFilterConfig[filterName] = luaPerRouteAny + } + luaOnFCFilterAny, err := anypb.New(luaOnFCFilter) + if err != nil { + return err + } + filterChainConfigPerRoute.FilterChain.Filters = append(filterChainConfigPerRoute.FilterChain.Filters, + &corev3.TypedExtensionConfig{ + Name: filterName, + TypedConfig: luaOnFCFilterAny, + }, + ) + } + + if len(filterChainConfigPerRoute.FilterChain.Filters) == 0 { + return nil + } + fcAny, err := anypb.New(filterChainConfigPerRoute) + if err != nil { + return err + } + + route.TypedPerFilterConfig[luaFCFilterName()] = fcAny + return nil +} + +// patchVirtualHost delivers listener-level Lua source at VirtualHost scope so that a listener's +// Lua policy does not bleed into virtual hosts belonging to a different listener that shares the +// same RouteConfiguration (cleartext listeners on the same port). Delivery via VirtualHost +// TypedPerFilterConfig still goes through RDS, so Lua script changes do not trigger listener drains. +func (*lua) patchVirtualHost(vh *routev3.VirtualHost, httpListener *ir.HTTPListener) error { + if httpListener.EnvoyExtensions == nil { + return nil + } + + if vh.TypedPerFilterConfig == nil { + vh.TypedPerFilterConfig = map[string]*anypb.Any{} + } + + var errs error + for i, ep := range httpListener.EnvoyExtensions.Luas { + filterName := luaListenerFilterName(i) + if vh.TypedPerFilterConfig[filterName] != nil { + continue + } + luaPerRoute := &luafilterv3.LuaPerRoute{ Override: &luafilterv3.LuaPerRoute_SourceCode{ SourceCode: &corev3.DataSource{ @@ -164,15 +319,12 @@ func (*lua) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute, _ *ir.HTTPLi } luaPerRouteAny, err := anypb.New(luaPerRoute) if err != nil { - return err - } - if route.TypedPerFilterConfig == nil { - route.TypedPerFilterConfig = make(map[string]*anypb.Any) - } - if _, exists := route.TypedPerFilterConfig[filterName]; exists { - return fmt.Errorf("route already has Lua per-route config for %s", filterName) + errs = errors.Join(errs, err) + continue } - route.TypedPerFilterConfig[filterName] = luaPerRouteAny + + vh.TypedPerFilterConfig[filterName] = luaPerRouteAny } - return nil + + return errs } diff --git a/internal/xds/translator/oidc.go b/internal/xds/translator/oidc.go index fb4bca8ee9..98b1a6ecf7 100644 --- a/internal/xds/translator/oidc.go +++ b/internal/xds/translator/oidc.go @@ -587,3 +587,7 @@ func (*oidc) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute, _ *ir.HTTPL route.TypedPerFilterConfig[string(egv1a1.EnvoyFilterOAuth2)] = oauth2Any return nil } + +func (*oidc) patchVirtualHost(_ *routev3.VirtualHost, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/request_buffer.go b/internal/xds/translator/request_buffer.go index 231506e34b..ec7b4e321d 100644 --- a/internal/xds/translator/request_buffer.go +++ b/internal/xds/translator/request_buffer.go @@ -145,3 +145,7 @@ func buildRequestBufferPerRouteProto(spec *ir.RequestBuffer) (*anypb.Any, error) func routeContainsRequestBuffer(route *ir.HTTPRoute) bool { return route.Traffic != nil && route.Traffic.RequestBuffer != nil } + +func (*requestBuffer) patchVirtualHost(_ *routev3.VirtualHost, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/session_persistence.go b/internal/xds/translator/session_persistence.go index 114672a7cd..7d202cad59 100644 --- a/internal/xds/translator/session_persistence.go +++ b/internal/xds/translator/session_persistence.go @@ -232,3 +232,7 @@ func (s *sessionPersistence) patchRoute(route *routev3.Route, irRoute *ir.HTTPRo func (s *sessionPersistence) patchResources(_ *types.ResourceVersionTable, _ []*ir.HTTPRoute) error { return nil } + +func (*sessionPersistence) patchVirtualHost(_ *routev3.VirtualHost, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/testdata/in/xds-ir/lua.yaml b/internal/xds/translator/testdata/in/xds-ir/lua.yaml index fddfa9a4a7..8fb2404882 100644 --- a/internal/xds/translator/testdata/in/xds-ir/lua.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/lua.yaml @@ -1,4 +1,5 @@ http: +# Lua filter per route only - address: 0.0.0.0 hostnames: - '*' @@ -61,3 +62,297 @@ http: response_handle:logError('Hello.') end name: envoyextensionpolicy/envoy-gateway/policy-for-gateway/lua/1 +# Lua filter per route +- address: 0.0.0.0 + hostnames: + - '*' + name: envoy-gateway/gateway-1/http-2 + envoyExtensions: + luas: + - code: function envoy_on_request(request_handle) + request_handle:logInfo('Goodbye from gateway.') + end + name: envoyextensionpolicy/default/policy-for-gateway/lua/0 + filterContext: + token_header: x-api-key + mode: strict + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10081 + routes: + - destination: + name: httproute/default/httproute-2/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + name: httproute/default/httproute-3/rule/0/backend/0 + hostname: www.example.com + isHTTP2: false + name: httproute/default/httproute-3/rule/0/match/0/www_example_com + pathMatch: + distinct: false + name: "" + prefix: /foo + envoyExtensions: + luas: + - code: function envoy_on_request(request_handle) + request_handle:logInfo('Goodbye.') + end + name: envoyextensionpolicy/default/policy-for-http-route/lua/0 + filterContext: + token_header: x-api-key + mode: strict +# Lua filter per listener +- address: 0.0.0.0 + hostnames: + - '*' + name: envoy-gateway/gateway-1/http-3 + envoyExtensions: + luas: + - code: function envoy_on_request(request_handle) + request_handle:logInfo('Goodbye from gateway.') + end + name: envoyextensionpolicy/default/policy-for-gateway/lua/0 + filterContext: + token_header: x-api-key + mode: strict + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10082 + routes: + - destination: + name: httproute/default/httproute-3/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + name: httproute/default/httproute-3/rule/0/backend/0 + hostname: www.example.com + isHTTP2: false + name: httproute/default/httproute-3/rule/0/match/0/www_example_com + pathMatch: + distinct: false + name: "" + prefix: /foo +# Listener Lua with an ExtProc-only route override (EnvoyExtensions non-nil, Luas empty). +# The ExtProc route must disable the inherited listener Lua even though it has no route-level Lua. +- address: 0.0.0.0 + hostnames: + - '*' + name: envoy-gateway/gateway-1/http-4 + envoyExtensions: + luas: + - code: function envoy_on_request(request_handle) + request_handle:logInfo('Goodbye from gateway.') + end + name: envoyextensionpolicy/default/policy-for-gateway/lua/0 + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10083 + routes: + # This route has an ExtProc-only EnvoyExtensionPolicy (Luas is empty). + # Inherited listener Lua must be disabled so the more-specific policy fully governs this route. + - destination: + name: httproute/default/httproute-4/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + name: httproute/default/httproute-4/rule/0/backend/0 + hostname: www.example.com + isHTTP2: false + name: httproute/default/httproute-4/rule/0/match/0/www_example_com + pathMatch: + distinct: false + name: "" + prefix: /foo + envoyExtensions: + extProcs: + - name: envoyextensionpolicy/default/policy-for-http-route/extproc/0 + authority: grpc-backend.default:8000 + destination: + name: envoyextensionpolicy/default/policy-for-http-route/0/grpc-backend + settings: + - protocol: GRPC + weight: 1 + name: envoyextensionpolicy/default/policy-for-http-route/0/grpc-backend/backend/0 + # This route has no EnvoyExtensionPolicy — it inherits the listener Lua normally. + - destination: + name: httproute/default/httproute-5/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + name: httproute/default/httproute-5/rule/0/backend/0 + hostname: www.example.com + isHTTP2: false + name: httproute/default/httproute-5/rule/0/match/0/www_example_com + pathMatch: + distinct: false + name: "" + prefix: /bar +# Two cleartext listeners sharing the same port — shared RouteConfiguration. +# Each listener's Lua script must be scoped to its own virtual hosts only. +# Listener A +- address: 0.0.0.0 + hostnames: + - 'app-a.example.com' + name: envoy-gateway/gateway-1/http-5 + envoyExtensions: + luas: + - code: function envoy_on_request(request_handle) + request_handle:logInfo('tenant-A script.') + end + name: envoyextensionpolicy/default/policy-for-gateway-a/lua/0 + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10084 + routes: + - destination: + name: httproute/default/httproute-6/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + name: httproute/default/httproute-6/rule/0/backend/0 + hostname: app-a.example.com + isHTTP2: false + name: httproute/default/httproute-6/rule/0/match/0/app-a_example_com + pathMatch: + distinct: false + name: "" + prefix: / +# Listener B — same port 10084, different hostname +- address: 0.0.0.0 + hostnames: + - 'app-b.example.com' + name: envoy-gateway/gateway-1/http-6 + envoyExtensions: + luas: + - code: function envoy_on_request(request_handle) + request_handle:logInfo('tenant-B script.') + end + name: envoyextensionpolicy/default/policy-for-gateway-b/lua/0 + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10084 + routes: + - destination: + name: httproute/default/httproute-7/rule/0 + settings: + - addressType: IP + endpoints: + - host: 8.8.8.8 + port: 8080 + protocol: HTTP + weight: 1 + name: httproute/default/httproute-7/rule/0/backend/0 + hostname: app-b.example.com + isHTTP2: false + name: httproute/default/httproute-7/rule/0/match/0/app-b_example_com + pathMatch: + distinct: false + name: "" + prefix: / +# Gateway policy with Lua (at listener) + ExtProc (at route), both from the same policy. +# The route's ExtProc carries fromGatewayPolicy:true so the listener Lua must NOT be disabled. +- address: 0.0.0.0 + hostnames: + - '*' + name: envoy-gateway/gateway-1/http-7 + envoyExtensions: + luas: + - code: function envoy_on_request(request_handle) + request_handle:logInfo('Gateway Lua.') + end + name: envoyextensionpolicy/default/policy-for-gateway/lua/0 + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10085 + routes: + - destination: + name: httproute/default/httproute-8/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + name: httproute/default/httproute-8/rule/0/backend/0 + hostname: www.example.com + isHTTP2: false + name: httproute/default/httproute-8/rule/0/match/0/www_example_com + pathMatch: + distinct: false + name: "" + prefix: /foo + envoyExtensions: + fromGatewayPolicy: true + extProcs: + - name: envoyextensionpolicy/default/policy-for-gateway/extproc/0 + authority: grpc-backend.default:8000 + destination: + name: envoyextensionpolicy/default/policy-for-gateway/0/grpc-backend + settings: + - protocol: GRPC + weight: 1 + name: envoyextensionpolicy/default/policy-for-gateway/0/grpc-backend/backend/0 +# Listener Lua with a route that has a fail-open empty policy (EnvoyExtensions non-nil but all +# extension slices empty and no fromGatewayPolicy). The listener Lua must still be disabled +# because the more-specific route policy owns this route regardless of producing no extensions. +- address: 0.0.0.0 + hostnames: + - '*' + name: envoy-gateway/gateway-1/http-8 + envoyExtensions: + luas: + - code: function envoy_on_request(request_handle) + request_handle:logInfo('Gateway Lua.') + end + name: envoyextensionpolicy/default/policy-for-gateway/lua/0 + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10086 + routes: + - destination: + name: httproute/default/httproute-9/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + name: httproute/default/httproute-9/rule/0/backend/0 + hostname: www.example.com + isHTTP2: false + name: httproute/default/httproute-9/rule/0/match/0/www_example_com + pathMatch: + distinct: false + name: "" + prefix: /foo + envoyExtensions: {} diff --git a/internal/xds/translator/testdata/out/xds-ir/lua.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/lua.clusters.yaml index d1b786402b..376d420242 100644 --- a/internal/xds/translator/testdata/out/xds-ir/lua.clusters.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/lua.clusters.yaml @@ -44,3 +44,224 @@ name: httproute/default/httproute-2/rule/0 perConnectionBufferLimitBytes: 32768 type: EDS +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_PREFERRED + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: httproute/default/httproute-3/rule/0 + ignoreHealthOnHostRemoval: true + loadBalancingPolicy: + policies: + - typedExtensionConfig: + name: envoy.load_balancing_policies.least_request + typedConfig: + '@type': type.googleapis.com/envoy.extensions.load_balancing_policies.least_request.v3.LeastRequest + localityLbConfig: + localityWeightedLbConfig: {} + name: httproute/default/httproute-3/rule/0 + perConnectionBufferLimitBytes: 32768 + type: EDS +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_PREFERRED + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: httproute/default/httproute-4/rule/0 + ignoreHealthOnHostRemoval: true + loadBalancingPolicy: + policies: + - typedExtensionConfig: + name: envoy.load_balancing_policies.least_request + typedConfig: + '@type': type.googleapis.com/envoy.extensions.load_balancing_policies.least_request.v3.LeastRequest + localityLbConfig: + localityWeightedLbConfig: {} + name: httproute/default/httproute-4/rule/0 + perConnectionBufferLimitBytes: 32768 + type: EDS +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_PREFERRED + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: httproute/default/httproute-5/rule/0 + ignoreHealthOnHostRemoval: true + loadBalancingPolicy: + policies: + - typedExtensionConfig: + name: envoy.load_balancing_policies.least_request + typedConfig: + '@type': type.googleapis.com/envoy.extensions.load_balancing_policies.least_request.v3.LeastRequest + localityLbConfig: + localityWeightedLbConfig: {} + name: httproute/default/httproute-5/rule/0 + perConnectionBufferLimitBytes: 32768 + type: EDS +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_PREFERRED + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: envoyextensionpolicy/default/policy-for-http-route/0/grpc-backend + ignoreHealthOnHostRemoval: true + loadBalancingPolicy: + policies: + - typedExtensionConfig: + name: envoy.load_balancing_policies.least_request + typedConfig: + '@type': type.googleapis.com/envoy.extensions.load_balancing_policies.least_request.v3.LeastRequest + localityLbConfig: + localityWeightedLbConfig: {} + name: envoyextensionpolicy/default/policy-for-http-route/0/grpc-backend + perConnectionBufferLimitBytes: 32768 + type: EDS + typedExtensionProtocolOptions: + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions + explicitHttpConfig: + http2ProtocolOptions: + initialConnectionWindowSize: 1048576 + initialStreamWindowSize: 65536 +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_PREFERRED + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: httproute/default/httproute-6/rule/0 + ignoreHealthOnHostRemoval: true + loadBalancingPolicy: + policies: + - typedExtensionConfig: + name: envoy.load_balancing_policies.least_request + typedConfig: + '@type': type.googleapis.com/envoy.extensions.load_balancing_policies.least_request.v3.LeastRequest + localityLbConfig: + localityWeightedLbConfig: {} + name: httproute/default/httproute-6/rule/0 + perConnectionBufferLimitBytes: 32768 + type: EDS +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_PREFERRED + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: httproute/default/httproute-7/rule/0 + ignoreHealthOnHostRemoval: true + loadBalancingPolicy: + policies: + - typedExtensionConfig: + name: envoy.load_balancing_policies.least_request + typedConfig: + '@type': type.googleapis.com/envoy.extensions.load_balancing_policies.least_request.v3.LeastRequest + localityLbConfig: + localityWeightedLbConfig: {} + name: httproute/default/httproute-7/rule/0 + perConnectionBufferLimitBytes: 32768 + type: EDS +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_PREFERRED + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: httproute/default/httproute-8/rule/0 + ignoreHealthOnHostRemoval: true + loadBalancingPolicy: + policies: + - typedExtensionConfig: + name: envoy.load_balancing_policies.least_request + typedConfig: + '@type': type.googleapis.com/envoy.extensions.load_balancing_policies.least_request.v3.LeastRequest + localityLbConfig: + localityWeightedLbConfig: {} + name: httproute/default/httproute-8/rule/0 + perConnectionBufferLimitBytes: 32768 + type: EDS +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_PREFERRED + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: envoyextensionpolicy/default/policy-for-gateway/0/grpc-backend + ignoreHealthOnHostRemoval: true + loadBalancingPolicy: + policies: + - typedExtensionConfig: + name: envoy.load_balancing_policies.least_request + typedConfig: + '@type': type.googleapis.com/envoy.extensions.load_balancing_policies.least_request.v3.LeastRequest + localityLbConfig: + localityWeightedLbConfig: {} + name: envoyextensionpolicy/default/policy-for-gateway/0/grpc-backend + perConnectionBufferLimitBytes: 32768 + type: EDS + typedExtensionProtocolOptions: + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions + explicitHttpConfig: + http2ProtocolOptions: + initialConnectionWindowSize: 1048576 + initialStreamWindowSize: 65536 +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_PREFERRED + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: httproute/default/httproute-9/rule/0 + ignoreHealthOnHostRemoval: true + loadBalancingPolicy: + policies: + - typedExtensionConfig: + name: envoy.load_balancing_policies.least_request + typedConfig: + '@type': type.googleapis.com/envoy.extensions.load_balancing_policies.least_request.v3.LeastRequest + localityLbConfig: + localityWeightedLbConfig: {} + name: httproute/default/httproute-9/rule/0 + perConnectionBufferLimitBytes: 32768 + type: EDS diff --git a/internal/xds/translator/testdata/out/xds-ir/lua.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/lua.endpoints.yaml index 05442a9a15..11449dbc2a 100644 --- a/internal/xds/translator/testdata/out/xds-ir/lua.endpoints.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/lua.endpoints.yaml @@ -22,3 +22,97 @@ loadBalancingWeight: 1 locality: region: httproute/default/httproute-2/rule/0/backend/0 +- clusterName: httproute/default/httproute-3/rule/0 + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 7.7.7.7 + portValue: 8080 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: httproute/default/httproute-3/rule/0/backend/0 +- clusterName: httproute/default/httproute-4/rule/0 + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 7.7.7.7 + portValue: 8080 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: httproute/default/httproute-4/rule/0/backend/0 +- clusterName: httproute/default/httproute-5/rule/0 + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 7.7.7.7 + portValue: 8080 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: httproute/default/httproute-5/rule/0/backend/0 +- clusterName: envoyextensionpolicy/default/policy-for-http-route/0/grpc-backend + endpoints: + - loadBalancingWeight: 1 + locality: + region: envoyextensionpolicy/default/policy-for-http-route/0/grpc-backend/backend/0 +- clusterName: httproute/default/httproute-6/rule/0 + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 7.7.7.7 + portValue: 8080 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: httproute/default/httproute-6/rule/0/backend/0 +- clusterName: httproute/default/httproute-7/rule/0 + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 8.8.8.8 + portValue: 8080 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: httproute/default/httproute-7/rule/0/backend/0 +- clusterName: httproute/default/httproute-8/rule/0 + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 7.7.7.7 + portValue: 8080 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: httproute/default/httproute-8/rule/0/backend/0 +- clusterName: envoyextensionpolicy/default/policy-for-gateway/0/grpc-backend + endpoints: + - loadBalancingWeight: 1 + locality: + region: envoyextensionpolicy/default/policy-for-gateway/0/grpc-backend/backend/0 +- clusterName: httproute/default/httproute-9/rule/0 + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 7.7.7.7 + portValue: 8080 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: httproute/default/httproute-9/rule/0/backend/0 diff --git a/internal/xds/translator/testdata/out/xds-ir/lua.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/lua.listeners.yaml index ea11d2dd22..7b4a265b1c 100644 --- a/internal/xds/translator/testdata/out/xds-ir/lua.listeners.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/lua.listeners.yaml @@ -15,17 +15,155 @@ maxConcurrentStreams: 100 httpFilters: - disabled: true - name: envoy.filters.http.lua/0 + name: envoy.filters.http.filter_chain.eep.lua + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.filter_chain.v3.FilterChainConfig + - 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-10080 + useRemoteAddress: true + name: envoy-gateway/gateway-1/http + maxConnectionsToAcceptPerSocketEvent: 1 + name: envoy-gateway/gateway-1/http + perConnectionBufferLimitBytes: 32768 +- address: + socketAddress: + address: 0.0.0.0 + portValue: 10081 + 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.lua/listener/0 + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua + defaultSourceCode: + inlineString: "" + - disabled: true + name: envoy.filters.http.filter_chain.eep.lua + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.filter_chain.v3.FilterChainConfig + - 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-2 + serverHeaderTransformation: PASS_THROUGH + statPrefix: http-10081 + useRemoteAddress: true + name: envoy-gateway/gateway-1/http-2 + maxConnectionsToAcceptPerSocketEvent: 1 + name: envoy-gateway/gateway-1/http-2 + perConnectionBufferLimitBytes: 32768 +- address: + socketAddress: + address: 0.0.0.0 + portValue: 10082 + 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.lua/listener/0 typedConfig: '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua defaultSourceCode: inlineString: "" - disabled: true - name: envoy.filters.http.lua/1 + name: envoy.filters.http.filter_chain.eep.lua + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.filter_chain.v3.FilterChainConfig + - 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-3 + serverHeaderTransformation: PASS_THROUGH + statPrefix: http-10082 + useRemoteAddress: true + name: envoy-gateway/gateway-1/http-3 + maxConnectionsToAcceptPerSocketEvent: 1 + name: envoy-gateway/gateway-1/http-3 + perConnectionBufferLimitBytes: 32768 +- address: + socketAddress: + address: 0.0.0.0 + portValue: 10083 + 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.lua/listener/0 typedConfig: '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua defaultSourceCode: inlineString: "" + - disabled: true + name: envoy.filters.http.filter_chain.eep.lua + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.filter_chain.v3.FilterChainConfig + - disabled: true + name: envoy.filters.http.ext_proc/envoyextensionpolicy/default/policy-for-http-route/extproc/0 + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.ext_proc.v3.ExternalProcessor + grpcService: + envoyGrpc: + authority: grpc-backend.default:8000 + clusterName: envoyextensionpolicy/default/policy-for-http-route/0/grpc-backend + timeout: 10s + processingMode: + requestHeaderMode: SKIP + requestTrailerMode: SKIP + responseHeaderMode: SKIP + responseTrailerMode: SKIP - name: envoy.filters.http.router typedConfig: '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router @@ -37,11 +175,157 @@ configSource: ads: {} resourceApiVersion: V3 - routeConfigName: envoy-gateway/gateway-1/http + routeConfigName: envoy-gateway/gateway-1/http-4 serverHeaderTransformation: PASS_THROUGH - statPrefix: http-10080 + statPrefix: http-10083 useRemoteAddress: true - name: envoy-gateway/gateway-1/http + name: envoy-gateway/gateway-1/http-4 maxConnectionsToAcceptPerSocketEvent: 1 - name: envoy-gateway/gateway-1/http + name: envoy-gateway/gateway-1/http-4 + perConnectionBufferLimitBytes: 32768 +- address: + socketAddress: + address: 0.0.0.0 + portValue: 10084 + 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.lua/listener/0 + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua + defaultSourceCode: + inlineString: "" + - disabled: true + name: envoy.filters.http.filter_chain.eep.lua + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.filter_chain.v3.FilterChainConfig + - 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-5 + serverHeaderTransformation: PASS_THROUGH + statPrefix: http-10084 + useRemoteAddress: true + name: envoy-gateway/gateway-1/http-5 + maxConnectionsToAcceptPerSocketEvent: 1 + name: envoy-gateway/gateway-1/http-5 + perConnectionBufferLimitBytes: 32768 +- address: + socketAddress: + address: 0.0.0.0 + portValue: 10085 + 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.lua/listener/0 + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua + defaultSourceCode: + inlineString: "" + - disabled: true + name: envoy.filters.http.filter_chain.eep.lua + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.filter_chain.v3.FilterChainConfig + - disabled: true + name: envoy.filters.http.ext_proc/envoyextensionpolicy/default/policy-for-gateway/extproc/0 + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.ext_proc.v3.ExternalProcessor + grpcService: + envoyGrpc: + authority: grpc-backend.default:8000 + clusterName: envoyextensionpolicy/default/policy-for-gateway/0/grpc-backend + timeout: 10s + processingMode: + requestHeaderMode: SKIP + requestTrailerMode: SKIP + responseHeaderMode: SKIP + responseTrailerMode: SKIP + - 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-7 + serverHeaderTransformation: PASS_THROUGH + statPrefix: http-10085 + useRemoteAddress: true + name: envoy-gateway/gateway-1/http-7 + maxConnectionsToAcceptPerSocketEvent: 1 + name: envoy-gateway/gateway-1/http-7 + perConnectionBufferLimitBytes: 32768 +- address: + socketAddress: + address: 0.0.0.0 + portValue: 10086 + 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.lua/listener/0 + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua + defaultSourceCode: + inlineString: "" + - disabled: true + name: envoy.filters.http.filter_chain.eep.lua + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.filter_chain.v3.FilterChainConfig + - 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-8 + serverHeaderTransformation: PASS_THROUGH + statPrefix: http-10086 + useRemoteAddress: true + name: envoy-gateway/gateway-1/http-8 + maxConnectionsToAcceptPerSocketEvent: 1 + name: envoy-gateway/gateway-1/http-8 perConnectionBufferLimitBytes: 32768 diff --git a/internal/xds/translator/testdata/out/xds-ir/lua.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/lua.routes.yaml index 447e419e3d..95d3ed8ab7 100644 --- a/internal/xds/translator/testdata/out/xds-ir/lua.routes.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/lua.routes.yaml @@ -13,14 +13,21 @@ upgradeConfigs: - upgradeType: websocket typedPerFilterConfig: + envoy.filters.http.filter_chain.eep.lua: + '@type': type.googleapis.com/envoy.extensions.filters.http.filter_chain.v3.FilterChainConfigPerRoute + filterChain: + filters: + - name: envoy.filters.http.lua/0 + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua + defaultSourceCode: + inlineString: function envoy_on_request(request_handle) request_handle:logInfo('Goodbye.') + end envoy.filters.http.lua/0: '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute filterContext: mode: strict token_header: x-api-key - sourceCode: - inlineString: function envoy_on_request(request_handle) request_handle:logInfo('Goodbye.') - end - match: pathSeparatedPrefix: /bar name: httproute/default/httproute-2/rule/0/match/0/www_example_com @@ -29,13 +36,203 @@ upgradeConfigs: - upgradeType: websocket typedPerFilterConfig: + envoy.filters.http.filter_chain.eep.lua: + '@type': type.googleapis.com/envoy.extensions.filters.http.filter_chain.v3.FilterChainConfigPerRoute + filterChain: + filters: + - name: envoy.filters.http.lua/0 + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua + defaultSourceCode: + inlineString: function envoy_on_response(response_handle) response_handle:logWarn('Goodbye.') + end + - name: envoy.filters.http.lua/1 + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua + defaultSourceCode: + inlineString: function envoy_on_response(response_handle) response_handle:logError('Hello.') + end +- ignorePortInHostMatching: true + name: envoy-gateway/gateway-1/http-2 + virtualHosts: + - domains: + - www.example.com + name: envoy-gateway/gateway-1/http-2/www_example_com + routes: + - match: + pathSeparatedPrefix: /foo + name: httproute/default/httproute-3/rule/0/match/0/www_example_com + route: + cluster: httproute/default/httproute-2/rule/0 + upgradeConfigs: + - upgradeType: websocket + typedPerFilterConfig: + envoy.filters.http.filter_chain.eep.lua: + '@type': type.googleapis.com/envoy.extensions.filters.http.filter_chain.v3.FilterChainConfigPerRoute + filterChain: + filters: + - name: envoy.filters.http.lua/0 + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua + defaultSourceCode: + inlineString: function envoy_on_request(request_handle) request_handle:logInfo('Goodbye.') + end envoy.filters.http.lua/0: '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute - sourceCode: - inlineString: function envoy_on_response(response_handle) response_handle:logWarn('Goodbye.') - end - envoy.filters.http.lua/1: + filterContext: + mode: strict + token_header: x-api-key + envoy.filters.http.lua/listener/0: + '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute + disabled: true + typedPerFilterConfig: + envoy.filters.http.lua/listener/0: + '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute + filterContext: + mode: strict + token_header: x-api-key + sourceCode: + inlineString: function envoy_on_request(request_handle) request_handle:logInfo('Goodbye + from gateway.') end +- ignorePortInHostMatching: true + name: envoy-gateway/gateway-1/http-3 + virtualHosts: + - domains: + - www.example.com + name: envoy-gateway/gateway-1/http-3/www_example_com + routes: + - match: + pathSeparatedPrefix: /foo + name: httproute/default/httproute-3/rule/0/match/0/www_example_com + route: + cluster: httproute/default/httproute-3/rule/0 + upgradeConfigs: + - upgradeType: websocket + typedPerFilterConfig: + envoy.filters.http.lua/listener/0: + '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute + filterContext: + mode: strict + token_header: x-api-key + sourceCode: + inlineString: function envoy_on_request(request_handle) request_handle:logInfo('Goodbye + from gateway.') end +- ignorePortInHostMatching: true + name: envoy-gateway/gateway-1/http-4 + virtualHosts: + - domains: + - www.example.com + name: envoy-gateway/gateway-1/http-4/www_example_com + routes: + - match: + pathSeparatedPrefix: /foo + name: httproute/default/httproute-4/rule/0/match/0/www_example_com + route: + cluster: httproute/default/httproute-4/rule/0 + upgradeConfigs: + - upgradeType: websocket + typedPerFilterConfig: + envoy.filters.http.ext_proc/envoyextensionpolicy/default/policy-for-http-route/extproc/0: + '@type': type.googleapis.com/envoy.config.route.v3.FilterConfig + config: {} + envoy.filters.http.lua/listener/0: + '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute + disabled: true + - match: + pathSeparatedPrefix: /bar + name: httproute/default/httproute-5/rule/0/match/0/www_example_com + route: + cluster: httproute/default/httproute-5/rule/0 + upgradeConfigs: + - upgradeType: websocket + typedPerFilterConfig: + envoy.filters.http.lua/listener/0: + '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute + sourceCode: + inlineString: function envoy_on_request(request_handle) request_handle:logInfo('Goodbye + from gateway.') end +- ignorePortInHostMatching: true + name: envoy-gateway/gateway-1/http-5 + virtualHosts: + - domains: + - app-a.example.com + name: envoy-gateway/gateway-1/http-5/app-a_example_com + routes: + - match: + prefix: / + name: httproute/default/httproute-6/rule/0/match/0/app-a_example_com + route: + cluster: httproute/default/httproute-6/rule/0 + upgradeConfigs: + - upgradeType: websocket + typedPerFilterConfig: + envoy.filters.http.lua/listener/0: + '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute + sourceCode: + inlineString: function envoy_on_request(request_handle) request_handle:logInfo('tenant-A + script.') end + - domains: + - app-b.example.com + name: envoy-gateway/gateway-1/http-6/app-b_example_com + routes: + - match: + prefix: / + name: httproute/default/httproute-7/rule/0/match/0/app-b_example_com + route: + cluster: httproute/default/httproute-7/rule/0 + upgradeConfigs: + - upgradeType: websocket + typedPerFilterConfig: + envoy.filters.http.lua/listener/0: + '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute + sourceCode: + inlineString: function envoy_on_request(request_handle) request_handle:logInfo('tenant-B + script.') end +- ignorePortInHostMatching: true + name: envoy-gateway/gateway-1/http-7 + virtualHosts: + - domains: + - www.example.com + name: envoy-gateway/gateway-1/http-7/www_example_com + routes: + - match: + pathSeparatedPrefix: /foo + name: httproute/default/httproute-8/rule/0/match/0/www_example_com + route: + cluster: httproute/default/httproute-8/rule/0 + upgradeConfigs: + - upgradeType: websocket + typedPerFilterConfig: + envoy.filters.http.ext_proc/envoyextensionpolicy/default/policy-for-gateway/extproc/0: + '@type': type.googleapis.com/envoy.config.route.v3.FilterConfig + config: {} + typedPerFilterConfig: + envoy.filters.http.lua/listener/0: + '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute + sourceCode: + inlineString: function envoy_on_request(request_handle) request_handle:logInfo('Gateway + Lua.') end +- ignorePortInHostMatching: true + name: envoy-gateway/gateway-1/http-8 + virtualHosts: + - domains: + - www.example.com + name: envoy-gateway/gateway-1/http-8/www_example_com + routes: + - match: + pathSeparatedPrefix: /foo + name: httproute/default/httproute-9/rule/0/match/0/www_example_com + route: + cluster: httproute/default/httproute-9/rule/0 + upgradeConfigs: + - upgradeType: websocket + typedPerFilterConfig: + envoy.filters.http.lua/listener/0: '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute - sourceCode: - inlineString: function envoy_on_response(response_handle) response_handle:logError('Hello.') - end + disabled: true + typedPerFilterConfig: + envoy.filters.http.lua/listener/0: + '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute + sourceCode: + inlineString: function envoy_on_request(request_handle) request_handle:logInfo('Gateway + Lua.') end diff --git a/internal/xds/translator/translator.go b/internal/xds/translator/translator.go index ec4272e49d..183899d02e 100644 --- a/internal/xds/translator/translator.go +++ b/internal/xds/translator/translator.go @@ -659,6 +659,10 @@ func (t *Translator) addRouteToRouteConfig( } for _, vHost := range vHostList { + if err = patchVirtualHost(vHost, httpListener); err != nil { + errs = errors.Join(errs, err) + } + // Check if an extension want to modify the Virtual Host we just generated // If no extension exists (or it doesn't subscribe to this hook) then this is a quick no-op. if err = processExtensionPostVHostHook(vHost, t.ExtensionManager); err != nil { diff --git a/internal/xds/translator/wasm.go b/internal/xds/translator/wasm.go index 0bbbcddd73..68a99c8f46 100644 --- a/internal/xds/translator/wasm.go +++ b/internal/xds/translator/wasm.go @@ -197,3 +197,7 @@ func (*wasm) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute, _ *ir.HTTPL } return nil } + +func (*wasm) patchVirtualHost(_ *routev3.VirtualHost, _ *ir.HTTPListener) error { + return nil +} diff --git a/release-notes/current/breaking_changes/9131-lua-xds-layout-per-route-filterchain.md b/release-notes/current/breaking_changes/9131-lua-xds-layout-per-route-filterchain.md new file mode 100644 index 0000000000..689dfdbfba --- /dev/null +++ b/release-notes/current/breaking_changes/9131-lua-xds-layout-per-route-filterchain.md @@ -0,0 +1 @@ +The xDS layout for Gateway/listener-targeted Lua EnvoyExtensionPolicies has changed: the Lua script is now delivered through a named HCM-level filter plus VirtualHost `TypedPerFilterConfig`, rather than being embedded directly in each route's `TypedPerFilterConfig`. Route-level Lua filter slots are now installed as disabled-by-default in the HCM filter chain and activated per-route. EnvoyPatchPolicy rules or extension-server logic that reads or patches the old per-route Lua config shape must be updated to target the new filter names (`envoy.filters.http.lua/listener/`) and VirtualHost-scoped overrides. diff --git a/test/e2e/testdata/lua-http.yaml b/test/e2e/testdata/lua-http.yaml index af509b24a4..c93669c4a1 100644 --- a/test/e2e/testdata/lua-http.yaml +++ b/test/e2e/testdata/lua-http.yaml @@ -7,6 +7,7 @@ metadata: spec: parentRefs: - name: same-namespace + - name: all-namespaces hostnames: ["www.example.com"] rules: - matches: @@ -43,6 +44,7 @@ metadata: spec: parentRefs: - name: same-namespace + - name: all-namespaces hostnames: ["www.example.com"] rules: - matches: @@ -101,6 +103,23 @@ data: --- apiVersion: gateway.envoyproxy.io/v1alpha1 kind: EnvoyExtensionPolicy +metadata: + name: lua-for-gateway + namespace: gateway-conformance-infra +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: same-namespace + lua: + - type: Inline + inline: | + function envoy_on_response(response_handle) + response_handle:headers():add("X-Custom-Response-Header", "gateway") + end +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: EnvoyExtensionPolicy metadata: name: example-lua-1 namespace: gateway-conformance-infra diff --git a/test/e2e/tests/lua_http.go b/test/e2e/tests/lua_http.go index e2dc561ea4..066fe3355f 100644 --- a/test/e2e/tests/lua_http.go +++ b/test/e2e/tests/lua_http.go @@ -55,20 +55,15 @@ var HTTPLuaTest = suite.ConformanceTest{ Headers: map[string]string{ "X-Custom-Lua-Header": "lua_value_1", }, - AbsentHeaders: []string{"X-Custom-Lua-Another-Header"}, + AbsentHeaders: []string{ + "X-Custom-Response-Header", // gateway policy never took effect + "X-Custom-Lua-Another-Header", + }, }, 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.CompareRoundTrip(t, &req, cReq, cResp, expectedResponse); err != nil { - t.Errorf("failed to compare request and response: %v", err) - } + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse) }) t.Run("http route with lua filter 2", func(t *testing.T) { @@ -97,22 +92,17 @@ var HTTPLuaTest = suite.ConformanceTest{ "X-Custom-Lua-Header": "lua_value_2", "X-Custom-Lua-Another-Header": "lua_another_value", }, + AbsentHeaders: []string{ + "X-Custom-Response-Header", // gateway policy never took effect + }, }, 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.CompareRoundTrip(t, &req, cReq, cResp, expectedResponse); err != nil { - t.Errorf("failed to compare request and response: %v", err) - } + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse) }) - t.Run("http route without lua filter", func(t *testing.T) { + t.Run("http route fallback to gateway policy", func(t *testing.T) { ns := "gateway-conformance-infra" routeNN := types.NamespacedName{Name: "example-route-3-without-lua", Namespace: ns} gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} @@ -133,21 +123,18 @@ var HTTPLuaTest = suite.ConformanceTest{ Path: "/route3", }, Response: http.Response{ - StatusCodes: []int{200}, - AbsentHeaders: []string{"X-Custom-Lua-Header", "X-Custom-Lua-Another-Header"}, + StatusCodes: []int{200}, + Headers: map[string]string{ + "X-Custom-Response-Header": "gateway", // fallback to gateway policy + }, + AbsentHeaders: []string{ + "X-Custom-Lua-Header", "X-Custom-Lua-Another-Header", + }, }, 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.CompareRoundTrip(t, &req, cReq, cResp, expectedResponse); err != nil { - t.Errorf("failed to compare request and response: %v", err) - } + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse) }) t.Run("http route with lua filter context", func(t *testing.T) { @@ -174,6 +161,41 @@ var HTTPLuaTest = suite.ConformanceTest{ Headers: map[string]string{ "X-Lua-Filter-Context": "hello_from_filter_context", }, + AbsentHeaders: []string{ + "X-Custom-Response-Header", // gateway policy never took effect + }, + }, + Namespace: ns, + } + + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse) + }) + + t.Run("http route without lua filter", func(t *testing.T) { + ns := "gateway-conformance-infra" + routeNN := types.NamespacedName{Name: "example-route-3-without-lua", Namespace: ns} + gwNN := types.NamespacedName{Name: "all-namespaces", Namespace: ns} + gwAddr := kubernetes.GatewayAndRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), &gwapiv1.HTTPRoute{}, false, routeNN) + + ancestorRef := gwapiv1.ParentReference{ + Group: gatewayapi.GroupPtr(gwapiv1.GroupName), + Kind: gatewayapi.KindPtr(resource.KindGateway), + Namespace: gatewayapi.NamespacePtr(gwNN.Namespace), + Name: gwapiv1.ObjectName(gwNN.Name), + } + EnvoyExtensionPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "example-lua-1", Namespace: ns}, suite.ControllerName, ancestorRef) + + expectedResponse := http.ExpectedResponse{ + Request: http.Request{ + Host: "www.example.com", + Path: "/route3", + }, + Response: http.Response{ + StatusCodes: []int{200}, + AbsentHeaders: []string{ + "X-Custom-Response-Header", // no policy for all-namespaces gatweway + "X-Custom-Lua-Header", "X-Custom-Lua-Another-Header", + }, }, Namespace: ns, }