From fbc698ed9fa8cb58590474189c2c940d32ed689e Mon Sep 17 00:00:00 2001 From: zirain Date: Mon, 29 Jun 2026 13:16:06 +0800 Subject: [PATCH 1/8] split lua with listener and route - Added a Luas field to the listener-level EnvoyExtensions struct so listener-scoped Lua policies can be represented separately from per-route ones. - Refactored to add HCM placeholder filters for both per-route Lua slots and listener-level Lua filters (using named Lua HCM filters with empty default source). Listener filters are appended after route-slot filters to preserve ordering after HCM filter reversal. The Disabled: true flag was removed from listener Lua HCM filters since they are always active for the listener. - New implementation that injects listener-level Lua source code and filter context into RouteConfiguration.TypedPerFilterConfig, delivering script changes via RDS to avoid listener drains. Signed-off-by: zirain --- internal/gatewayapi/envoyextensionpolicy.go | 51 ++++---- ...h-invalid-lua-validation-disabled.out.yaml | 5 +- ...ith-invalid-lua-validation-syntax.out.yaml | 5 +- ...yextensionpolicy-with-invalid-lua.out.yaml | 19 ++- ...xtensionpolicy-with-lua-configmap.out.yaml | 49 +++----- .../envoyextensionpolicy-with-lua.out.yaml | 18 +-- internal/ir/xds.go | 8 +- internal/ir/zz_generated.deepcopy.go | 5 + internal/xds/translator/api_key_auth.go | 4 + internal/xds/translator/authorization.go | 4 + internal/xds/translator/bandwidth_limit.go | 4 + internal/xds/translator/basicauth.go | 4 + internal/xds/translator/compressor.go | 4 + internal/xds/translator/cors.go | 4 + internal/xds/translator/credentialInjector.go | 4 + internal/xds/translator/custom_response.go | 4 + .../xds/translator/dynamic_forward_proxy.go | 4 + internal/xds/translator/dynamicmodule.go | 4 + internal/xds/translator/extauth.go | 4 + internal/xds/translator/extproc.go | 4 + internal/xds/translator/fault.go | 4 + internal/xds/translator/geoip.go | 4 + internal/xds/translator/header_mutation.go | 4 + internal/xds/translator/healthcheck.go | 4 + internal/xds/translator/httpfilters.go | 21 +++- internal/xds/translator/jwt.go | 4 + internal/xds/translator/listener.go | 2 + internal/xds/translator/local_ratelimit.go | 4 + internal/xds/translator/lua.go | 117 +++++++++++++++--- internal/xds/translator/oidc.go | 4 + internal/xds/translator/request_buffer.go | 4 + .../xds/translator/session_persistence.go | 4 + .../translator/testdata/in/xds-ir/lua.yaml | 82 ++++++++++++ .../testdata/out/xds-ir/lua.clusters.yaml | 23 ++++ .../testdata/out/xds-ir/lua.endpoints.yaml | 12 ++ .../testdata/out/xds-ir/lua.listeners.yaml | 91 +++++++++++++- .../testdata/out/xds-ir/lua.routes.yaml | 58 +++++++++ internal/xds/translator/translator.go | 4 + internal/xds/translator/wasm.go | 4 + test/e2e/testdata/lua-http.yaml | 19 +++ test/e2e/tests/lua_http.go | 84 ++++++++----- 41 files changed, 619 insertions(+), 142 deletions(-) diff --git a/internal/gatewayapi/envoyextensionpolicy.go b/internal/gatewayapi/envoyextensionpolicy.go index b03451309b..4db2510fa0 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,19 @@ 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. + totalExtensions := len(luas) + if totalExtensions > 0 { + 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 +709,19 @@ 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, } } } } - 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/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..f99cfd8e77 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: - '*' @@ -288,12 +293,7 @@ xdsIR: weight: 1 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 + envoyExtensions: {} hostname: www.example.com isHTTP2: false metadata: @@ -324,12 +324,7 @@ xdsIR: name: httproute/default/httproute-2/rule/0/backend/0 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 + envoyExtensions: {} 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..ea2cdfcc1e 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: - '*' @@ -460,14 +467,7 @@ xdsIR: weight: 1 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 + envoyExtensions: {} hostname: www.example.com isHTTP2: false metadata: @@ -500,14 +500,7 @@ xdsIR: weight: 1 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 + envoyExtensions: {} hostname: www.example.com isHTTP2: false metadata: @@ -540,14 +533,7 @@ xdsIR: weight: 1 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 + envoyExtensions: {} hostname: www.example.com isHTTP2: false metadata: @@ -580,13 +566,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: @@ -617,14 +603,7 @@ xdsIR: name: httproute/default/httproute-2/rule/0/backend/0 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 + envoyExtensions: {} hostname: www.example.com isHTTP2: false metadata: diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua.out.yaml index 29bfd408e5..af0a07e945 100644 --- a/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua.out.yaml +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua.out.yaml @@ -244,6 +244,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 +284,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: @@ -315,12 +320,7 @@ xdsIR: name: httproute/default/httproute-2/rule/0/backend/0 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 + envoyExtensions: {} hostname: www.example.com isHTTP2: false metadata: diff --git a/internal/ir/xds.go b/internal/ir/xds.go index 5987fb8706..d5823b51bd 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 @@ -3715,12 +3717,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..67df6c3e31 100644 --- a/internal/ir/zz_generated.deepcopy.go +++ b/internal/ir/zz_generated.deepcopy.go @@ -2352,6 +2352,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..a8c84835f4 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) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/authorization.go b/internal/xds/translator/authorization.go index 3119dab922..fd9ae3bb38 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) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/bandwidth_limit.go b/internal/xds/translator/bandwidth_limit.go index 02e5d63bf8..605aaff883 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) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/basicauth.go b/internal/xds/translator/basicauth.go index bbf4b3f511..a783bcbb4d 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) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/compressor.go b/internal/xds/translator/compressor.go index ddc35d6d6a..02686c14fb 100644 --- a/internal/xds/translator/compressor.go +++ b/internal/xds/translator/compressor.go @@ -195,3 +195,7 @@ func compressorPerRouteConfig() *compressorv3.CompressorPerRoute { }, } } + +func (*compressor) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/cors.go b/internal/xds/translator/cors.go index 5aff0a80e9..ac79a5110e 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) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/credentialInjector.go b/internal/xds/translator/credentialInjector.go index d647e7f1a4..21a59713ba 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) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/custom_response.go b/internal/xds/translator/custom_response.go index 65977b6791..63b0c23260 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 (c *customResponse) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *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..cdc67c45a9 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) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/dynamicmodule.go b/internal/xds/translator/dynamicmodule.go index c117d29054..a3474f1deb 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) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/extauth.go b/internal/xds/translator/extauth.go index 2405ba505b..e6482f6993 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) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/extproc.go b/internal/xds/translator/extproc.go index b31df8c60f..aa54050cfd 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) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/fault.go b/internal/xds/translator/fault.go index 142fd58ea1..2dadbc8a77 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) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/geoip.go b/internal/xds/translator/geoip.go index fffdf7c887..af6245dc10 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) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/header_mutation.go b/internal/xds/translator/header_mutation.go index 7281c3f645..c5bc0f8906 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) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/healthcheck.go b/internal/xds/translator/healthcheck.go index 6551aa61fa..2daf1150b4 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) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/httpfilters.go b/internal/xds/translator/httpfilters.go index d365dcd866..c439433980 100644 --- a/internal/xds/translator/httpfilters.go +++ b/internal/xds/translator/httpfilters.go @@ -58,6 +58,9 @@ 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 + // patchRoute patches the provider RouteConfiguration. + patchRouteConfiguration(rc *routev3.RouteConfiguration, httpListener *ir.HTTPListener) error + // patchResources adds all the other needed resources referenced by this // filter to the resource version table. // for example: @@ -124,7 +127,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 +347,17 @@ func patchRouteWithPerRouteConfig(route *routev3.Route, irRoute *ir.HTTPRoute, h return nil } +// patchRouteConfiguration +func patchRouteConfiguration(rc *routev3.RouteConfiguration, httpListener *ir.HTTPListener) error { + for _, filter := range httpFilters { + if err := filter.patchRouteConfiguration(rc, 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..d961de13c6 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) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *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..bc3570375d 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) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/lua.go b/internal/xds/translator/lua.go index 34c273c20b..0edd056a62 100644 --- a/internal/xds/translator/lua.go +++ b/internal/xds/translator/lua.go @@ -55,18 +55,39 @@ 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 { + // add place holder http filter for per listener lua filter + if hasListenerLua { + for i := range irListener.EnvoyExtensions.Luas { + filterName := luaListenerFilterName(i) + if hcmContainsFilter(mgr, filterName) { + continue + } + luaFilter, err := buildHCMLuaFilter(filterName) + if err != nil { + errs = errors.Join(errs, err) + continue + } + + mgr.HttpFilters = append(mgr.HttpFilters, luaFilter) + } + } + + // add place holder filters for route Lua + for idx := range maxRouteLuaCount { filterName := luaFilterName(idx) if hcmContainsFilter(mgr, filterName) { continue @@ -81,8 +102,6 @@ func (*lua) patchHCM(mgr *hcmv3.HttpConnectionManager, irListener *ir.HTTPListen 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) { var ( luaProto *luafilterv3.Lua @@ -91,9 +110,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 { @@ -104,21 +121,27 @@ func buildHCMLuaFilter(filterName string) (*hcmv3.HttpFilter, error) { } return &hcmv3.HttpFilter{ - Name: filterName, - Disabled: true, + Name: filterName, 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,17 +156,33 @@ 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 { +// When per-route Lua is active, listener-level Lua HCM filters are disabled for the route so only +// the per-route scripts run. +func (*lua) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute, irListener *ir.HTTPListener) error { if route == nil { return errors.New("xds route is nil") } if irRoute == nil { return errors.New("ir route is nil") } - if irRoute.EnvoyExtensions == nil { + if irRoute.EnvoyExtensions == nil || len(irRoute.EnvoyExtensions.Luas) == 0 { return nil } + // Disable listener-level Lua HCM filters so they don't run alongside the per-route scripts. + if irListener != nil && irListener.EnvoyExtensions != nil { + 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 + } + } + } + for idx, ep := range irRoute.EnvoyExtensions.Luas { filterName := luaFilterName(idx) luaPerRoute := &luafilterv3.LuaPerRoute{ @@ -176,3 +215,51 @@ func (*lua) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute, _ *ir.HTTPLi } return nil } + +// patchRouteConfiguration patches the RouteConfiguration for the listener so that listener-level +// Lua source code and filter context are delivered via RDS rather than baked into the HCM filter, +// preventing Lua script changes from causing a listener drain. +func (l *lua) patchRouteConfiguration(rc *routev3.RouteConfiguration, httpListener *ir.HTTPListener) error { + if httpListener.EnvoyExtensions == nil { + return nil + } + + if rc.TypedPerFilterConfig == nil { + rc.TypedPerFilterConfig = map[string]*anypb.Any{} + } + + var errs error + for i, ep := range httpListener.EnvoyExtensions.Luas { + filterName := luaListenerFilterName(i) + if rc.TypedPerFilterConfig[filterName] != nil { + continue + } + + // Override lua if needed, this won't create lua VM per route. + luaPerRoute := &luafilterv3.LuaPerRoute{ + Override: &luafilterv3.LuaPerRoute_SourceCode{ + SourceCode: &corev3.DataSource{ + Specifier: &corev3.DataSource_InlineString{ + InlineString: *ep.Code, + }, + }, + }, + } + if ep.FilterContext != nil && ep.FilterContext.Raw != nil { + 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 { + errs = errors.Join(errs, err) + continue + } + + rc.TypedPerFilterConfig[filterName] = luaPerRouteAny + } + + return errs +} diff --git a/internal/xds/translator/oidc.go b/internal/xds/translator/oidc.go index fb4bca8ee9..dac83e2b0a 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) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/request_buffer.go b/internal/xds/translator/request_buffer.go index 231506e34b..1ee01a56c7 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) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { + return nil +} diff --git a/internal/xds/translator/session_persistence.go b/internal/xds/translator/session_persistence.go index 114672a7cd..2709f228fd 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) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *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..88f2d9d11a 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,84 @@ 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 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..ad4ad66bbe 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,26 @@ 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 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..dadc93ba2d 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,15 @@ 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 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..1a2c86b91e 100644 --- a/internal/xds/translator/testdata/out/xds-ir/lua.listeners.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/lua.listeners.yaml @@ -14,14 +14,12 @@ initialStreamWindowSize: 65536 maxConcurrentStreams: 100 httpFilters: - - disabled: true - name: envoy.filters.http.lua/0 + - name: envoy.filters.http.lua/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.lua/1 typedConfig: '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua defaultSourceCode: @@ -45,3 +43,88 @@ 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: "" + - name: envoy.filters.http.lua/0 + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua + defaultSourceCode: + inlineString: "" + - 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: "" + - 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 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..43065b2fa4 100644 --- a/internal/xds/translator/testdata/out/xds-ir/lua.routes.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/lua.routes.yaml @@ -39,3 +39,61 @@ sourceCode: inlineString: function envoy_on_response(response_handle) response_handle:logError('Hello.') end +- ignorePortInHostMatching: true + name: envoy-gateway/gateway-1/http-2 + 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 + 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.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 + envoy.filters.http.lua/listener/0: + '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute + disabled: true +- ignorePortInHostMatching: true + name: envoy-gateway/gateway-1/http-3 + 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 + 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 diff --git a/internal/xds/translator/translator.go b/internal/xds/translator/translator.go index ec4272e49d..3834f1da53 100644 --- a/internal/xds/translator/translator.go +++ b/internal/xds/translator/translator.go @@ -464,6 +464,10 @@ func (t *Translator) processHTTPListenerXdsTranslation( } } + if err = patchRouteConfiguration(xdsRouteCfg, httpListener); err != nil { + errs = errors.Join(errs, err) + } + // Generate xDS virtual hosts and routes for the given HTTPListener, // and add them to the xDS route config. if err = t.addRouteToRouteConfig(tCtx, xdsRouteCfg, httpListener, metrics, http3Settings); err != nil { diff --git a/internal/xds/translator/wasm.go b/internal/xds/translator/wasm.go index 0bbbcddd73..52a22dda70 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) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { + return nil +} 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, } From bdccdd08ce903f569ea57f9076a5edb2cb750ebe Mon Sep 17 00:00:00 2001 From: zirain Date: Mon, 29 Jun 2026 13:42:15 +0800 Subject: [PATCH 2/8] fix: disable inherited Lua for any overridden route Signed-off-by: zirain --- internal/xds/translator/lua.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/internal/xds/translator/lua.go b/internal/xds/translator/lua.go index 0edd056a62..eeed41fa5a 100644 --- a/internal/xds/translator/lua.go +++ b/internal/xds/translator/lua.go @@ -156,8 +156,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. -// When per-route Lua is active, listener-level Lua HCM filters are disabled for the route so only -// the per-route scripts run. +// Any route that carries an EnvoyExtensionPolicy override (EnvoyExtensions != nil) disables inherited +// listener-level Lua, even when the overriding policy contains no Lua entries (e.g. ExtProc/Wasm only). +// Without this, the listener Lua installed via RouteConfiguration.TypedPerFilterConfig would remain +// active on the route despite the gateway translation treating EnvoyExtensions != nil as a full override. func (*lua) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute, irListener *ir.HTTPListener) error { if route == nil { return errors.New("xds route is nil") @@ -165,11 +167,12 @@ func (*lua) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute, irListener * if irRoute == nil { return errors.New("ir route is nil") } - if irRoute.EnvoyExtensions == nil || len(irRoute.EnvoyExtensions.Luas) == 0 { + if irRoute.EnvoyExtensions == nil { return nil } - // Disable listener-level Lua HCM filters so they don't run alongside the per-route scripts. + // Disable inherited listener-level Lua for any route that carries an overriding + // EnvoyExtensionPolicy, even when that policy contains no Lua entries. if irListener != nil && irListener.EnvoyExtensions != nil { for i := range irListener.EnvoyExtensions.Luas { luaPerRoute := &luafilterv3.LuaPerRoute{ @@ -183,6 +186,10 @@ func (*lua) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute, irListener * } } + if len(irRoute.EnvoyExtensions.Luas) == 0 { + return nil + } + for idx, ep := range irRoute.EnvoyExtensions.Luas { filterName := luaFilterName(idx) luaPerRoute := &luafilterv3.LuaPerRoute{ From 9b269cbe52165ec4e2aa5101854877595b24f4f4 Mon Sep 17 00:00:00 2001 From: zirain Date: Mon, 29 Jun 2026 14:36:58 +0800 Subject: [PATCH 3/8] fix: disable inherited Lua for any overridden route Signed-off-by: zirain --- .../envoyextensionpolicy-with-lua.in.yaml | 97 +++++ .../envoyextensionpolicy-with-lua.out.yaml | 373 ++++++++++++++++++ internal/xds/translator/api_key_auth.go | 2 +- internal/xds/translator/authorization.go | 2 +- internal/xds/translator/bandwidth_limit.go | 2 +- internal/xds/translator/basicauth.go | 2 +- internal/xds/translator/compressor.go | 2 +- internal/xds/translator/cors.go | 2 +- internal/xds/translator/credentialInjector.go | 2 +- internal/xds/translator/custom_response.go | 2 +- .../xds/translator/dynamic_forward_proxy.go | 2 +- internal/xds/translator/dynamicmodule.go | 2 +- internal/xds/translator/extauth.go | 2 +- internal/xds/translator/extproc.go | 2 +- internal/xds/translator/fault.go | 2 +- internal/xds/translator/geoip.go | 2 +- internal/xds/translator/header_mutation.go | 2 +- internal/xds/translator/healthcheck.go | 2 +- internal/xds/translator/httpfilters.go | 13 +- internal/xds/translator/jwt.go | 2 +- internal/xds/translator/local_ratelimit.go | 2 +- internal/xds/translator/lua.go | 35 +- internal/xds/translator/oidc.go | 2 +- internal/xds/translator/request_buffer.go | 2 +- .../xds/translator/session_persistence.go | 2 +- .../translator/testdata/in/xds-ir/lua.yaml | 132 +++++++ .../testdata/out/xds-ir/lua.clusters.yaml | 122 ++++++ .../testdata/out/xds-ir/lua.endpoints.yaml | 53 +++ .../testdata/out/xds-ir/lua.listeners.yaml | 94 +++++ .../testdata/out/xds-ir/lua.routes.yaml | 104 ++++- internal/xds/translator/translator.go | 8 +- internal/xds/translator/wasm.go | 2 +- 32 files changed, 1005 insertions(+), 70 deletions(-) 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 af0a07e945..21bcf96ea4 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: @@ -337,3 +577,136 @@ 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: {} + 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: {} + 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/xds/translator/api_key_auth.go b/internal/xds/translator/api_key_auth.go index a8c84835f4..bf545088b7 100644 --- a/internal/xds/translator/api_key_auth.go +++ b/internal/xds/translator/api_key_auth.go @@ -177,6 +177,6 @@ func buildAPIKeyAuthFilterPerRouteConfig(apiKeyAuth *ir.APIKeyAuth) *apikeyauthv } } -func (*apiKeyAuth) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { +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 fd9ae3bb38..e5b342590e 100644 --- a/internal/xds/translator/authorization.go +++ b/internal/xds/translator/authorization.go @@ -856,6 +856,6 @@ func wrapPredicateWithNot(predicate *matcherv3.Matcher_MatcherList_Predicate, in } } -func (*rbac) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { +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 605aaff883..1a7120c9ba 100644 --- a/internal/xds/translator/bandwidth_limit.go +++ b/internal/xds/translator/bandwidth_limit.go @@ -183,6 +183,6 @@ func buildBandwidthLimitResponseProto(cfg *ir.BandwidthLimitConfig) *bwlimitv3.B return proto } -func (*bandwidthLimit) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { +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 a783bcbb4d..25799c8af5 100644 --- a/internal/xds/translator/basicauth.go +++ b/internal/xds/translator/basicauth.go @@ -164,6 +164,6 @@ func basicAuthPerRouteConfig(basicAuth *ir.BasicAuth) *basicauthv3.BasicAuthPerR } } -func (*basicAuth) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { +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 02686c14fb..3f447d0bf5 100644 --- a/internal/xds/translator/compressor.go +++ b/internal/xds/translator/compressor.go @@ -196,6 +196,6 @@ func compressorPerRouteConfig() *compressorv3.CompressorPerRoute { } } -func (*compressor) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { +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 ac79a5110e..4c41bead94 100644 --- a/internal/xds/translator/cors.go +++ b/internal/xds/translator/cors.go @@ -203,6 +203,6 @@ func (c *cors) patchResources(*types.ResourceVersionTable, []*ir.HTTPRoute) erro return nil } -func (*cors) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { +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 21a59713ba..d14ddef9a2 100644 --- a/internal/xds/translator/credentialInjector.go +++ b/internal/xds/translator/credentialInjector.go @@ -161,6 +161,6 @@ func (*credentialInjector) patchRoute(route *routev3.Route, irRoute *ir.HTTPRout return nil } -func (*credentialInjector) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { +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 63b0c23260..dec7020da7 100644 --- a/internal/xds/translator/custom_response.go +++ b/internal/xds/translator/custom_response.go @@ -525,6 +525,6 @@ func (c *customResponse) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute, return nil } -func (c *customResponse) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { +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 cdc67c45a9..fb419f9018 100644 --- a/internal/xds/translator/dynamic_forward_proxy.go +++ b/internal/xds/translator/dynamic_forward_proxy.go @@ -235,6 +235,6 @@ func buildDFPLoopbackRBACPerRoute(irRoute *ir.HTTPRoute) (*rbacv3.RBACPerRoute, }, nil } -func (*dynamicForwardProxy) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { +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 a3474f1deb..1d4d56d4ed 100644 --- a/internal/xds/translator/dynamicmodule.go +++ b/internal/xds/translator/dynamicmodule.go @@ -211,6 +211,6 @@ func (*dynamicModule) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute, _ return nil } -func (*dynamicModule) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { +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 e6482f6993..b9fbf3a1cd 100644 --- a/internal/xds/translator/extauth.go +++ b/internal/xds/translator/extauth.go @@ -330,6 +330,6 @@ func convertContextExtensions(irCtxExts []*ir.ContextExtention) map[string]strin return ctxExts } -func (*extAuth) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { +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 aa54050cfd..c4182b24be 100644 --- a/internal/xds/translator/extproc.go +++ b/internal/xds/translator/extproc.go @@ -266,6 +266,6 @@ func translateExtProcBodyProcessingMode(mode *ir.ExtProcBodyProcessingMode) extp return extprocv3.ProcessingMode_NONE } -func (*extProc) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { +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 2dadbc8a77..83dd9c4f2d 100644 --- a/internal/xds/translator/fault.go +++ b/internal/xds/translator/fault.go @@ -175,6 +175,6 @@ func (*fault) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute, _ *ir.HTTP return nil } -func (*fault) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { +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 af6245dc10..71a9281f30 100644 --- a/internal/xds/translator/geoip.go +++ b/internal/xds/translator/geoip.go @@ -294,6 +294,6 @@ func (*geoip) patchResources(*types.ResourceVersionTable, []*ir.HTTPRoute) error return nil } -func (*geoip) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { +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 c5bc0f8906..ddc9d970b9 100644 --- a/internal/xds/translator/header_mutation.go +++ b/internal/xds/translator/header_mutation.go @@ -163,6 +163,6 @@ func buildHeaderMutationRules(addHeaders []ir.AddHeader, removeHeaders []string, return mutationRules } -func (*headerMutation) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { +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 2daf1150b4..3db72fc2a1 100644 --- a/internal/xds/translator/healthcheck.go +++ b/internal/xds/translator/healthcheck.go @@ -102,6 +102,6 @@ func (*healthCheck) patchRoute(_ *routev3.Route, _ *ir.HTTPRoute, _ *ir.HTTPList return nil } -func (*healthCheck) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { +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 c439433980..60640be8c2 100644 --- a/internal/xds/translator/httpfilters.go +++ b/internal/xds/translator/httpfilters.go @@ -58,8 +58,10 @@ 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 - // patchRoute patches the provider RouteConfiguration. - patchRouteConfiguration(rc *routev3.RouteConfiguration, 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. @@ -347,14 +349,13 @@ func patchRouteWithPerRouteConfig(route *routev3.Route, irRoute *ir.HTTPRoute, h return nil } -// patchRouteConfiguration -func patchRouteConfiguration(rc *routev3.RouteConfiguration, httpListener *ir.HTTPListener) error { +// 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.patchRouteConfiguration(rc, httpListener); err != nil { + if err := filter.patchVirtualHost(vh, httpListener); err != nil { return err } } - return nil } diff --git a/internal/xds/translator/jwt.go b/internal/xds/translator/jwt.go index d961de13c6..13d3cdf674 100644 --- a/internal/xds/translator/jwt.go +++ b/internal/xds/translator/jwt.go @@ -390,6 +390,6 @@ func buildJwtFromHeaders(headers []egv1a1.JWTHeaderExtractor) []*jwtauthnv3.JwtH return jwtHeaders } -func (*jwt) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { +func (*jwt) patchVirtualHost(_ *routev3.VirtualHost, _ *ir.HTTPListener) error { return nil } diff --git a/internal/xds/translator/local_ratelimit.go b/internal/xds/translator/local_ratelimit.go index bc3570375d..62391c303e 100644 --- a/internal/xds/translator/local_ratelimit.go +++ b/internal/xds/translator/local_ratelimit.go @@ -484,6 +484,6 @@ func buildQueryParamMatchLocalRateLimitActions( } } -func (*localRateLimit) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { +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 eeed41fa5a..538b91dde2 100644 --- a/internal/xds/translator/lua.go +++ b/internal/xds/translator/lua.go @@ -156,10 +156,9 @@ 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. -// Any route that carries an EnvoyExtensionPolicy override (EnvoyExtensions != nil) disables inherited -// listener-level Lua, even when the overriding policy contains no Lua entries (e.g. ExtProc/Wasm only). -// Without this, the listener Lua installed via RouteConfiguration.TypedPerFilterConfig would remain -// active on the route despite the gateway translation treating EnvoyExtensions != nil as a full override. +// 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") @@ -167,12 +166,12 @@ func (*lua) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute, irListener * if irRoute == nil { return errors.New("ir route is nil") } - if irRoute.EnvoyExtensions == nil { + if irRoute.EnvoyExtensions == nil || len(irRoute.EnvoyExtensions.Luas) == 0 { return nil } - // Disable inherited listener-level Lua for any route that carries an overriding - // EnvoyExtensionPolicy, even when that policy contains no Lua entries. + // 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 { for i := range irListener.EnvoyExtensions.Luas { luaPerRoute := &luafilterv3.LuaPerRoute{ @@ -186,10 +185,6 @@ func (*lua) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute, irListener * } } - if len(irRoute.EnvoyExtensions.Luas) == 0 { - return nil - } - for idx, ep := range irRoute.EnvoyExtensions.Luas { filterName := luaFilterName(idx) luaPerRoute := &luafilterv3.LuaPerRoute{ @@ -223,26 +218,26 @@ func (*lua) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute, irListener * return nil } -// patchRouteConfiguration patches the RouteConfiguration for the listener so that listener-level -// Lua source code and filter context are delivered via RDS rather than baked into the HCM filter, -// preventing Lua script changes from causing a listener drain. -func (l *lua) patchRouteConfiguration(rc *routev3.RouteConfiguration, httpListener *ir.HTTPListener) error { +// 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 rc.TypedPerFilterConfig == nil { - rc.TypedPerFilterConfig = map[string]*anypb.Any{} + if vh.TypedPerFilterConfig == nil { + vh.TypedPerFilterConfig = map[string]*anypb.Any{} } var errs error for i, ep := range httpListener.EnvoyExtensions.Luas { filterName := luaListenerFilterName(i) - if rc.TypedPerFilterConfig[filterName] != nil { + if vh.TypedPerFilterConfig[filterName] != nil { continue } - // Override lua if needed, this won't create lua VM per route. luaPerRoute := &luafilterv3.LuaPerRoute{ Override: &luafilterv3.LuaPerRoute_SourceCode{ SourceCode: &corev3.DataSource{ @@ -265,7 +260,7 @@ func (l *lua) patchRouteConfiguration(rc *routev3.RouteConfiguration, httpListen continue } - rc.TypedPerFilterConfig[filterName] = luaPerRouteAny + vh.TypedPerFilterConfig[filterName] = luaPerRouteAny } return errs diff --git a/internal/xds/translator/oidc.go b/internal/xds/translator/oidc.go index dac83e2b0a..98b1a6ecf7 100644 --- a/internal/xds/translator/oidc.go +++ b/internal/xds/translator/oidc.go @@ -588,6 +588,6 @@ func (*oidc) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute, _ *ir.HTTPL return nil } -func (*oidc) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { +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 1ee01a56c7..ec7b4e321d 100644 --- a/internal/xds/translator/request_buffer.go +++ b/internal/xds/translator/request_buffer.go @@ -146,6 +146,6 @@ func routeContainsRequestBuffer(route *ir.HTTPRoute) bool { return route.Traffic != nil && route.Traffic.RequestBuffer != nil } -func (*requestBuffer) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { +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 2709f228fd..7d202cad59 100644 --- a/internal/xds/translator/session_persistence.go +++ b/internal/xds/translator/session_persistence.go @@ -233,6 +233,6 @@ func (s *sessionPersistence) patchResources(_ *types.ResourceVersionTable, _ []* return nil } -func (*sessionPersistence) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { +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 88f2d9d11a..321b204e24 100644 --- a/internal/xds/translator/testdata/in/xds-ir/lua.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/lua.yaml @@ -143,3 +143,135 @@ http: 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: / 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 ad4ad66bbe..defb5d672a 100644 --- a/internal/xds/translator/testdata/out/xds-ir/lua.clusters.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/lua.clusters.yaml @@ -67,3 +67,125 @@ 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 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 dadc93ba2d..40b120366e 100644 --- a/internal/xds/translator/testdata/out/xds-ir/lua.endpoints.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/lua.endpoints.yaml @@ -34,3 +34,56 @@ 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 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 1a2c86b91e..53bde1df72 100644 --- a/internal/xds/translator/testdata/out/xds-ir/lua.listeners.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/lua.listeners.yaml @@ -128,3 +128,97 @@ 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.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 + suppressEnvoyHeaders: true + mergeSlashes: true + normalizePath: true + pathWithEscapedSlashesAction: UNESCAPE_AND_REDIRECT + rds: + configSource: + ads: {} + resourceApiVersion: V3 + routeConfigName: envoy-gateway/gateway-1/http-4 + serverHeaderTransformation: PASS_THROUGH + statPrefix: http-10083 + useRemoteAddress: true + name: envoy-gateway/gateway-1/http-4 + maxConnectionsToAcceptPerSocketEvent: 1 + 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: "" + - 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 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 43065b2fa4..8b56504b35 100644 --- a/internal/xds/translator/testdata/out/xds-ir/lua.routes.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/lua.routes.yaml @@ -41,15 +41,6 @@ end - ignorePortInHostMatching: true name: envoy-gateway/gateway-1/http-2 - 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 virtualHosts: - domains: - www.example.com @@ -74,17 +65,17 @@ 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 - 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 virtualHosts: - domains: - www.example.com @@ -97,3 +88,80 @@ 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: {} + - 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 diff --git a/internal/xds/translator/translator.go b/internal/xds/translator/translator.go index 3834f1da53..183899d02e 100644 --- a/internal/xds/translator/translator.go +++ b/internal/xds/translator/translator.go @@ -464,10 +464,6 @@ func (t *Translator) processHTTPListenerXdsTranslation( } } - if err = patchRouteConfiguration(xdsRouteCfg, httpListener); err != nil { - errs = errors.Join(errs, err) - } - // Generate xDS virtual hosts and routes for the given HTTPListener, // and add them to the xDS route config. if err = t.addRouteToRouteConfig(tCtx, xdsRouteCfg, httpListener, metrics, http3Settings); err != nil { @@ -663,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 52a22dda70..68a99c8f46 100644 --- a/internal/xds/translator/wasm.go +++ b/internal/xds/translator/wasm.go @@ -198,6 +198,6 @@ func (*wasm) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute, _ *ir.HTTPL return nil } -func (*wasm) patchRouteConfiguration(_ *routev3.RouteConfiguration, _ *ir.HTTPListener) error { +func (*wasm) patchVirtualHost(_ *routev3.VirtualHost, _ *ir.HTTPListener) error { return nil } From 00b6b5cad7f3da68180fddeb07efecf4eab57293 Mon Sep 17 00:00:00 2001 From: zirain Date: Mon, 29 Jun 2026 18:22:15 +0800 Subject: [PATCH 4/8] fix listener-level Lua leaks onto routes claimed by a more-specific non-Lua policy Signed-off-by: zirain --- internal/xds/translator/lua.go | 8 ++++++-- .../xds/translator/testdata/out/xds-ir/lua.routes.yaml | 3 +++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/internal/xds/translator/lua.go b/internal/xds/translator/lua.go index 538b91dde2..13b8d51527 100644 --- a/internal/xds/translator/lua.go +++ b/internal/xds/translator/lua.go @@ -166,13 +166,17 @@ func (*lua) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute, irListener * if irRoute == nil { return errors.New("ir route is nil") } - if irRoute.EnvoyExtensions == nil || len(irRoute.EnvoyExtensions.Luas) == 0 { + if irRoute.EnvoyExtensions == nil { return nil } + // Count the route level extensions, if it > 0, that means a route level policy is applied. + extensionsCount := len(irRoute.EnvoyExtensions.Luas) + len(irRoute.EnvoyExtensions.Wasms) + + len(irRoute.EnvoyExtensions.DynamicModules) + len(irRoute.EnvoyExtensions.ExtProcs) + // 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 { + if irListener != nil && irListener.EnvoyExtensions != nil && extensionsCount > 0 { for i := range irListener.EnvoyExtensions.Luas { luaPerRoute := &luafilterv3.LuaPerRoute{ Override: &luafilterv3.LuaPerRoute_Disabled{ 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 8b56504b35..6cc6807036 100644 --- a/internal/xds/translator/testdata/out/xds-ir/lua.routes.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/lua.routes.yaml @@ -115,6 +115,9 @@ 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 From 72ee6d6a920e4090ab47708be051ad07bcd62d6a Mon Sep 17 00:00:00 2001 From: zirain Date: Tue, 30 Jun 2026 16:13:56 +0800 Subject: [PATCH 5/8] fix same-policy listener Lua for route extensions Signed-off-by: zirain --- internal/gatewayapi/envoyextensionpolicy.go | 7 +-- .../testdata/custom-filter-order.out.yaml | 1 + ...yextensionpolicy-override-replace.out.yaml | 1 + ...nsionpolicy-section-name-override.out.yaml | 2 + ...xtensionpolicy-with-dynamicmodule.out.yaml | 1 + ...ith-extproc-with-backendtlspolicy.out.yaml | 2 + ...yextensionpolicy-with-invalid-lua.out.yaml | 6 ++- ...xtensionpolicy-with-lua-configmap.out.yaml | 12 +++-- .../envoyextensionpolicy-with-lua.out.yaml | 9 ++-- ...xtensionpolicy-with-wasm-env-vars.out.yaml | 1 + ...y-with-wasm-invalid-configuration.out.yaml | 6 ++- ...ensionpolicy-with-wasm-targetrefs.out.yaml | 1 + .../envoyextensionpolicy-with-wasm.out.yaml | 1 + internal/ir/xds.go | 2 + internal/ir/zz_generated.deepcopy.go | 5 ++ internal/xds/translator/lua.go | 8 ++- .../translator/testdata/in/xds-ir/lua.yaml | 45 ++++++++++++++++ .../testdata/out/xds-ir/lua.clusters.yaml | 53 ++++++++++++++++++ .../testdata/out/xds-ir/lua.endpoints.yaml | 17 ++++++ .../testdata/out/xds-ir/lua.listeners.yaml | 54 +++++++++++++++++++ .../testdata/out/xds-ir/lua.routes.yaml | 24 +++++++++ 21 files changed, 243 insertions(+), 15 deletions(-) diff --git a/internal/gatewayapi/envoyextensionpolicy.go b/internal/gatewayapi/envoyextensionpolicy.go index 4db2510fa0..7bf6833ed4 100644 --- a/internal/gatewayapi/envoyextensionpolicy.go +++ b/internal/gatewayapi/envoyextensionpolicy.go @@ -715,9 +715,10 @@ func (t *Translator) translateEnvoyExtensionPolicyForGateway( } } else { r.EnvoyExtensions = &ir.EnvoyExtensionFeatures{ - ExtProcs: extProcs, - Wasms: wasms, - DynamicModules: dynamicModules, + ExtProcs: extProcs, + Wasms: wasms, + DynamicModules: dynamicModules, + FromGatewayPolicy: new(true), } } } 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.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua.out.yaml index f99cfd8e77..81c75376eb 100644 --- a/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua.out.yaml +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua.out.yaml @@ -293,7 +293,8 @@ xdsIR: weight: 1 directResponse: statusCode: 500 - envoyExtensions: {} + envoyExtensions: + fromGatewayPolicy: true hostname: www.example.com isHTTP2: false metadata: @@ -324,7 +325,8 @@ xdsIR: name: httproute/default/httproute-2/rule/0/backend/0 protocol: HTTP weight: 1 - envoyExtensions: {} + envoyExtensions: + 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 ea2cdfcc1e..800eb241ed 100644 --- a/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua-configmap.out.yaml +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua-configmap.out.yaml @@ -467,7 +467,8 @@ xdsIR: weight: 1 directResponse: statusCode: 500 - envoyExtensions: {} + envoyExtensions: + fromGatewayPolicy: true hostname: www.example.com isHTTP2: false metadata: @@ -500,7 +501,8 @@ xdsIR: weight: 1 directResponse: statusCode: 500 - envoyExtensions: {} + envoyExtensions: + fromGatewayPolicy: true hostname: www.example.com isHTTP2: false metadata: @@ -533,7 +535,8 @@ xdsIR: weight: 1 directResponse: statusCode: 500 - envoyExtensions: {} + envoyExtensions: + fromGatewayPolicy: true hostname: www.example.com isHTTP2: false metadata: @@ -603,7 +606,8 @@ xdsIR: name: httproute/default/httproute-2/rule/0/backend/0 protocol: HTTP weight: 1 - envoyExtensions: {} + envoyExtensions: + fromGatewayPolicy: true hostname: www.example.com isHTTP2: false metadata: diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua.out.yaml index 21bcf96ea4..589c26ff13 100644 --- a/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua.out.yaml +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua.out.yaml @@ -560,7 +560,8 @@ xdsIR: name: httproute/default/httproute-2/rule/0/backend/0 protocol: HTTP weight: 1 - envoyExtensions: {} + envoyExtensions: + fromGatewayPolicy: true hostname: www.example.com isHTTP2: false metadata: @@ -642,7 +643,8 @@ xdsIR: name: httproute/default/httproute-a/rule/0/backend/0 protocol: HTTP weight: 1 - envoyExtensions: {} + envoyExtensions: + fromGatewayPolicy: true hostname: app-a.example.com isHTTP2: false metadata: @@ -693,7 +695,8 @@ xdsIR: name: httproute/default/httproute-b/rule/0/backend/0 protocol: HTTP weight: 1 - envoyExtensions: {} + envoyExtensions: + fromGatewayPolicy: true hostname: app-b.example.com isHTTP2: false metadata: 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.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-wasm-invalid-configuration.out.yaml index dcfca230a6..700fc84753 100644 --- a/internal/gatewayapi/testdata/envoyextensionpolicy-with-wasm-invalid-configuration.out.yaml +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-wasm-invalid-configuration.out.yaml @@ -928,7 +928,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 +960,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 d5823b51bd..a9c62d2e06 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -1156,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 diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go index 67df6c3e31..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. diff --git a/internal/xds/translator/lua.go b/internal/xds/translator/lua.go index 13b8d51527..48ca264f74 100644 --- a/internal/xds/translator/lua.go +++ b/internal/xds/translator/lua.go @@ -17,6 +17,7 @@ import ( "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" @@ -174,9 +175,14 @@ func (*lua) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute, irListener * extensionsCount := len(irRoute.EnvoyExtensions.Luas) + len(irRoute.EnvoyExtensions.Wasms) + len(irRoute.EnvoyExtensions.DynamicModules) + len(irRoute.EnvoyExtensions.ExtProcs) + // Only disable inherited listener-level Lua when the route's extensions come from a + // more-specific route policy. When they come from the same gateway/listener policy that + // also installed the listener Lua (FromGatewayPolicy=true), both coexist. + disableListenerLevelFilter := !ptr.Deref(irRoute.EnvoyExtensions.FromGatewayPolicy, false) && extensionsCount > 0 + // 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 && extensionsCount > 0 { + if irListener != nil && irListener.EnvoyExtensions != nil && disableListenerLevelFilter { for i := range irListener.EnvoyExtensions.Luas { luaPerRoute := &luafilterv3.LuaPerRoute{ Override: &luafilterv3.LuaPerRoute_Disabled{ diff --git a/internal/xds/translator/testdata/in/xds-ir/lua.yaml b/internal/xds/translator/testdata/in/xds-ir/lua.yaml index 321b204e24..9244659315 100644 --- a/internal/xds/translator/testdata/in/xds-ir/lua.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/lua.yaml @@ -275,3 +275,48 @@ http: 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 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 defb5d672a..837628b283 100644 --- a/internal/xds/translator/testdata/out/xds-ir/lua.clusters.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/lua.clusters.yaml @@ -189,3 +189,56 @@ 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 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 40b120366e..527f66efa7 100644 --- a/internal/xds/translator/testdata/out/xds-ir/lua.endpoints.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/lua.endpoints.yaml @@ -87,3 +87,20 @@ 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 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 53bde1df72..7f5267a707 100644 --- a/internal/xds/translator/testdata/out/xds-ir/lua.listeners.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/lua.listeners.yaml @@ -222,3 +222,57 @@ 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.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 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 6cc6807036..3008969961 100644 --- a/internal/xds/translator/testdata/out/xds-ir/lua.routes.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/lua.routes.yaml @@ -168,3 +168,27 @@ 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 From c8dc11cf79b723392ca28cb40f3ff8ee45a9db01 Mon Sep 17 00:00:00 2001 From: zirain Date: Tue, 30 Jun 2026 17:05:40 +0800 Subject: [PATCH 6/8] fix Signed-off-by: zirain --- internal/gatewayapi/envoyextensionpolicy.go | 6 ++- ...cy-with-wasm-invalid-configuration.in.yaml | 3 ++ ...y-with-wasm-invalid-configuration.out.yaml | 3 ++ internal/xds/translator/lua.go | 15 ++++--- .../translator/testdata/in/xds-ir/lua.yaml | 36 +++++++++++++++++ .../testdata/out/xds-ir/lua.clusters.yaml | 23 +++++++++++ .../testdata/out/xds-ir/lua.endpoints.yaml | 12 ++++++ .../testdata/out/xds-ir/lua.listeners.yaml | 40 +++++++++++++++++++ .../testdata/out/xds-ir/lua.routes.yaml | 24 +++++++++++ 9 files changed, 152 insertions(+), 10 deletions(-) diff --git a/internal/gatewayapi/envoyextensionpolicy.go b/internal/gatewayapi/envoyextensionpolicy.go index 7bf6833ed4..4778e6366a 100644 --- a/internal/gatewayapi/envoyextensionpolicy.go +++ b/internal/gatewayapi/envoyextensionpolicy.go @@ -694,8 +694,10 @@ func (t *Translator) translateEnvoyExtensionPolicyForGateway( } // TODO: move other extensions to listener level. - totalExtensions := len(luas) - if totalExtensions > 0 { + // 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, } 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 700fc84753..3ce41b6c18 100644 --- a/internal/gatewayapi/testdata/envoyextensionpolicy-with-wasm-invalid-configuration.out.yaml +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-wasm-invalid-configuration.out.yaml @@ -242,6 +242,9 @@ envoyExtensionPolicies: type: Image name: wasm-filter-1 rootID: my-root-id + lua: + - inline: function envoy_on_request(request_handle) end + type: Inline status: ancestors: - ancestorRef: diff --git a/internal/xds/translator/lua.go b/internal/xds/translator/lua.go index 48ca264f74..38c4df62e7 100644 --- a/internal/xds/translator/lua.go +++ b/internal/xds/translator/lua.go @@ -171,14 +171,13 @@ func (*lua) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute, irListener * return nil } - // Count the route level extensions, if it > 0, that means a route level policy is applied. - extensionsCount := len(irRoute.EnvoyExtensions.Luas) + len(irRoute.EnvoyExtensions.Wasms) + - len(irRoute.EnvoyExtensions.DynamicModules) + len(irRoute.EnvoyExtensions.ExtProcs) - - // Only disable inherited listener-level Lua when the route's extensions come from a - // more-specific route policy. When they come from the same gateway/listener policy that - // also installed the listener Lua (FromGatewayPolicy=true), both coexist. - disableListenerLevelFilter := !ptr.Deref(irRoute.EnvoyExtensions.FromGatewayPolicy, false) && extensionsCount > 0 + // 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. diff --git a/internal/xds/translator/testdata/in/xds-ir/lua.yaml b/internal/xds/translator/testdata/in/xds-ir/lua.yaml index 9244659315..8fb2404882 100644 --- a/internal/xds/translator/testdata/in/xds-ir/lua.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/lua.yaml @@ -320,3 +320,39 @@ http: - 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 837628b283..376d420242 100644 --- a/internal/xds/translator/testdata/out/xds-ir/lua.clusters.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/lua.clusters.yaml @@ -242,3 +242,26 @@ 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 527f66efa7..11449dbc2a 100644 --- a/internal/xds/translator/testdata/out/xds-ir/lua.endpoints.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/lua.endpoints.yaml @@ -104,3 +104,15 @@ - 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 7f5267a707..ab78d99b4e 100644 --- a/internal/xds/translator/testdata/out/xds-ir/lua.listeners.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/lua.listeners.yaml @@ -276,3 +276,43 @@ 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: "" + - 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 3008969961..f76b6650e3 100644 --- a/internal/xds/translator/testdata/out/xds-ir/lua.routes.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/lua.routes.yaml @@ -192,3 +192,27 @@ 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 + 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 From fe6ae4ee69fb06fc4f4f8ba024a3dcc471fecf77 Mon Sep 17 00:00:00 2001 From: zirain Date: Tue, 30 Jun 2026 20:30:54 +0800 Subject: [PATCH 7/8] fix Signed-off-by: zirain --- ...licy-with-wasm-invalid-configuration.out.yaml | 6 +++--- internal/xds/translator/lua.go | 16 +++++++++++----- .../testdata/out/xds-ir/lua.listeners.yaml | 9 ++++++--- .../9131-lua-xds-layout-per-route-filterchain.md | 1 + 4 files changed, 21 insertions(+), 11 deletions(-) create mode 100644 release-notes/current/breaking_changes/9131-lua-xds-layout-per-route-filterchain.md 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 3ce41b6c18..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 @@ -242,9 +245,6 @@ envoyExtensionPolicies: type: Image name: wasm-filter-1 rootID: my-root-id - lua: - - inline: function envoy_on_request(request_handle) end - type: Inline status: ancestors: - ancestorRef: diff --git a/internal/xds/translator/lua.go b/internal/xds/translator/lua.go index 38c4df62e7..c11327f5d6 100644 --- a/internal/xds/translator/lua.go +++ b/internal/xds/translator/lua.go @@ -77,7 +77,7 @@ func (*lua) patchHCM(mgr *hcmv3.HttpConnectionManager, irListener *ir.HTTPListen if hcmContainsFilter(mgr, filterName) { continue } - luaFilter, err := buildHCMLuaFilter(filterName) + luaFilter, err := buildHCMLuaFilter(filterName, false) if err != nil { errs = errors.Join(errs, err) continue @@ -87,13 +87,14 @@ func (*lua) patchHCM(mgr *hcmv3.HttpConnectionManager, irListener *ir.HTTPListen } } - // add place holder filters for route Lua + // add place holder filters for route Lua — disabled by default so they only execute + // on routes that explicitly enable them via LuaPerRoute in TypedPerFilterConfig. for idx := range maxRouteLuaCount { filterName := luaFilterName(idx) if hcmContainsFilter(mgr, filterName) { continue } - filter, err := buildHCMLuaFilter(filterName) + filter, err := buildHCMLuaFilter(filterName, true) if err != nil { errs = errors.Join(errs, err) continue @@ -103,7 +104,11 @@ func (*lua) patchHCM(mgr *hcmv3.HttpConnectionManager, irListener *ir.HTTPListen return errs } -func buildHCMLuaFilter(filterName string) (*hcmv3.HttpFilter, error) { +// 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 @@ -122,7 +127,8 @@ func buildHCMLuaFilter(filterName string) (*hcmv3.HttpFilter, error) { } return &hcmv3.HttpFilter{ - Name: filterName, + Name: filterName, + Disabled: disabled, ConfigType: &hcmv3.HttpFilter_TypedConfig{ TypedConfig: luaAny, }, 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 ab78d99b4e..c2a8ba20f8 100644 --- a/internal/xds/translator/testdata/out/xds-ir/lua.listeners.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/lua.listeners.yaml @@ -14,12 +14,14 @@ initialStreamWindowSize: 65536 maxConcurrentStreams: 100 httpFilters: - - name: envoy.filters.http.lua/0 + - disabled: true + name: envoy.filters.http.lua/0 typedConfig: '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua defaultSourceCode: inlineString: "" - - name: envoy.filters.http.lua/1 + - disabled: true + name: envoy.filters.http.lua/1 typedConfig: '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua defaultSourceCode: @@ -64,7 +66,8 @@ '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua defaultSourceCode: inlineString: "" - - name: envoy.filters.http.lua/0 + - disabled: true + name: envoy.filters.http.lua/0 typedConfig: '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua defaultSourceCode: 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. From c8d0be5e0c71561c069a64b1295e06358a676f1d Mon Sep 17 00:00:00 2001 From: zirain Date: Tue, 30 Jun 2026 21:03:50 +0800 Subject: [PATCH 8/8] use filter_chain filter Signed-off-by: zirain --- internal/xds/translator/lua.go | 100 +++++++++++++----- .../testdata/out/xds-ir/lua.listeners.yaml | 38 ++++--- .../testdata/out/xds-ir/lua.routes.yaml | 52 ++++++--- 3 files changed, 134 insertions(+), 56 deletions(-) diff --git a/internal/xds/translator/lua.go b/internal/xds/translator/lua.go index c11327f5d6..d6ad511dfd 100644 --- a/internal/xds/translator/lua.go +++ b/internal/xds/translator/lua.go @@ -12,6 +12,7 @@ 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" @@ -71,7 +72,7 @@ func (*lua) patchHCM(mgr *hcmv3.HttpConnectionManager, irListener *ir.HTTPListen var errs error // add place holder http filter for per listener lua filter - if hasListenerLua { + if irListener.EnvoyExtensions != nil { for i := range irListener.EnvoyExtensions.Luas { filterName := luaListenerFilterName(i) if hcmContainsFilter(mgr, filterName) { @@ -89,21 +90,47 @@ func (*lua) patchHCM(mgr *hcmv3.HttpConnectionManager, irListener *ir.HTTPListen // add place holder filters for route Lua — disabled by default so they only execute // on routes that explicitly enable them via LuaPerRoute in TypedPerFilterConfig. - for idx := range maxRouteLuaCount { - filterName := luaFilterName(idx) - if hcmContainsFilter(mgr, filterName) { - continue - } - filter, err := buildHCMLuaFilter(filterName, true) - if err != nil { - errs = errors.Join(errs, err) - continue - } - mgr.HttpFilters = append(mgr.HttpFilters, filter) + 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 } +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 @@ -200,36 +227,57 @@ func (*lua) patchRoute(route *routev3.Route, irRoute *ir.HTTPRoute, irListener * } } + 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) - luaPerRoute := &luafilterv3.LuaPerRoute{ - Override: &luafilterv3.LuaPerRoute_SourceCode{ - SourceCode: &corev3.DataSource{ - Specifier: &corev3.DataSource_InlineString{ - InlineString: *ep.Code, - }, + 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 } - luaPerRouteAny, err := anypb.New(luaPerRoute) + luaOnFCFilterAny, err := anypb.New(luaOnFCFilter) 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) - } - route.TypedPerFilterConfig[filterName] = luaPerRouteAny + 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 } 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 c2a8ba20f8..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,9 @@ 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.lua.v3.Lua - defaultSourceCode: - inlineString: "" - - disabled: true - name: envoy.filters.http.lua/1 - typedConfig: - '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua - defaultSourceCode: - inlineString: "" + '@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 @@ -67,11 +59,9 @@ defaultSourceCode: inlineString: "" - 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.lua.v3.Lua - defaultSourceCode: - inlineString: "" + '@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 @@ -112,6 +102,10 @@ '@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 @@ -152,6 +146,10 @@ '@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: @@ -206,6 +204,10 @@ '@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 @@ -246,6 +248,10 @@ '@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: @@ -300,6 +306,10 @@ '@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 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 f76b6650e3..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,16 +36,22 @@ upgradeConfigs: - upgradeType: websocket typedPerFilterConfig: - 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: - '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute - sourceCode: - inlineString: function envoy_on_response(response_handle) response_handle:logError('Hello.') - end + 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: @@ -54,14 +67,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 envoy.filters.http.lua/listener/0: '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute disabled: true