diff --git a/api/v1alpha1/envoyproxy_types.go b/api/v1alpha1/envoyproxy_types.go index d5567d2c52..20baa32df2 100644 --- a/api/v1alpha1/envoyproxy_types.go +++ b/api/v1alpha1/envoyproxy_types.go @@ -173,9 +173,14 @@ const ( // For supported APIs, see: https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/lua_filter#stream-handle-api LuaValidationStrict LuaValidation = "Strict" - // LuaValidationDisabled disables all validation of Lua scripts. + // LuaValidationSyntax checks for syntax errors in the Lua script. + // Note that this is not a full runtime validation and does not check for issues during script execution. + // This is recommended if your scripts use external libraries that are not supported by Lua runtime validation. + LuaValidationSyntax LuaValidation = "Syntax" + + // LuaValidationDisabled disables all validations of Lua scripts. // Scripts will be accepted and executed without any validation checks. - // This is not recommended unless your scripts import libraries that are not supported by Lua runtime validation. + // This is not recommended unless both runtime and syntax validations are failing unexpectedly. LuaValidationDisabled LuaValidation = "Disabled" ) diff --git a/internal/gatewayapi/envoyextensionpolicy.go b/internal/gatewayapi/envoyextensionpolicy.go index 5e8899c21f..bccc6f743e 100644 --- a/internal/gatewayapi/envoyextensionpolicy.go +++ b/internal/gatewayapi/envoyextensionpolicy.go @@ -443,6 +443,7 @@ func (t *Translator) buildLua( envoyProxy *egv1a1.EnvoyProxy, ) (*ir.Lua, error) { var luaCode *string + var luaValidation egv1a1.LuaValidation var err error if lua.Type == egv1a1.LuaValueTypeValueRef { luaCode, err = getLuaBodyFromLocalObjectReference(lua.ValueRef, resources, policy.Namespace) @@ -452,14 +453,12 @@ func (t *Translator) buildLua( if err != nil { return nil, err } - if envoyProxy != nil && envoyProxy.Spec.LuaValidation != nil && - *envoyProxy.Spec.LuaValidation == egv1a1.LuaValidationDisabled { - return &ir.Lua{ - Name: name, - Code: luaCode, - }, nil + if envoyProxy != nil && envoyProxy.Spec.LuaValidation != nil { + luaValidation = *envoyProxy.Spec.LuaValidation + } else { + luaValidation = egv1a1.LuaValidationStrict } - if err = luavalidator.NewLuaValidator(*luaCode).Validate(); err != nil { + if err = luavalidator.NewLuaValidator(*luaCode, luaValidation).Validate(); err != nil { return nil, fmt.Errorf("validation failed for lua body in policy with name %v: %w", name, err) } return &ir.Lua{ diff --git a/internal/gatewayapi/luavalidator/lua_validator.go b/internal/gatewayapi/luavalidator/lua_validator.go index b2977de995..d3628f51bc 100644 --- a/internal/gatewayapi/luavalidator/lua_validator.go +++ b/internal/gatewayapi/luavalidator/lua_validator.go @@ -11,6 +11,13 @@ import ( "strings" lua "github.com/yuin/gopher-lua" + + egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" +) + +const ( + envoyOnRequestFunctionName = "envoy_on_request" + envoyOnResponseFunctionName = "envoy_on_response" ) // mockData contains mocks of Envoy supported APIs for Lua filters. @@ -20,35 +27,50 @@ import ( var mockData []byte // LuaValidator validates user provided Lua for compatibility with Envoy supported Lua HTTP filter +// Validation strictness is controlled by the validation field type LuaValidator struct { - code string + code string + validation egv1a1.LuaValidation } // NewLuaValidator returns a LuaValidator for user provided Lua code -func NewLuaValidator(code string) *LuaValidator { +func NewLuaValidator(code string, validation egv1a1.LuaValidation) *LuaValidator { return &LuaValidator{ - code: code, + code: code, + validation: validation, } } // Validate runs all validations for the LuaValidator func (l *LuaValidator) Validate() error { - if !strings.Contains(l.code, "envoy_on_request") && !strings.Contains(l.code, "envoy_on_response") { - return fmt.Errorf("expected one of envoy_on_request() or envoy_on_response() to be defined") + if !strings.Contains(l.code, envoyOnRequestFunctionName) && !strings.Contains(l.code, envoyOnResponseFunctionName) { + return fmt.Errorf("expected one of %s() or %s() to be defined", envoyOnRequestFunctionName, envoyOnResponseFunctionName) } - if strings.Contains(l.code, "envoy_on_request") { - if err := l.runLua(string(mockData) + "\n" + l.code + "\nenvoy_on_request(StreamHandle)"); err != nil { - return fmt.Errorf("failed to mock run envoy_on_request: %w", err) + if strings.Contains(l.code, envoyOnRequestFunctionName) { + if err := l.validate(string(mockData) + "\n" + l.code + "\n" + envoyOnRequestFunctionName + "(StreamHandle)"); err != nil { + return fmt.Errorf("failed to validate with %s: %w", envoyOnRequestFunctionName, err) } } - if strings.Contains(l.code, "envoy_on_response") { - if err := l.runLua(string(mockData) + "\n" + l.code + "\nenvoy_on_response(StreamHandle)"); err != nil { - return fmt.Errorf("failed to mock run envoy_on_response: %w", err) + if strings.Contains(l.code, envoyOnResponseFunctionName) { + if err := l.validate(string(mockData) + "\n" + l.code + "\n" + envoyOnResponseFunctionName + "(StreamHandle)"); err != nil { + return fmt.Errorf("failed to validate with %s: %w", envoyOnResponseFunctionName, err) } } return nil } +// validate runs the validation on given code +func (l *LuaValidator) validate(code string) error { + switch l.validation { + case egv1a1.LuaValidationSyntax: + return l.loadLua(code) + case egv1a1.LuaValidationDisabled: + return nil + default: + return l.runLua(code) + } +} + // runLua interprets and runs the provided Lua code in runtime using gopher-lua // Refer: https://github.com/yuin/gopher-lua?tab=readme-ov-file#differences-between-lua-and-gopherlua func (l *LuaValidator) runLua(code string) error { @@ -59,3 +81,14 @@ func (l *LuaValidator) runLua(code string) error { } return nil } + +// loadLua loads the Lua code into the Lua state, does not run it +// This is used to check for syntax errors in the Lua code +func (l *LuaValidator) loadLua(code string) error { + L := lua.NewState() + defer L.Close() + if _, err := L.LoadString(code); err != nil { + return err + } + return nil +} diff --git a/internal/gatewayapi/luavalidator/lua_validator_test.go b/internal/gatewayapi/luavalidator/lua_validator_test.go index cb3ba75489..00a3b7cc48 100644 --- a/internal/gatewayapi/luavalidator/lua_validator_test.go +++ b/internal/gatewayapi/luavalidator/lua_validator_test.go @@ -8,12 +8,15 @@ package luavalidator import ( "strings" "testing" + + egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" ) func Test_Validate(t *testing.T) { type args struct { name string code string + validation egv1a1.LuaValidation expectedErrSubstring string } tests := []args{ @@ -130,10 +133,52 @@ func Test_Validate(t *testing.T) { end`, expectedErrSubstring: "attempt to call a non-function object", }, + { + name: "unsupported api", + code: `function envoy_on_request(request_handle) + request_handle:unknownApi() + end`, + validation: egv1a1.LuaValidationSyntax, + expectedErrSubstring: "", + }, + { + name: "unsupported api", + code: `function envoy_on_response(response_handle) + -- Sets the content-type. + response_handle:headers():replace("content-type", "text/html") + local last + for chunk in response_handle:bodyChunks() do + -- Clears each received chunk. + chunk:setBytes("") + last = chunk + -- invalid syntax as there is no end for the for loop + + last:setBytes("Not Found") + end`, + validation: egv1a1.LuaValidationSyntax, + expectedErrSubstring: " at EOF: syntax error", + }, + { + name: "unsupported api", + code: `function envoy_on_response(response_handle) + -- Sets the content-type. + response_handle:headers():replace("content-type", "text/html") + local last + for chunk in response_handle:bodyChunks() do + -- Clears each received chunk. + chunk:setBytes("") + last = chunk + -- invalid syntax as there is no end for the for loop + + last:setBytes("Not Found") + end`, + validation: egv1a1.LuaValidationDisabled, + expectedErrSubstring: "", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - l := NewLuaValidator(tt.code) + l := NewLuaValidator(tt.code, tt.validation) if err := l.Validate(); err != nil && tt.expectedErrSubstring == "" { t.Errorf("Unexpected error: %v", err) } else if err != nil && !strings.Contains(err.Error(), tt.expectedErrSubstring) { diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua-validation-disabled.in.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua-validation-disabled.in.yaml index 86178c9b5c..ecfffbcfbb 100644 --- a/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua-validation-disabled.in.yaml +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua-validation-disabled.in.yaml @@ -46,14 +46,18 @@ envoyextensionpolicies: kind: EnvoyExtensionPolicy metadata: namespace: default - name: policy-for-http-route # Invalid Lua but still gets accepted + name: policy-for-http-route spec: targetRef: group: gateway.networking.k8s.io kind: HTTPRoute name: httproute-1 lua: - - type: Inline - inline: "function envoy_on_response(response_handle) - response_handle:UnknownApi() - end" + - type: Inline # Invalid Lua syntax (missing then keyword in if statement) but should be accepted + inline: | + function envoy_on_response(response_handle) + local value = 10 + if value > 5 + print("Value is greater than 5") + end + end 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 1803ccef52..b73d1a359b 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 @@ -7,7 +7,12 @@ envoyExtensionPolicies: namespace: default spec: lua: - - inline: function envoy_on_response(response_handle) response_handle:UnknownApi() + - inline: | + function envoy_on_response(response_handle) + local value = 10 + if value > 5 + print("Value is greater than 5") + end end type: Inline targetRef: @@ -181,7 +186,12 @@ xdsIR: weight: 1 envoyExtensions: luas: - - Code: function envoy_on_response(response_handle) response_handle:UnknownApi() + - Code: | + function envoy_on_response(response_handle) + local value = 10 + if value > 5 + print("Value is greater than 5") + end end Name: envoyextensionpolicy/default/policy-for-http-route/lua/0 hostname: www.example.com diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua-validation-syntax.in.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua-validation-syntax.in.yaml new file mode 100644 index 0000000000..17fb6fca65 --- /dev/null +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua-validation-syntax.in.yaml @@ -0,0 +1,113 @@ +envoyProxyForGatewayClass: + apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyProxy + metadata: + namespace: envoy-gateway-system + name: test + spec: + luaValidation: Syntax +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - www.example.com + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/foo" + backendRefs: + - name: service-1 + port: 8080 +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-2 + spec: + hostnames: + - www.example.com + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/foo" + backendRefs: + - name: service-1 + port: 8080 +envoyextensionpolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + namespace: default + name: policy-for-http-route + spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + lua: + - type: Inline # Lua with external library and UnknownApi() call but correct syntax so should be accepted + inline: | + local json = require("json") + function envoy_on_response(response_handle) + local content_type = response_handle:headers():get("content-type") + if content_type and string.find(content_type, "application/json", 1, true) then + response_handle:body():setBytes(0, response_handle:body():length()) + response_handle:UnknownApi() + local response_body = response_handle:body():getBytes(0, response_handle:body():length()) + if response_body and #response_body > 0 then + local parsed_json = json.decode(response_body) + if type(parsed_json) == "table" then + response_handle:logInfo("Successfully parsed JSON response.") + else + response_handle:logWarn("Parsed JSON is not a table, or unexpected format.") + end + end + end + return envoy.lua.ResponseStatus.Continue + end +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + namespace: default + name: policy-for-http-route-2 + spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-2 + lua: + - type: Inline # Invalid Lua syntax (missing then keyword in if statement) so should be rejected + inline: | + function envoy_on_response(response_handle) + local value = 10 + if value > 5 + print("Value is greater than 5") + end + end 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 new file mode 100644 index 0000000000..7f489f7621 --- /dev/null +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua-validation-syntax.out.yaml @@ -0,0 +1,342 @@ +envoyExtensionPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + creationTimestamp: null + name: policy-for-http-route + namespace: default + spec: + lua: + - inline: | + local json = require("json") + function envoy_on_response(response_handle) + local content_type = response_handle:headers():get("content-type") + if content_type and string.find(content_type, "application/json", 1, true) then + response_handle:body():setBytes(0, response_handle:body():length()) + response_handle:UnknownApi() + local response_body = response_handle:body():getBytes(0, response_handle:body():length()) + if response_body and #response_body > 0 then + local parsed_json = json.decode(response_body) + if type(parsed_json) == "table" then + response_handle:logInfo("Successfully parsed JSON response.") + else + response_handle:logWarn("Parsed JSON is not a table, or unexpected format.") + end + end + end + return envoy.lua.ResponseStatus.Continue + end + type: Inline + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + creationTimestamp: null + name: policy-for-http-route-2 + namespace: default + spec: + lua: + - inline: | + function envoy_on_response(response_handle) + local value = 10 + if value > 5 + print("Value is greater than 5") + end + end + type: Inline + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-2 + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + conditions: + - lastTransitionTime: null + message: |- + Lua: validation failed for lua body in policy with name envoyextensionpolicy/default/policy-for-http-route-2/lua/0: failed to validate with envoy_on_response: line:617(column:9) near 'print': syntax error + . + reason: Invalid + status: "False" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 2 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - www.example.com + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + value: /foo + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-2 + namespace: default + spec: + hostnames: + - www.example.com + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + value: /foo + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + config: + apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyProxy + metadata: + creationTimestamp: null + name: test + namespace: envoy-gateway-system + spec: + logging: {} + luaValidation: Syntax + status: {} + listeners: + - address: null + name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + ownerReference: + kind: GatewayClass + name: envoy-gateway-class + name: envoy-gateway/gateway-1 + namespace: envoy-gateway-system +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + metadata: + name: service-1 + namespace: default + sectionName: "8080" + name: httproute/default/httproute-1/rule/0/backend/0 + protocol: HTTP + weight: 1 + envoyExtensions: + luas: + - Code: | + local json = require("json") + function envoy_on_response(response_handle) + local content_type = response_handle:headers():get("content-type") + if content_type and string.find(content_type, "application/json", 1, true) then + response_handle:body():setBytes(0, response_handle:body():length()) + response_handle:UnknownApi() + local response_body = response_handle:body():getBytes(0, response_handle:body():length()) + if response_body and #response_body > 0 then + local parsed_json = json.decode(response_body) + if type(parsed_json) == "table" then + response_handle:logInfo("Successfully parsed JSON response.") + else + response_handle:logWarn("Parsed JSON is not a table, or unexpected format.") + end + end + end + return envoy.lua.ResponseStatus.Continue + end + Name: envoyextensionpolicy/default/policy-for-http-route/lua/0 + hostname: www.example.com + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/www_example_com + pathMatch: + distinct: false + name: "" + prefix: /foo + - destination: + metadata: + kind: HTTPRoute + name: httproute-2 + namespace: default + name: httproute/default/httproute-2/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + metadata: + name: service-1 + namespace: default + sectionName: "8080" + name: httproute/default/httproute-2/rule/0/backend/0 + protocol: HTTP + weight: 1 + directResponse: + statusCode: 500 + hostname: www.example.com + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-2 + namespace: default + name: httproute/default/httproute-2/rule/0/match/0/www_example_com + pathMatch: + distinct: false + name: "" + prefix: /foo + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua.in.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua.in.yaml index e47c317a3c..acaf4386cd 100644 --- a/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua.in.yaml +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua.in.yaml @@ -72,14 +72,29 @@ envoyextensionpolicies: kind: EnvoyExtensionPolicy metadata: namespace: default - name: policy-for-http-route # Invalid Lua + name: policy-for-http-route spec: targetRef: group: gateway.networking.k8s.io kind: HTTPRoute name: httproute-1 lua: - - type: Inline - inline: "function envoy_on_response(response_handle) - response_handle:UnknownApi() - end" + - type: Inline # Lua with external library and UnknownApi() call should be rejected + inline: | + function envoy_on_response(response_handle) + local content_type = response_handle:headers():get("content-type") + if content_type and string.find(content_type, "application/json", 1, true) then + response_handle:body():setBytes(0, response_handle:body():length()) + response_handle:UnknownApi() + local response_body = response_handle:body():getBytes(0, response_handle:body():length()) + if response_body and #response_body > 0 then + local parsed_json = json.decode(response_body) + if type(parsed_json) == "table" then + response_handle:logInfo("Successfully parsed JSON response.") + else + response_handle:logWarn("Parsed JSON is not a table, or unexpected format.") + end + end + end + return envoy.lua.ResponseStatus.Continue + end diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua.out.yaml index 4747ef8a9b..71665e2863 100644 --- a/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua.out.yaml +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua.out.yaml @@ -7,7 +7,23 @@ envoyExtensionPolicies: namespace: default spec: lua: - - inline: function envoy_on_response(response_handle) response_handle:UnknownApi() + - inline: | + function envoy_on_response(response_handle) + local content_type = response_handle:headers():get("content-type") + if content_type and string.find(content_type, "application/json", 1, true) then + response_handle:body():setBytes(0, response_handle:body():length()) + response_handle:UnknownApi() + local response_body = response_handle:body():getBytes(0, response_handle:body():length()) + if response_body and #response_body > 0 then + local parsed_json = json.decode(response_body) + if type(parsed_json) == "table" then + response_handle:logInfo("Successfully parsed JSON response.") + else + response_handle:logWarn("Parsed JSON is not a table, or unexpected format.") + end + end + end + return envoy.lua.ResponseStatus.Continue end type: Inline targetRef: @@ -25,9 +41,9 @@ envoyExtensionPolicies: conditions: - lastTransitionTime: null message: "Lua: validation failed for lua body in policy with name envoyextensionpolicy/default/policy-for-http-route/lua/0: - failed to mock run envoy_on_response: :614: attempt to call a non-function - object\nstack traceback:\n\t:614: in function 'envoy_on_response'\n\t:615: - in main chunk\n\t[G]: ?." + failed to validate with envoy_on_response: :629: attempt to index + a non-table object(nil) with key 'lua'\nstack traceback:\n\t:629: + in function 'envoy_on_response'\n\t:632: in main chunk\n\t[G]: ?." reason: Invalid status: "False" type: Accepted diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index 1c9a02a7d2..fd48b7154b 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -3167,7 +3167,8 @@ _Appears in:_ | Value | Description | | ----- | ----------- | | `Strict` | LuaValidationStrict is the default level and checks for issues during script execution.
Recommended if your scripts only use the standard Envoy Lua stream handle API.
For supported APIs, see: https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/lua_filter#stream-handle-api
| -| `Disabled` | LuaValidationDisabled disables all validation of Lua scripts.
Scripts will be accepted and executed without any validation checks.
This is not recommended unless your scripts import libraries that are not supported by Lua runtime validation.
| +| `Syntax` | LuaValidationSyntax checks for syntax errors in the Lua script.
Note that this is not a full runtime validation and does not check for issues during script execution.
This is recommended if your scripts use external libraries that are not supported by Lua runtime validation.
| +| `Disabled` | LuaValidationDisabled disables all validations of Lua scripts.
Scripts will be accepted and executed without any validation checks.
This is not recommended unless both runtime and syntax validations are failing unexpectedly.
| #### LuaValueType