From f168256f5a3b04f7e7fe09fab238eea45a24bff8 Mon Sep 17 00:00:00 2001 From: Rudrakh Panigrahi Date: Sat, 13 Jun 2026 11:39:08 +0530 Subject: [PATCH 1/3] feat: whitelist for allowed env and paths for lua Signed-off-by: Rudrakh Panigrahi --- api/v1alpha1/envoyproxy_types.go | 73 +++ api/v1alpha1/zz_generated.deepcopy.go | 55 ++ .../gateway.envoyproxy.io_envoyproxies.yaml | 69 +++ .../gateway.envoyproxy.io_envoyproxies.yaml | 69 +++ .../gatewayapi/luavalidator/lua_validator.go | 69 ++- .../luavalidator/lua_validator_test.go | 540 +++++++----------- internal/gatewayapi/luavalidator/security.lua | 102 ++-- internal/gatewayapi/status/envoyproxy.go | 25 + internal/gatewayapi/status/envoyproxy_test.go | 53 ++ ...th-invalid-lua-validation-disabled.in.yaml | 12 + ...h-invalid-lua-validation-disabled.out.yaml | 65 ++- ...with-invalid-lua-validation-syntax.in.yaml | 12 + ...ith-invalid-lua-validation-syntax.out.yaml | 65 ++- ...npolicy-with-lua-validation-config.in.yaml | 76 +++ ...policy-with-lua-validation-config.out.yaml | 288 ++++++++++ internal/gatewayapi/translator.go | 11 + ...20-lua-validation-allowlist-fail-closed.md | 1 + site/content/en/latest/api/extension_types.md | 38 +- test/cel-validation/envoyproxy_test.go | 98 ++++ test/helm/gateway-crds-helm/all.out.yaml | 69 +++ test/helm/gateway-crds-helm/e2e.out.yaml | 69 +++ .../envoy-gateway-crds.out.yaml | 69 +++ 22 files changed, 1542 insertions(+), 386 deletions(-) create mode 100644 internal/gatewayapi/status/envoyproxy_test.go create mode 100644 internal/gatewayapi/testdata/envoyextensionpolicy-with-lua-validation-config.in.yaml create mode 100644 internal/gatewayapi/testdata/envoyextensionpolicy-with-lua-validation-config.out.yaml create mode 100644 release-notes/current/breaking_changes/9220-lua-validation-allowlist-fail-closed.md diff --git a/api/v1alpha1/envoyproxy_types.go b/api/v1alpha1/envoyproxy_types.go index a4ef2ee57a..2a203c6ea3 100644 --- a/api/v1alpha1/envoyproxy_types.go +++ b/api/v1alpha1/envoyproxy_types.go @@ -35,6 +35,7 @@ type EnvoyProxy struct { } // EnvoyProxySpec defines the desired state of EnvoyProxy. +// +kubebuilder:validation:XValidation:rule="!has(self.luaValidation) || !has(self.luaValidationConfig)",message="only one of luaValidation or luaValidationConfig may be set" type EnvoyProxySpec struct { // Provider defines the desired resource provider and provider-specific configuration. // If unspecified, the "Kubernetes" resource provider is used with default configuration @@ -186,9 +187,19 @@ type EnvoyProxySpec struct { // LuaValidation determines strictness of the Lua script validation for Lua EnvoyExtensionPolicies // Default: Strict + // + // Deprecated: Use LuaValidationConfig.Type instead. This field will be removed in a future release. // +optional LuaValidation *LuaValidation `json:"luaValidation,omitempty"` + // LuaValidationConfig configures how Lua scripts from EnvoyExtensionPolicy resources are + // validated in the gateway controller. It selects the validation mode and, for the Strict + // mode, defines the filesystem paths and environment variables the scripts are permitted to + // access during validation. + // + // +optional + LuaValidationConfig *LuaValidationConfig `json:"luaValidationConfig,omitempty"` + // DynamicModules defines the set of dynamic modules that are allowed to be // used by EnvoyExtensionPolicy resources and dynamic module load balancer // policies. Each entry registers a module by a logical name and specifies @@ -250,6 +261,64 @@ const ( LuaValidationDisabled LuaValidation = "Disabled" ) +// LuaValidationConfig configures how Lua scripts from EnvoyExtensionPolicy resources are validated +// in the gateway controller. +// +// +union +// +kubebuilder:validation:XValidation:rule="!has(self.strict) || !has(self.type) || self.type == 'Strict'",message="strict can only be set when type is Strict" +type LuaValidationConfig struct { + // Type determines the strictness of the Lua script validation. + // Default: Strict + // + // +unionDiscriminator + // +kubebuilder:default=Strict + // +optional + Type *LuaValidation `json:"type,omitempty"` + + // Strict configures the security sandbox that the Strict validation mode executes Lua scripts + // in, defining the filesystem paths and environment variables the scripts are permitted to + // access during validation. + // + // It has no effect for the InsecureSyntax or Disabled modes, which do not execute the security + // sandbox. + // + // +optional + Strict *StrictValidation `json:"strict,omitempty"` +} + +// StrictValidation defines the configuration that Strict Lua validation runs with. +// +// This configuration only applies to the Strict validation mode; it has no effect on the +// InsecureSyntax and Disabled modes. +type StrictValidation struct { + // AllowedPaths is the list of filesystem path prefixes that Lua scripts are permitted to + // access during validation (via io.open, io.input, io.output, io.lines, os.remove, os.rename). + // A path is allowed when it equals an entry or is contained within an entry's subtree + // (e.g. "/tmp" allows "/tmp/file.txt"). Paths are normalized (separators collapsed, made + // absolute) before matching, and any "." or ".." traversal segment is always rejected. + // When empty, all filesystem access is denied. Blank or whitespace-only entries are rejected, + // as they would otherwise match every path and disable the sandbox. + // + // +kubebuilder:validation:MaxItems=64 + // +kubebuilder:validation:items:MinLength=1 + // +kubebuilder:validation:items:MaxLength=4096 + // +kubebuilder:validation:XValidation:rule="self.all(p, p.trim() != '')",message="allowedPaths entries must not be blank or whitespace-only" + // +optional + AllowedPaths []string `json:"allowedPaths,omitempty"` + + // AllowedEnvVars is the list of environment variable names that Lua scripts are permitted to + // access during validation (via os.getenv, os.setenv). Matching is exact and case-sensitive. + // When empty, access to all environment variables is denied. Blank or whitespace-only entries + // are rejected. + // + // +kubebuilder:validation:MaxItems=64 + // +kubebuilder:validation:items:MinLength=1 + // +kubebuilder:validation:items:MaxLength=256 + // +kubebuilder:validation:XValidation:rule="self.all(e, e.trim() != '')",message="allowedEnvVars entries must not be blank or whitespace-only" + // +optional + AllowedEnvVars []string `json:"allowedEnvVars,omitempty"` +} + // RoutingType defines the type of routing of this Envoy proxy. type RoutingType string @@ -675,6 +744,8 @@ type EnvoyProxyConditionType string const ( EnvoyProxyConditionAccepted EnvoyProxyConditionType = "Accepted" + + EnvoyProxyConditionWarning EnvoyProxyConditionType = "Warning" ) type EnvoyProxyConditionReason string @@ -683,6 +754,8 @@ const ( EnvoyProxyReasonAccepted EnvoyProxyConditionReason = "Accepted" EnvoyProxyReasonInvalidParameters EnvoyProxyConditionReason = "InvalidParameters" + + EnvoyProxyReasonDeprecatedField EnvoyProxyConditionReason = "DeprecatedField" ) // +kubebuilder:object:root=true diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 6d7268f027..be3a81df3c 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -3294,6 +3294,11 @@ func (in *EnvoyProxySpec) DeepCopyInto(out *EnvoyProxySpec) { *out = new(LuaValidation) **out = **in } + if in.LuaValidationConfig != nil { + in, out := &in.LuaValidationConfig, &out.LuaValidationConfig + *out = new(LuaValidationConfig) + (*in).DeepCopyInto(*out) + } if in.DynamicModules != nil { in, out := &in.DynamicModules, &out.DynamicModules *out = make([]DynamicModuleEntry, len(*in)) @@ -6006,6 +6011,31 @@ func (in *Lua) DeepCopy() *Lua { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LuaValidationConfig) DeepCopyInto(out *LuaValidationConfig) { + *out = *in + if in.Type != nil { + in, out := &in.Type, &out.Type + *out = new(LuaValidation) + **out = **in + } + if in.Strict != nil { + in, out := &in.Strict, &out.Strict + *out = new(StrictValidation) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LuaValidationConfig. +func (in *LuaValidationConfig) DeepCopy() *LuaValidationConfig { + if in == nil { + return nil + } + out := new(LuaValidationConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MethodMatch) DeepCopyInto(out *MethodMatch) { *out = *in @@ -8266,6 +8296,31 @@ func (in *StatusCodeRange) DeepCopy() *StatusCodeRange { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StrictValidation) DeepCopyInto(out *StrictValidation) { + *out = *in + if in.AllowedPaths != nil { + in, out := &in.AllowedPaths, &out.AllowedPaths + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.AllowedEnvVars != nil { + in, out := &in.AllowedEnvVars, &out.AllowedEnvVars + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StrictValidation. +func (in *StrictValidation) DeepCopy() *StrictValidation { + if in == nil { + return nil + } + out := new(StrictValidation) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *StringMatch) DeepCopyInto(out *StringMatch) { *out = *in diff --git a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyproxies.yaml b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyproxies.yaml index c6ad2cbf8a..3769daf474 100644 --- a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyproxies.yaml +++ b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyproxies.yaml @@ -773,11 +773,77 @@ spec: description: |- LuaValidation determines strictness of the Lua script validation for Lua EnvoyExtensionPolicies Default: Strict + + Deprecated: Use LuaValidationConfig.Type instead. This field will be removed in a future release. enum: - Strict - InsecureSyntax - Disabled type: string + luaValidationConfig: + description: |- + LuaValidationConfig configures how Lua scripts from EnvoyExtensionPolicy resources are + validated in the gateway controller. It selects the validation mode and, for the Strict + mode, defines the filesystem paths and environment variables the scripts are permitted to + access during validation. + properties: + strict: + description: |- + Strict configures the security sandbox that the Strict validation mode executes Lua scripts + in, defining the filesystem paths and environment variables the scripts are permitted to + access during validation. + + It has no effect for the InsecureSyntax or Disabled modes, which do not execute the security + sandbox. + properties: + allowedEnvVars: + description: |- + AllowedEnvVars is the list of environment variable names that Lua scripts are permitted to + access during validation (via os.getenv, os.setenv). Matching is exact and case-sensitive. + When empty, access to all environment variables is denied. Blank or whitespace-only entries + are rejected. + items: + maxLength: 256 + minLength: 1 + type: string + maxItems: 64 + type: array + x-kubernetes-validations: + - message: allowedEnvVars entries must not be blank or whitespace-only + rule: self.all(e, e.trim() != '') + allowedPaths: + description: |- + AllowedPaths is the list of filesystem path prefixes that Lua scripts are permitted to + access during validation (via io.open, io.input, io.output, io.lines, os.remove, os.rename). + A path is allowed when it equals an entry or is contained within an entry's subtree + (e.g. "/tmp" allows "/tmp/file.txt"). Paths are normalized (separators collapsed, made + absolute) before matching, and any "." or ".." traversal segment is always rejected. + When empty, all filesystem access is denied. Blank or whitespace-only entries are rejected, + as they would otherwise match every path and disable the sandbox. + items: + maxLength: 4096 + minLength: 1 + type: string + maxItems: 64 + type: array + x-kubernetes-validations: + - message: allowedPaths entries must not be blank or whitespace-only + rule: self.all(p, p.trim() != '') + type: object + type: + default: Strict + description: |- + Type determines the strictness of the Lua script validation. + Default: Strict + enum: + - Strict + - InsecureSyntax + - Disabled + type: string + type: object + x-kubernetes-validations: + - message: strict can only be set when type is Strict + rule: '!has(self.strict) || !has(self.type) || self.type == ''Strict''' mergeGateways: description: |- MergeGateways defines if Gateway resources should be merged onto the same Envoy Proxy Infrastructure. @@ -18235,6 +18301,9 @@ spec: rule: '!(has(self.samplingRate) && has(self.samplingFraction))' type: object type: object + x-kubernetes-validations: + - message: only one of luaValidation or luaValidationConfig may be set + rule: '!has(self.luaValidation) || !has(self.luaValidationConfig)' status: description: EnvoyProxyStatus defines the actual state of EnvoyProxy. properties: diff --git a/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml b/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml index 9a26adb2f4..335b4fd356 100644 --- a/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml +++ b/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml @@ -772,11 +772,77 @@ spec: description: |- LuaValidation determines strictness of the Lua script validation for Lua EnvoyExtensionPolicies Default: Strict + + Deprecated: Use LuaValidationConfig.Type instead. This field will be removed in a future release. enum: - Strict - InsecureSyntax - Disabled type: string + luaValidationConfig: + description: |- + LuaValidationConfig configures how Lua scripts from EnvoyExtensionPolicy resources are + validated in the gateway controller. It selects the validation mode and, for the Strict + mode, defines the filesystem paths and environment variables the scripts are permitted to + access during validation. + properties: + strict: + description: |- + Strict configures the security sandbox that the Strict validation mode executes Lua scripts + in, defining the filesystem paths and environment variables the scripts are permitted to + access during validation. + + It has no effect for the InsecureSyntax or Disabled modes, which do not execute the security + sandbox. + properties: + allowedEnvVars: + description: |- + AllowedEnvVars is the list of environment variable names that Lua scripts are permitted to + access during validation (via os.getenv, os.setenv). Matching is exact and case-sensitive. + When empty, access to all environment variables is denied. Blank or whitespace-only entries + are rejected. + items: + maxLength: 256 + minLength: 1 + type: string + maxItems: 64 + type: array + x-kubernetes-validations: + - message: allowedEnvVars entries must not be blank or whitespace-only + rule: self.all(e, e.trim() != '') + allowedPaths: + description: |- + AllowedPaths is the list of filesystem path prefixes that Lua scripts are permitted to + access during validation (via io.open, io.input, io.output, io.lines, os.remove, os.rename). + A path is allowed when it equals an entry or is contained within an entry's subtree + (e.g. "/tmp" allows "/tmp/file.txt"). Paths are normalized (separators collapsed, made + absolute) before matching, and any "." or ".." traversal segment is always rejected. + When empty, all filesystem access is denied. Blank or whitespace-only entries are rejected, + as they would otherwise match every path and disable the sandbox. + items: + maxLength: 4096 + minLength: 1 + type: string + maxItems: 64 + type: array + x-kubernetes-validations: + - message: allowedPaths entries must not be blank or whitespace-only + rule: self.all(p, p.trim() != '') + type: object + type: + default: Strict + description: |- + Type determines the strictness of the Lua script validation. + Default: Strict + enum: + - Strict + - InsecureSyntax + - Disabled + type: string + type: object + x-kubernetes-validations: + - message: strict can only be set when type is Strict + rule: '!has(self.strict) || !has(self.type) || self.type == ''Strict''' mergeGateways: description: |- MergeGateways defines if Gateway resources should be merged onto the same Envoy Proxy Infrastructure. @@ -18234,6 +18300,9 @@ spec: rule: '!(has(self.samplingRate) && has(self.samplingFraction))' type: object type: object + x-kubernetes-validations: + - message: only one of luaValidation or luaValidationConfig may be set + rule: '!has(self.luaValidation) || !has(self.luaValidationConfig)' status: description: EnvoyProxyStatus defines the actual state of EnvoyProxy. properties: diff --git a/internal/gatewayapi/luavalidator/lua_validator.go b/internal/gatewayapi/luavalidator/lua_validator.go index fbdc0316d2..3f23e8e034 100644 --- a/internal/gatewayapi/luavalidator/lua_validator.go +++ b/internal/gatewayapi/luavalidator/lua_validator.go @@ -82,14 +82,75 @@ func (l *LuaValidator) validate(code string) error { } } -// getLuaValidation returns the Lua validation level, defaulting to strict if not configured +// getLuaValidation returns the Lua validation level, defaulting to strict if not configured. +// The union LuaValidationConfig.Type takes precedence over the deprecated LuaValidation field. func (l *LuaValidator) getLuaValidation() egv1a1.LuaValidation { - if l.envoyProxy != nil && l.envoyProxy.Spec.LuaValidation != nil { - return *l.envoyProxy.Spec.LuaValidation + if l.envoyProxy != nil { + if cfg := l.envoyProxy.Spec.LuaValidationConfig; cfg != nil && cfg.Type != nil { + return *cfg.Type + } + if l.envoyProxy.Spec.LuaValidation != nil { + return *l.envoyProxy.Spec.LuaValidation + } } return egv1a1.LuaValidationStrict } +// allowlistData generates the Lua source consumed by security.lua: the path allowlist as a list and +// the env var allowlist as a map (name -> true). The allowlist is fail-closed; an unconfigured +// EnvoyProxy yields empty tables, denying all access. +func (l *LuaValidator) allowlistData() string { + var paths, envVars []string + if l.envoyProxy != nil { + if cfg := l.envoyProxy.Spec.LuaValidationConfig; cfg != nil && cfg.Strict != nil { + paths = cfg.Strict.AllowedPaths + envVars = cfg.Strict.AllowedEnvVars + } + } + + var b strings.Builder + + b.WriteString("__lua_allowed_paths = {") + for i, p := range paths { + if i > 0 { + b.WriteString(", ") + } + b.WriteString(luaStringLiteral(p)) + } + b.WriteString("}\n") + + b.WriteString("__lua_allowed_env_vars = {") + for i, e := range envVars { + if i > 0 { + b.WriteString(", ") + } + // Spaces around the key are required: "[[[..." would lex as a long-string opener. + b.WriteString("[ ") + b.WriteString(luaStringLiteral(e)) + b.WriteString(" ] = true") + } + b.WriteString("}\n") + + return b.String() +} + +// luaStringLiteral encodes s as a Lua long-bracket literal ([==[...]==]) with an equals level that +// avoids any closing delimiter in s. Long brackets don't interpret escapes, so spec values are +// passed verbatim as data and cannot inject Lua. +func luaStringLiteral(s string) string { + level := 0 + for strings.Contains(s, "]"+strings.Repeat("=", level)+"]") { + level++ + } + eq := strings.Repeat("=", level) + // Lua strips a leading newline after the opening bracket; re-add it to preserve such values. + prefix := "" + if strings.HasPrefix(s, "\n") { + prefix = "\n" + } + return "[" + eq + "[" + prefix + s + "]" + eq + "]" +} + // newLuaState creates a new Lua state with global settings and resource limits applied // Returns the Lua state and a cancel function that must be called when done func (l *LuaValidator) newLuaState() (*lua.LState, context.CancelFunc) { @@ -123,6 +184,8 @@ func (l *LuaValidator) runLua(code string) error { // Execute mocks first (trusted code, needs setmetatable, defines StreamHandle, etc.) _ = L.DoString(mockData) + // Inject the allowlists before security.lua, which reads and then clears them. + _ = L.DoString(l.allowlistData()) // Execute Lua security wrappers (trusted code) to protect the gateway controller // See security advisory: https://github.com/envoyproxy/gateway/security/advisories/GHSA-xrwg-mqj6-6m22 _ = L.DoString(securityData) diff --git a/internal/gatewayapi/luavalidator/lua_validator_test.go b/internal/gatewayapi/luavalidator/lua_validator_test.go index 497b27d847..f7839fb6e2 100644 --- a/internal/gatewayapi/luavalidator/lua_validator_test.go +++ b/internal/gatewayapi/luavalidator/lua_validator_test.go @@ -209,431 +209,317 @@ func Test_BasicValidation(t *testing.T) { } } -func Test_block_or_sanitize_io(t *testing.T) { - type testCase struct { +// allowlistProxy returns an EnvoyProxy configured with the given Lua validation allowlists. +func allowlistProxy(paths, envVars []string) *egv1a1.EnvoyProxy { + return &egv1a1.EnvoyProxy{ + Spec: egv1a1.EnvoyProxySpec{ + LuaValidationConfig: &egv1a1.LuaValidationConfig{ + Strict: &egv1a1.StrictValidation{ + AllowedPaths: paths, + AllowedEnvVars: envVars, + }, + }, + }, + } +} + +// Test_io_path_allowlist verifies the filesystem allowlist for the sanitized io functions. +// The allowlist is fail-closed: only paths under /tmp are permitted; everything else is denied. +// Path traversal segments are always rejected, regardless of the allowlist. +func Test_io_path_allowlist(t *testing.T) { + proxy := allowlistProxy([]string{"/tmp"}, nil) + + tests := []struct { name string code string expectedErrSubstring string - } - tests := []testCase{ - // io.open tests + }{ { - name: "io.open critical path /certs", - code: `function envoy_on_response(response_handle) - local file = io.open("/certs/tls.crt", "w") - if file then file:close() end - end`, - expectedErrSubstring: "critical path", + name: "io.open allowed /tmp", + code: `function envoy_on_response(h) local f = io.open("/tmp/x", "w") if f then f:close() end end`, + expectedErrSubstring: "", }, { - name: "io.open non-critical path /tmp", - code: `function envoy_on_response(response_handle) - local file = io.open("/tmp/tls.crt", "w") - if file then - file:write("test") - file:close() - end - end`, + name: "io.open allowed via subtree /tmp/sub/x", + code: `function envoy_on_response(h) local f = io.open("/tmp/sub/x", "w") if f then f:close() end end`, expectedErrSubstring: "", }, { - name: "io.open /etc/passwd", - code: `function envoy_on_response(response_handle) - local file = io.open("/etc/passwd", "r") - if file then file:close() end - end`, - expectedErrSubstring: "critical path", + name: "io.open denied /etc/passwd", + code: `function envoy_on_response(h) io.open("/etc/passwd", "r") end`, + expectedErrSubstring: "io.open restricted for param", }, { - name: "io.open /proc/self/environ", - code: `function envoy_on_response(response_handle) - local file = io.open("/proc/self/environ", "r") - if file then file:close() end - end`, - expectedErrSubstring: "critical path", + name: "io.open denied path outside allowlist", + code: `function envoy_on_response(h) io.open("/tmpfoo/x", "r") end`, + expectedErrSubstring: "io.open restricted for param", }, + // Path normalization: relative, backslash, and multi-slash forms must normalize + // consistently before the allowlist match. { - name: "io.open /sys/kernel", - code: `function envoy_on_response(response_handle) - local file = io.open("/sys/kernel", "r") - if file then file:close() end - end`, - expectedErrSubstring: "critical path", + name: "io.open relative path normalizes under allowed root", + code: `function envoy_on_response(h) local f = io.open("tmp/x", "r") if f then f:close() end end`, + expectedErrSubstring: "", }, { - name: "io.open /var/run/secrets/token", - code: `function envoy_on_response(response_handle) - local file = io.open("/var/run/secrets/token", "r") - if file then file:close() end - end`, - expectedErrSubstring: "critical path", + name: "io.open allowed double-slash //tmp/x", + code: `function envoy_on_response(h) local f = io.open("//tmp/x", "w") if f then f:close() end end`, + expectedErrSubstring: "", }, { - name: "io.open relative path etc/passwd", - code: `function envoy_on_response(response_handle) - local file = io.open("etc/passwd", "r") - if file then file:close() end - end`, - expectedErrSubstring: "critical path", + name: "io.open allowed trailing slash /tmp/", + code: `function envoy_on_response(h) local f = io.open("/tmp/", "r") if f then f:close() end end`, + expectedErrSubstring: "", }, { - name: "io.open path traversal /tmp/../etc/passwd", - code: `function envoy_on_response(response_handle) - local file = io.open("/tmp/../etc/passwd", "r") - if file then file:close() end - end`, - expectedErrSubstring: "path traversals", + name: "io.open denied double-slash //etc/passwd", + code: `function envoy_on_response(h) io.open("//etc/passwd", "r") end`, + expectedErrSubstring: "io.open restricted for param", }, { - name: "io.open path traversal ../etc/passwd", - code: `function envoy_on_response(response_handle) - local file = io.open("../etc/passwd", "r") - if file then file:close() end - end`, - expectedErrSubstring: "path traversals", + name: "io.open denied embedded double-slash /etc//passwd", + code: `function envoy_on_response(h) io.open("/etc//passwd", "r") end`, + expectedErrSubstring: "io.open restricted for param", }, { - name: "io.open relative path certs/tls.crt", - code: `function envoy_on_response(response_handle) - local file = io.open("certs/tls.crt", "r") - if file then file:close() end - end`, - expectedErrSubstring: "critical path", + name: "io.open denied multiple-slash ///etc/passwd", + code: `function envoy_on_response(h) io.open("///etc/passwd", "r") end`, + expectedErrSubstring: "io.open restricted for param", }, { - name: "io.open relative path var/run/secrets/token", - code: `function envoy_on_response(response_handle) - local file = io.open("var/run/secrets/token", "r") - if file then file:close() end - end`, - expectedErrSubstring: "critical path", + name: "io.open denied backslash etc\\passwd", + code: `function envoy_on_response(h) io.open("etc\\passwd", "r") end`, + expectedErrSubstring: "io.open restricted for param", }, { - name: "io.open with backslash etc\\passwd", - code: `function envoy_on_response(response_handle) - local file = io.open("etc\\passwd", "r") - if file then file:close() end - end`, - expectedErrSubstring: "critical path", + name: "io.open denied string concatenation", + code: `function envoy_on_response(h) local p = "/" .. "etc" .. "/" .. "passwd" io.open(p, "r") end`, + expectedErrSubstring: "io.open restricted for param", }, + // Traversal segments are always rejected with a distinct error, regardless of the allowlist. { - name: "io.open path traversal with backslash", - code: `function envoy_on_response(response_handle) - local file = io.open("..\\etc\\passwd", "r") - if file then file:close() end - end`, + name: "io.open traversal rejected even under allowed root", + code: `function envoy_on_response(h) io.open("/tmp/../etc/passwd", "r") end`, expectedErrSubstring: "path traversals", }, { - name: "io.open with string concatenation", - code: `function envoy_on_response(response_handle) - local path = "/" .. "etc" .. "/" .. "passwd" - local file = io.open(path, "r") - if file then file:close() end - end`, - expectedErrSubstring: "critical path", - }, - { - name: "io.open with trailing slash /certs/", - code: `function envoy_on_response(response_handle) - local file = io.open("/certs/", "r") - if file then file:close() end - end`, - expectedErrSubstring: "critical path", - }, - { - name: "io.open double-slash //etc/passwd", - code: `function envoy_on_response(response_handle) - local file = io.open("//etc/passwd", "r") - if file then file:close() end - end`, - expectedErrSubstring: "critical path", - }, - { - name: "io.open double-slash //var/run/secrets/token", - code: `function envoy_on_response(response_handle) - local file = io.open("//var/run/secrets/kubernetes.io/serviceaccount/token", "r") - if file then file:close() end - end`, - expectedErrSubstring: "critical path", - }, - { - name: "io.open run secrets alias /run/secrets/token", - code: `function envoy_on_response(response_handle) - local file = io.open("/run/secrets/kubernetes.io/serviceaccount/token", "r") - if file then file:close() end - end`, - expectedErrSubstring: "critical path", - }, - { - name: "io.open run secrets alias double-slash //run/secrets/token", - code: `function envoy_on_response(response_handle) - local file = io.open("//run/secrets/kubernetes.io/serviceaccount/token", "r") - if file then file:close() end - end`, - expectedErrSubstring: "critical path", - }, - { - name: "io.open multiple-slash ///etc/passwd", - code: `function envoy_on_response(response_handle) - local file = io.open("///etc/passwd", "r") - if file then file:close() end - end`, - expectedErrSubstring: "critical path", - }, - { - name: "io.open embedded double-slash /etc//passwd", - code: `function envoy_on_response(response_handle) - local file = io.open("/etc//passwd", "r") - if file then file:close() end - end`, - expectedErrSubstring: "critical path", - }, - { - name: "io.open double-slash //proc/self/environ", - code: `function envoy_on_response(response_handle) - local file = io.open("//proc/self/environ", "r") - if file then file:close() end - end`, - expectedErrSubstring: "critical path", + name: "io.open traversal rejected ../etc/passwd", + code: `function envoy_on_response(h) io.open("../etc/passwd", "r") end`, + expectedErrSubstring: "path traversals", }, { - name: "io.open double-slash //certs/tls.crt", - code: `function envoy_on_response(response_handle) - local file = io.open("//certs/tls.crt", "r") - if file then file:close() end - end`, - expectedErrSubstring: "critical path", + name: "io.open traversal rejected with backslash", + code: `function envoy_on_response(h) io.open("..\\etc\\passwd", "r") end`, + expectedErrSubstring: "path traversals", }, { - name: "io.open dot segment /etc/./passwd", - code: `function envoy_on_response(response_handle) - local file = io.open("/etc/./passwd", "r") - if file then file:close() end - end`, + name: "io.open traversal rejected dot segment /tmp/./x", + code: `function envoy_on_response(h) io.open("/tmp/./x", "r") end`, expectedErrSubstring: "path traversals", }, { - name: "io.open leading dot ./etc/passwd", - code: `function envoy_on_response(response_handle) - local file = io.open("./etc/passwd", "r") - if file then file:close() end - end`, + name: "io.open traversal rejected leading dot ./tmp/x", + code: `function envoy_on_response(h) io.open("./tmp/x", "r") end`, expectedErrSubstring: "path traversals", }, { - name: "io.open trailing dot /etc/.", - code: `function envoy_on_response(response_handle) - local file = io.open("/etc/.", "r") - if file then file:close() end - end`, + name: "io.open traversal rejected trailing dot /tmp/.", + code: `function envoy_on_response(h) io.open("/tmp/.", "r") end`, expectedErrSubstring: "path traversals", }, { - name: "io.open dot with backslash etc\\.\\passwd", - code: `function envoy_on_response(response_handle) - local file = io.open("etc\\.\\passwd", "r") - if file then file:close() end - end`, + name: "io.open traversal rejected dot with backslash tmp\\.\\x", + code: `function envoy_on_response(h) io.open("tmp\\.\\x", "r") end`, expectedErrSubstring: "path traversals", }, - // io.input tests { - name: "io.input critical path /certs", - code: `function envoy_on_response(response_handle) - io.input("/certs/tls.crt") - end`, - expectedErrSubstring: "critical path", + name: "io.input denied /etc/passwd", + code: `function envoy_on_response(h) io.input("/etc/passwd") end`, + expectedErrSubstring: "io.input restricted for param", }, { - name: "io.input non-critical path /tmp", - code: `function envoy_on_response(response_handle) - local file = io.open("/tmp/tls.crt", "w") - if file then - file:write("test content") - file:close() - end - io.input("/tmp/tls.crt") - end`, - expectedErrSubstring: "", + name: "io.output denied /certs", + code: `function envoy_on_response(h) io.output("/certs/tls.crt") end`, + expectedErrSubstring: "io.output restricted for param", }, { - name: "io.input /etc/passwd", - code: `function envoy_on_response(response_handle) - io.input("/etc/passwd") - end`, - expectedErrSubstring: "critical path", + name: "io.lines denied /etc/passwd", + code: `function envoy_on_response(h) for l in io.lines("/etc/passwd") do end end`, + expectedErrSubstring: "io.lines restricted for param", }, - // io.output tests + } + runAllowlistCases(t, proxy, tests) +} + +// Test_path_allowlist_literal_match ensures allowed prefixes containing Lua pattern magic characters +// (e.g. ".") are matched literally and do not widen the security boundary. +func Test_path_allowlist_literal_match(t *testing.T) { + proxy := allowlistProxy([]string{"/var/lib/app.v1"}, nil) + + tests := []struct { + name string + code string + expectedErrSubstring string + }{ { - name: "io.output critical path /certs", - code: `function envoy_on_response(response_handle) - io.output("/certs/tls.crt") - end`, - expectedErrSubstring: "critical path", + name: "exact allowed entry", + code: `function envoy_on_response(h) local f = io.open("/var/lib/app.v1", "r") if f then f:close() end end`, + expectedErrSubstring: "", }, { - name: "io.output non-critical path /tmp", - code: `function envoy_on_response(response_handle) - io.output("/tmp/tls.crt") - end`, + name: "subtree of allowed entry", + code: `function envoy_on_response(h) local f = io.open("/var/lib/app.v1/data", "r") if f then f:close() end end`, expectedErrSubstring: "", }, - // io.lines tests { - name: "io.lines critical path /certs", - code: `function envoy_on_response(response_handle) - for line in io.lines("/certs/tls.crt") do - response_handle:logInfo(line) - end - end`, - expectedErrSubstring: "critical path", + name: "dot must not match arbitrary character", + code: `function envoy_on_response(h) io.open("/var/lib/appXv1/data", "r") end`, + expectedErrSubstring: "io.open restricted for param", }, + } + runAllowlistCases(t, proxy, tests) +} + +// Test_path_allowlist_blank_entry_denied ensures a blank allowlist entry does not match every path +// and silently disable the sandbox (defense in depth; the CRD also rejects blank entries). +func Test_path_allowlist_blank_entry_denied(t *testing.T) { + proxy := allowlistProxy([]string{"", " ", "/tmp"}, nil) + + tests := []struct { + name string + code string + expectedErrSubstring string + }{ { - name: "io.lines non-critical path /tmp", - code: `function envoy_on_response(response_handle) - for line in io.lines("/tmp/tls.crt") do - response_handle:logInfo(line) - end - end`, - expectedErrSubstring: "", + name: "blank entry does not allow arbitrary path", + code: `function envoy_on_response(h) io.open("/etc/passwd", "r") end`, + expectedErrSubstring: "io.open restricted for param", }, { - name: "io.lines /etc/passwd", - code: `function envoy_on_response(response_handle) - for line in io.lines("/etc/passwd") do - response_handle:logInfo(line) - end - end`, - expectedErrSubstring: "critical path", + name: "real entry still allowed", + code: `function envoy_on_response(h) local f = io.open("/tmp/x", "r") if f then f:close() end end`, + expectedErrSubstring: "", }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - l := NewLuaValidator(tt.code, nil) - if err := l.Validate(); err != nil && tt.expectedErrSubstring == "" { - t.Errorf("Unexpected error: %v", err) - } else if err != nil && !strings.Contains(err.Error(), tt.expectedErrSubstring) { - t.Errorf("Expected substring in error: %v, got error: %v", tt.expectedErrSubstring, err) - } else if err == nil && tt.expectedErrSubstring != "" { - t.Errorf("Expected error with substring: %v", tt.expectedErrSubstring) - } - }) + runAllowlistCases(t, proxy, tests) +} + +// Test_io_denied_by_default ensures that with no allowlist configured, all filesystem access is denied. +func Test_io_denied_by_default(t *testing.T) { + code := `function envoy_on_response(h) io.open("/tmp/x", "w") end` + if err := NewLuaValidator(code, nil).Validate(); err == nil { + t.Errorf("Expected fail-closed denial with no allowlist, got no error") + } else if !strings.Contains(err.Error(), "io.open restricted for param") { + t.Errorf("Expected 'io.open restricted for param', got: %v", err) } } -func Test_block_or_sanitize_os(t *testing.T) { - type testCase struct { +// Test_os_path_allowlist verifies the filesystem allowlist for the sanitized os functions. +// Only /tmp is permitted; os.rename requires both source and destination to be allowed. +func Test_os_path_allowlist(t *testing.T) { + proxy := allowlistProxy([]string{"/tmp"}, nil) + + tests := []struct { name string code string expectedErrSubstring string - } - tests := []testCase{ - // os.remove tests - { - name: "os.remove critical path /certs", - code: `function envoy_on_response(response_handle) - os.remove("/certs/tls.crt") - end`, - expectedErrSubstring: "critical path", - }, + }{ { - name: "os.remove non-critical path /tmp", - code: `function envoy_on_response(response_handle) - os.remove("/tmp/tls.crt") - end`, + name: "os.remove allowed /tmp", + code: `function envoy_on_response(h) os.remove("/tmp/x") end`, expectedErrSubstring: "", }, - // os.rename tests { - name: "os.rename critical path /certs", - code: `function envoy_on_response(response_handle) - os.rename("/certs/tls.crt", "/certs/tls.crt.bak") - end`, - expectedErrSubstring: "critical path", + name: "os.remove denied /certs", + code: `function envoy_on_response(h) os.remove("/certs/tls.crt") end`, + expectedErrSubstring: "os.remove restricted for param", }, { - name: "os.rename non-critical path /tmp", - code: `function envoy_on_response(response_handle) - os.rename("/tmp/tls.crt", "/tmp/tls.crt.bak") - end`, + name: "os.rename allowed both /tmp", + code: `function envoy_on_response(h) os.rename("/tmp/a", "/tmp/b") end`, expectedErrSubstring: "", }, { - name: "os.rename critical source", - code: `function envoy_on_response(response_handle) - os.rename("/certs/tls.crt", "/tmp/tls.crt.bak") - end`, - expectedErrSubstring: "critical path", - }, - { - name: "os.rename critical destination", - code: `function envoy_on_response(response_handle) - os.rename("/tmp/tls.crt", "/certs/tls.crt.bak") - end`, - expectedErrSubstring: "critical path", + name: "os.rename denied source", + code: `function envoy_on_response(h) os.rename("/certs/a", "/tmp/b") end`, + expectedErrSubstring: "os.rename restricted for param", }, { - name: "os.rename to critical path /etc", - code: `function envoy_on_response(response_handle) - os.rename("/tmp/file", "/certs/file") - end`, - expectedErrSubstring: "critical path", + name: "os.rename denied destination", + code: `function envoy_on_response(h) os.rename("/tmp/a", "/certs/b") end`, + expectedErrSubstring: "os.rename restricted for param", }, + } + runAllowlistCases(t, proxy, tests) +} + +// Test_os_env_allowlist verifies the environment variable allowlist (exact, case-sensitive match) +// for os.getenv and os.setenv. +func Test_os_env_allowlist(t *testing.T) { + proxy := allowlistProxy(nil, []string{"LOG_LEVEL"}) + + tests := []struct { + name string + code string + expectedErrSubstring string + }{ { - name: "os.rename from critical path /etc", - code: `function envoy_on_response(response_handle) - os.rename("/certs/file", "/tmp/file") - end`, - expectedErrSubstring: "critical path", + name: "os.getenv allowed LOG_LEVEL", + code: `function envoy_on_response(h) os.getenv("LOG_LEVEL") end`, + expectedErrSubstring: "", }, - // os.getenv tests { - name: "os.getenv critical env var PWD", - code: `function envoy_on_response(response_handle) - local pwd = os.getenv("PWD") - end`, - expectedErrSubstring: "critical environment variable", + name: "os.getenv denied PWD", + code: `function envoy_on_response(h) os.getenv("PWD") end`, + expectedErrSubstring: "os.getenv restricted for param PWD", }, { - name: "os.getenv critical env var pwd (lowercase)", - code: `function envoy_on_response(response_handle) - local pwd = os.getenv("pwd") - end`, - expectedErrSubstring: "critical environment variable", + name: "os.getenv match is case-sensitive", + code: `function envoy_on_response(h) os.getenv("log_level") end`, + expectedErrSubstring: "os.getenv restricted for param log_level", }, - // os.setenv tests { - name: "os.setenv non-critical env var allowed", - code: `function envoy_on_response(response_handle) - os.setenv("TEST", "value") - end`, + name: "os.setenv allowed LOG_LEVEL", + code: `function envoy_on_response(h) os.setenv("LOG_LEVEL", "debug") end`, expectedErrSubstring: "", }, { - name: "os.setenv critical env var PWD", - code: `function envoy_on_response(response_handle) - os.setenv("PWD", "/etc") - end`, - expectedErrSubstring: "setting critical environment variable", - }, - { - name: "os.setenv critical env var pwd (lowercase)", - code: `function envoy_on_response(response_handle) - os.setenv("pwd", "/etc") - end`, - expectedErrSubstring: "setting critical environment variable", + name: "os.setenv denied PWD", + code: `function envoy_on_response(h) os.setenv("PWD", "/etc") end`, + expectedErrSubstring: "os.setenv restricted for param PWD", }, } + runAllowlistCases(t, proxy, tests) +} + +// Test_env_denied_by_default ensures that with no allowlist configured, all env var access is denied. +func Test_env_denied_by_default(t *testing.T) { + code := `function envoy_on_response(h) os.getenv("LOG_LEVEL") end` + if err := NewLuaValidator(code, nil).Validate(); err == nil { + t.Errorf("Expected fail-closed denial with no allowlist, got no error") + } else if !strings.Contains(err.Error(), "os.getenv restricted for param") { + t.Errorf("Expected 'os.getenv restricted for param', got: %v", err) + } +} + +// runAllowlistCases runs a set of allowlist validation cases against the given proxy. +func runAllowlistCases(t *testing.T, proxy *egv1a1.EnvoyProxy, tests []struct { + name string + code string + expectedErrSubstring string +}, +) { + t.Helper() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - l := NewLuaValidator(tt.code, nil) - if err := l.Validate(); err != nil && tt.expectedErrSubstring == "" { + err := NewLuaValidator(tt.code, proxy).Validate() + switch { + case err != nil && tt.expectedErrSubstring == "": t.Errorf("Unexpected error: %v", err) - } else if err != nil && !strings.Contains(err.Error(), tt.expectedErrSubstring) { - t.Errorf("Expected substring in error: %v, got error: %v", tt.expectedErrSubstring, err) - } else if err == nil && tt.expectedErrSubstring != "" { - t.Errorf("Expected error with substring: %v", tt.expectedErrSubstring) + case err != nil && !strings.Contains(err.Error(), tt.expectedErrSubstring): + t.Errorf("Expected substring %q in error, got: %v", tt.expectedErrSubstring, err) + case err == nil && tt.expectedErrSubstring != "": + t.Errorf("Expected error with substring %q, got none", tt.expectedErrSubstring) } }) } diff --git a/internal/gatewayapi/luavalidator/security.lua b/internal/gatewayapi/luavalidator/security.lua index 645c0d71ab..691d9d99d3 100644 --- a/internal/gatewayapi/luavalidator/security.lua +++ b/internal/gatewayapi/luavalidator/security.lua @@ -1,27 +1,20 @@ --- Security sandbox for Lua execution in Envoy Gateway --- Blocks dangerous functions and validates paths to prevent access to sensitive system resources +-- Security sandbox for Lua execution in Envoy Gateway: blocks dangerous functions and enforces a +-- fail-closed allowlist of filesystem paths and environment variables during validation. +-- +-- The allowed sets are injected by the Go validator before this script runs, as the globals +-- `__lua_allowed_paths` (array of path prefixes) and `__lua_allowed_env_vars` (map name -> true). +-- An absent or empty table denies that entire category. -- ============================================================================ --- CRITICAL PATHS +-- ALLOWLISTS (injected by the Go validator; default to empty = deny all) -- ============================================================================ -local critical_paths = { - "/etc", - "/proc", - "/sys", - "/certs", - "/var/run/secrets", - -- "/var/run" is a symlink to "/run" on Debian-derived (distroless) images. - "/run/secrets", -} +local allowed_paths = __lua_allowed_paths or {} +local allowed_env_vars = __lua_allowed_env_vars or {} --- ============================================================================ --- CRITICAL ENVIRONMENT VARIABLES --- ============================================================================ - -local critical_env_vars = { - ["PWD"] = true, -} +-- Remove the injected globals so user code cannot read or mutate the allowlists. +__lua_allowed_paths = nil +__lua_allowed_env_vars = nil -- ============================================================================ -- HELPER FUNCTIONS @@ -31,7 +24,7 @@ local function to_absolute_normalized_path(path) if not path or type(path) ~= "string" then return path end - + local normalized_separators = path:gsub("\\", "/") local collapsed_separators = normalized_separators:gsub("/+", "/") @@ -63,44 +56,51 @@ local function contains_traversal(path) return false end -local function is_critical_path(path) +-- is_allowed_path returns true when the path equals an allowed entry or falls within its subtree. +-- Both sides are normalized so relative, backslash, and double-slash forms match consistently. +-- The subtree check uses plain (non-pattern) string matching so allowed prefixes containing Lua +-- magic characters (e.g. "." in "/var/lib/app.v1") are treated literally and define an exact boundary. +local function is_allowed_path(path) if not path or type(path) ~= "string" then return false end - + local normalized = to_absolute_normalized_path(path) - - for _, critical_path in ipairs(critical_paths) do - local normalized_critical = to_absolute_normalized_path(critical_path) - local escaped_critical = normalized_critical:gsub("%-", "%%-") - - if normalized == normalized_critical or normalized:match("^" .. escaped_critical .. "/") then + + for _, allowed in ipairs(allowed_paths) do + local normalized_allowed = to_absolute_normalized_path(allowed) + + -- Skip blank entries: "" would match every absolute path and disable the sandbox. + if normalized_allowed ~= "" and + (normalized == normalized_allowed + or normalized:find(normalized_allowed .. "/", 1, true) == 1) then return true end end - + return false end -local function validate_path(path) +-- validate_path rejects traversal segments unconditionally, then enforces the path allowlist. +local function validate_path(fn_name, path) if not path or type(path) ~= "string" then return end - + if contains_traversal(path) then error("path traversals are restricted for security") end - - if is_critical_path(path) then - error("access to critical path " .. path .. " is restricted for security") + + if not is_allowed_path(path) then + error(fn_name .. " restricted for param " .. path) end end -local function is_critical_env_var(env_var) - if not env_var or type(env_var) ~= "string" then - return false +-- validate_env_var enforces the env var allowlist (exact, case-sensitive match). +local function validate_env_var(fn_name, env_var) + if not env_var or type(env_var) ~= "string" or allowed_env_vars[env_var] ~= true then + error(fn_name .. " restricted for param " .. tostring(env_var)) end - return critical_env_vars[env_var:upper()] == true end -- ============================================================================ @@ -125,7 +125,7 @@ setmetatable = nil _G = nil -- ============================================================================ --- SANITIZED IO FUNCTIONS (path validation) +-- SANITIZED IO FUNCTIONS (path allowlist) -- ============================================================================ do @@ -135,7 +135,7 @@ do local _unsafe_io_lines = io.lines io.open = function(filename, mode) - validate_path(filename) + validate_path("io.open", filename) return _unsafe_io_open(filename, mode) end @@ -144,7 +144,7 @@ do return _unsafe_io_input() end if type(file) == "string" then - validate_path(file) + validate_path("io.input", file) end return _unsafe_io_input(file) end @@ -154,21 +154,21 @@ do return _unsafe_io_output() end if type(file) == "string" then - validate_path(file) + validate_path("io.output", file) end return _unsafe_io_output(file) end io.lines = function(filename) if filename then - validate_path(filename) + validate_path("io.lines", filename) end return _unsafe_io_lines(filename) end end -- ============================================================================ --- SANITIZED OS FUNCTIONS (path/env var validation) +-- SANITIZED OS FUNCTIONS (path / env var allowlist) -- ============================================================================ do @@ -178,27 +178,23 @@ do local _unsafe_os_setenv = os.setenv os.remove = function(pathname) - validate_path(pathname) + validate_path("os.remove", pathname) return _unsafe_os_remove(pathname) end os.rename = function(oldname, newname) - validate_path(oldname) - validate_path(newname) + validate_path("os.rename", oldname) + validate_path("os.rename", newname) return _unsafe_os_rename(oldname, newname) end os.getenv = function(varname) - if is_critical_env_var(varname) then - error("access to critical environment variable " .. varname .. " is restricted for security") - end + validate_env_var("os.getenv", varname) return _unsafe_os_getenv(varname) end os.setenv = function(varname, value) - if is_critical_env_var(varname) then - error("setting critical environment variable " .. varname .. " is restricted for security") - end + validate_env_var("os.setenv", varname) return _unsafe_os_setenv(varname, value) end end diff --git a/internal/gatewayapi/status/envoyproxy.go b/internal/gatewayapi/status/envoyproxy.go index 9f942d156f..b00ebd957e 100644 --- a/internal/gatewayapi/status/envoyproxy.go +++ b/internal/gatewayapi/status/envoyproxy.go @@ -42,3 +42,28 @@ func UpdateEnvoyProxyStatusAccepted(ep *egv1a1.EnvoyProxy, ancestor *gwapiv1.Par }, }) } + +func SetEnvoyProxyDeprecatedFieldsWarning(ep *egv1a1.EnvoyProxy, ancestor *gwapiv1.ParentReference, deprecatedFields map[string]string) { + if ep == nil || ancestor == nil || len(deprecatedFields) == 0 { + return + } + + cond := newCondition(string(egv1a1.EnvoyProxyConditionWarning), metav1.ConditionTrue, + string(egv1a1.EnvoyProxyReasonDeprecatedField), buildDeprecationWarningMessage(deprecatedFields), ep.Generation) + + for i := range ep.Status.Ancestors { + item := ep.Status.Ancestors[i] + if ancestorRefsEqual(&item.AncestorRef, ancestor) { + ep.Status.Ancestors[i].Conditions = MergeConditions(item.Conditions, cond) + return + } + } + + // ancestor not found, append a new one + ep.Status.Ancestors = append(ep.Status.Ancestors, egv1a1.EnvoyProxyAncestorStatus{ + AncestorRef: *ancestor, + Conditions: []metav1.Condition{ + cond, + }, + }) +} diff --git a/internal/gatewayapi/status/envoyproxy_test.go b/internal/gatewayapi/status/envoyproxy_test.go new file mode 100644 index 0000000000..20ffd4c9d1 --- /dev/null +++ b/internal/gatewayapi/status/envoyproxy_test.go @@ -0,0 +1,53 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +package status + +import ( + "testing" + + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" + + egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" +) + +func TestSetEnvoyProxyDeprecatedFieldsWarning(t *testing.T) { + ancestor := &gwapiv1.ParentReference{Name: gwapiv1.ObjectName("eg")} + + t.Run("sets a warning condition when deprecated fields are used", func(t *testing.T) { + ep := &egv1a1.EnvoyProxy{} + SetEnvoyProxyDeprecatedFieldsWarning(ep, ancestor, map[string]string{ + "spec.luaValidation": "spec.luaValidationConfig.type", + }) + + assert.Len(t, ep.Status.Ancestors, 1) + conds := ep.Status.Ancestors[0].Conditions + assert.Len(t, conds, 1) + assert.Equal(t, string(egv1a1.EnvoyProxyConditionWarning), conds[0].Type) + assert.Equal(t, metav1.ConditionTrue, conds[0].Status) + assert.Equal(t, string(egv1a1.EnvoyProxyReasonDeprecatedField), conds[0].Reason) + assert.Equal(t, "spec.luaValidation is deprecated, use spec.luaValidationConfig.type instead", conds[0].Message) + }) + + t.Run("no-op when no deprecated fields are used", func(t *testing.T) { + ep := &egv1a1.EnvoyProxy{} + SetEnvoyProxyDeprecatedFieldsWarning(ep, ancestor, nil) + assert.Empty(t, ep.Status.Ancestors) + }) + + t.Run("appends the warning alongside an existing Accepted condition on the same ancestor", func(t *testing.T) { + ep := &egv1a1.EnvoyProxy{} + UpdateEnvoyProxyStatusAccepted(ep, ancestor, egv1a1.EnvoyProxyReasonAccepted, "EnvoyProxy has been accepted.") + SetEnvoyProxyDeprecatedFieldsWarning(ep, ancestor, map[string]string{ + "spec.luaValidation": "spec.luaValidationConfig.type", + }) + + assert.Len(t, ep.Status.Ancestors, 1) + conds := ep.Status.Ancestors[0].Conditions + assert.Len(t, conds, 2) + }) +} 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 ecfffbcfbb..a2c4b4ff5d 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 @@ -1,3 +1,15 @@ +gatewayClass: + apiVersion: gateway.networking.k8s.io/v1 + kind: GatewayClass + metadata: + name: envoy-gateway-class + spec: + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parametersRef: + group: gateway.envoyproxy.io + kind: EnvoyProxy + name: test + namespace: envoy-gateway-system envoyProxyForGatewayClass: apiVersion: gateway.envoyproxy.io/v1alpha1 kind: EnvoyProxy 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..36b165ad79 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 @@ -38,6 +38,52 @@ envoyExtensionPolicies: status: "True" type: Warning controllerName: gateway.envoyproxy.io/gatewayclass-controller +envoyProxyForGatewayClass: + apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyProxy + metadata: + name: test + namespace: envoy-gateway-system + spec: + logging: {} + luaValidation: Disabled + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: GatewayClass + name: envoy-gateway-class + conditions: + - lastTransitionTime: null + message: EnvoyProxy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: spec.luaValidation is deprecated, use spec.luaValidationConfig.type + instead + reason: DeprecatedField + status: "True" + type: Warning +gatewayClass: + apiVersion: gateway.networking.k8s.io/v1 + kind: GatewayClass + metadata: + name: envoy-gateway-class + spec: + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parametersRef: + group: gateway.envoyproxy.io + kind: EnvoyProxy + name: test + namespace: envoy-gateway-system + status: + conditions: + - lastTransitionTime: null + message: Valid GatewayClass + reason: Accepted + status: "True" + type: Accepted gateways: - apiVersion: gateway.networking.k8s.io/v1 kind: Gateway @@ -128,7 +174,24 @@ infraIR: spec: logging: {} luaValidation: Disabled - status: {} + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: GatewayClass + name: envoy-gateway-class + conditions: + - lastTransitionTime: null + message: EnvoyProxy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: spec.luaValidation is deprecated, use spec.luaValidationConfig.type + instead + reason: DeprecatedField + status: "True" + type: Warning listeners: - name: envoy-gateway/gateway-1/http ports: 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 index 6bce04172c..3360fffe4a 100644 --- a/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua-validation-syntax.in.yaml +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-invalid-lua-validation-syntax.in.yaml @@ -1,3 +1,15 @@ +gatewayClass: + apiVersion: gateway.networking.k8s.io/v1 + kind: GatewayClass + metadata: + name: envoy-gateway-class + spec: + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parametersRef: + group: gateway.envoyproxy.io + kind: EnvoyProxy + name: test + namespace: envoy-gateway-system envoyProxyForGatewayClass: apiVersion: gateway.envoyproxy.io/v1alpha1 kind: EnvoyProxy 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..b8fc2528d0 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 @@ -91,6 +91,52 @@ envoyExtensionPolicies: status: "True" type: Warning controllerName: gateway.envoyproxy.io/gatewayclass-controller +envoyProxyForGatewayClass: + apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyProxy + metadata: + name: test + namespace: envoy-gateway-system + spec: + logging: {} + luaValidation: InsecureSyntax + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: GatewayClass + name: envoy-gateway-class + conditions: + - lastTransitionTime: null + message: EnvoyProxy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: spec.luaValidation is deprecated, use spec.luaValidationConfig.type + instead + reason: DeprecatedField + status: "True" + type: Warning +gatewayClass: + apiVersion: gateway.networking.k8s.io/v1 + kind: GatewayClass + metadata: + name: envoy-gateway-class + spec: + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parametersRef: + group: gateway.envoyproxy.io + kind: EnvoyProxy + name: test + namespace: envoy-gateway-system + status: + conditions: + - lastTransitionTime: null + message: Valid GatewayClass + reason: Accepted + status: "True" + type: Accepted gateways: - apiVersion: gateway.networking.k8s.io/v1 kind: Gateway @@ -218,7 +264,24 @@ infraIR: spec: logging: {} luaValidation: InsecureSyntax - status: {} + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: GatewayClass + name: envoy-gateway-class + conditions: + - lastTransitionTime: null + message: EnvoyProxy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: spec.luaValidation is deprecated, use spec.luaValidationConfig.type + instead + reason: DeprecatedField + status: "True" + type: Warning listeners: - name: envoy-gateway/gateway-1/http ports: diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua-validation-config.in.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua-validation-config.in.yaml new file mode 100644 index 0000000000..d4d4c31c88 --- /dev/null +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua-validation-config.in.yaml @@ -0,0 +1,76 @@ +gatewayClass: + apiVersion: gateway.networking.k8s.io/v1 + kind: GatewayClass + metadata: + name: envoy-gateway-class + spec: + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parametersRef: + group: gateway.envoyproxy.io + kind: EnvoyProxy + name: test + namespace: envoy-gateway-system +envoyProxyForGatewayClass: + apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyProxy + metadata: + namespace: envoy-gateway-system + name: test + spec: + luaValidationConfig: + type: Disabled +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 +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 # 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-lua-validation-config.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua-validation-config.out.yaml new file mode 100644 index 0000000000..9b8029d3b7 --- /dev/null +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua-validation-config.out.yaml @@ -0,0 +1,288 @@ +envoyExtensionPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyExtensionPolicy + metadata: + name: policy-for-http-route + 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-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 + - lastTransitionTime: null + message: spec.targetRef is deprecated, use spec.targetRefs instead + reason: DeprecatedField + status: "True" + type: Warning + controllerName: gateway.envoyproxy.io/gatewayclass-controller +envoyProxyForGatewayClass: + apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyProxy + metadata: + name: test + namespace: envoy-gateway-system + spec: + logging: {} + luaValidationConfig: + type: Disabled + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: GatewayClass + name: envoy-gateway-class + conditions: + - lastTransitionTime: null + message: EnvoyProxy has been accepted. + reason: Accepted + status: "True" + type: Accepted +gatewayClass: + apiVersion: gateway.networking.k8s.io/v1 + kind: GatewayClass + metadata: + name: envoy-gateway-class + spec: + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parametersRef: + group: gateway.envoyproxy.io + kind: EnvoyProxy + name: test + namespace: envoy-gateway-system + status: + conditions: + - lastTransitionTime: null + message: Valid GatewayClass + reason: Accepted + status: "True" + type: Accepted +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + 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: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + 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 +infraIR: + envoy-gateway/gateway-1: + proxy: + config: + apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyProxy + metadata: + name: test + namespace: envoy-gateway-system + spec: + logging: {} + luaValidationConfig: + type: Disabled + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: GatewayClass + name: envoy-gateway-class + conditions: + - lastTransitionTime: null + message: EnvoyProxy has been accepted. + reason: Accepted + status: "True" + type: Accepted + listeners: + - 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 + globalResources: + proxyServiceCluster: + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-1-196ae069 + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway-1 + settings: + - addressType: IP + endpoints: + - host: 7.6.5.4 + port: 8080 + zone: zone1 + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-1-196ae069 + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway-1 + protocol: TCP + http: + - address: 0.0.0.0 + externalPort: 80 + hostnames: + - '*' + 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: + kind: Service + name: service-1 + namespace: default + sectionName: "8080" + name: httproute/default/httproute-1/rule/0/backend/0 + protocol: HTTP + weight: 1 + envoyExtensions: + luas: + - 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 + 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 + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/gatewayapi/translator.go b/internal/gatewayapi/translator.go index bc44d584bb..63f1890d56 100644 --- a/internal/gatewayapi/translator.go +++ b/internal/gatewayapi/translator.go @@ -462,6 +462,7 @@ func (t *Translator) GetRelevantGateways(resources *resource.Resources) ( status.UpdateEnvoyProxyStatusAccepted(ep, ancestor, egv1a1.EnvoyProxyReasonAccepted, "EnvoyProxy has been accepted.") + status.SetEnvoyProxyDeprecatedFieldsWarning(ep, ancestor, deprecatedFieldsUsedInEnvoyProxy(ep)) } } @@ -530,6 +531,7 @@ func (t *Translator) GetRelevantGateways(resources *resource.Resources) ( if gCtx.envoyProxyFromGateway { status.UpdateEnvoyProxyStatusAccepted(ep, ancestor, egv1a1.EnvoyProxyReasonAccepted, "EnvoyProxy has been accepted.") + status.SetEnvoyProxyDeprecatedFieldsWarning(ep, ancestor, deprecatedFieldsUsedInEnvoyProxy(ep)) } } @@ -560,6 +562,15 @@ func validateEnvoyProxy(ep *egv1a1.EnvoyProxy) error { return nil } +func deprecatedFieldsUsedInEnvoyProxy(ep *egv1a1.EnvoyProxy) map[string]string { + deprecatedFields := make(map[string]string) + if ep.Spec.LuaValidation != nil { + deprecatedFields["spec.luaValidation"] = "spec.luaValidationConfig.type" + } + + return deprecatedFields +} + // InitIRs checks if mergeGateways is enabled in EnvoyProxy config and initializes XdsIR and InfraIR maps with adequate keys. func (t *Translator) InitIRs(acceptedGateways, failedGateways []*GatewayContext) (map[string]*ir.Xds, map[string]*ir.Infra) { xdsIR := make(resource.XdsIRMap) diff --git a/release-notes/current/breaking_changes/9220-lua-validation-allowlist-fail-closed.md b/release-notes/current/breaking_changes/9220-lua-validation-allowlist-fail-closed.md new file mode 100644 index 0000000000..bdbe88e28c --- /dev/null +++ b/release-notes/current/breaking_changes/9220-lua-validation-allowlist-fail-closed.md @@ -0,0 +1 @@ +Strict Lua validation now enforces a fail-closed allowlist of filesystem paths and environment variables that Lua scripts may access during validation, replacing the previous fixed denylist of critical paths/variables. By default the allowlist is empty, so all filesystem and environment variable access during validation is denied. To permit specific paths or environment variables, configure `EnvoyProxy.spec.luaValidationConfig.strict.allowedPaths` and `allowedEnvVars`. This only affects the `Strict` Lua validation mode; `InsecureSyntax` and `Disabled` are unchanged. diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index 4138a7b993..0716dc221a 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -2254,7 +2254,8 @@ _Appears in:_ | `backendTLS` | _[BackendTLSConfig](#backendtlsconfig)_ | false | | BackendTLS is the TLS configuration for the Envoy proxy to use when connecting to backends.
These settings are applied on backends for which TLS policies are specified. | | `ipFamily` | _[IPFamily](#ipfamily)_ | false | | IPFamily specifies the IP family for the EnvoyProxy fleet.
This setting only affects the Gateway listener port and does not impact
other aspects of the Envoy proxy configuration.
If not specified, the system will operate as follows:
- It defaults to IPv4 only.
- IPv6 and dual-stack environments are not supported in this default configuration.
Note: To enable IPv6 or dual-stack functionality, explicit configuration is required. | | `preserveRouteOrder` | _boolean_ | false | | PreserveRouteOrder determines if the order of matching for HTTPRoutes is determined by Gateway-API
specification (https://gateway-api.sigs.k8s.io/reference/api-spec/main/spec/#httprouterule)
or preserves the order defined by users in the HTTPRoute's HTTPRouteRule list.
Default: False | -| `luaValidation` | _[LuaValidation](#luavalidation)_ | false | | LuaValidation determines strictness of the Lua script validation for Lua EnvoyExtensionPolicies
Default: Strict | +| `luaValidation` | _[LuaValidation](#luavalidation)_ | false | | LuaValidation determines strictness of the Lua script validation for Lua EnvoyExtensionPolicies
Default: Strict
Deprecated: Use LuaValidationConfig.Type instead. This field will be removed in a future release. | +| `luaValidationConfig` | _[LuaValidationConfig](#luavalidationconfig)_ | false | | LuaValidationConfig configures how Lua scripts from EnvoyExtensionPolicy resources are
validated in the gateway controller. It selects the validation mode and, for the Strict
mode, defines the filesystem paths and environment variables the scripts are permitted to
access during validation. | | `dynamicModules` | _[DynamicModuleEntry](#dynamicmoduleentry) array_ | false | | DynamicModules defines the set of dynamic modules that are allowed to be
used by EnvoyExtensionPolicy resources and dynamic module load balancer
policies. Each entry registers a module by a logical name and specifies
the shared library that Envoy will load.
The EnvoyProxy owner is responsible for ensuring the module .so files are available
on the proxy container's filesystem (e.g., via init containers, custom images,
or shared volumes). | | `geoIP` | _[EnvoyProxyGeoIP](#envoyproxygeoip)_ | false | | GeoIP defines shared GeoIP provider configuration for this EnvoyProxy fleet. | | `mergeType` | _[MergeType](#mergetype)_ | false | | MergeType controls how this EnvoyProxy merges with less specific configurations
in the hierarchy (EnvoyGateway defaults < GatewayClass < Gateway).
If unset, this EnvoyProxy completely replaces less specific settings.
Note: this field has no effect when set in EnvoyGateway's default EnvoyProxySpec. | @@ -4169,6 +4170,7 @@ _Underlying type:_ _string_ _Appears in:_ - [EnvoyProxySpec](#envoyproxyspec) +- [LuaValidationConfig](#luavalidationconfig) | Value | Description | | ----- | ----------- | @@ -4177,6 +4179,22 @@ _Appears in:_ | `Disabled` | LuaValidationDisabled disables all Lua script validations.
WARNING: This mode does NOT offer any runtime or syntax validations, so no security measures are applied to validate Lua code safety.
Not recommended unless you completely trust all EnvoyExtensionPolicy resources.
| +#### LuaValidationConfig + + + +LuaValidationConfig configures how Lua scripts from EnvoyExtensionPolicy resources are validated +in the gateway controller. + +_Appears in:_ +- [EnvoyProxySpec](#envoyproxyspec) + +| Field | Type | Required | Default | Description | +| --- | --- | --- | --- | --- | +| `type` | _[LuaValidation](#luavalidation)_ | false | Strict | Type determines the strictness of the Lua script validation.
Default: Strict | +| `strict` | _[StrictValidation](#strictvalidation)_ | false | | Strict configures the security sandbox that the Strict validation mode executes Lua scripts
in, defining the filesystem paths and environment variables the scripts are permitted to
access during validation.
It has no effect for the InsecureSyntax or Disabled modes, which do not execute the security
sandbox. | + + #### LuaValueType _Underlying type:_ _string_ @@ -6092,6 +6110,24 @@ _Appears in:_ | `Range` | StatusCodeValueTypeRange defines the "Range" status code match type.
| +#### StrictValidation + + + +StrictValidation defines the configuration that Strict Lua validation runs with. + +This configuration only applies to the Strict validation mode; it has no effect on the +InsecureSyntax and Disabled modes. + +_Appears in:_ +- [LuaValidationConfig](#luavalidationconfig) + +| Field | Type | Required | Default | Description | +| --- | --- | --- | --- | --- | +| `allowedPaths` | _string array_ | false | | AllowedPaths is the list of filesystem path prefixes that Lua scripts are permitted to
access during validation (via io.open, io.input, io.output, io.lines, os.remove, os.rename).
A path is allowed when it equals an entry or is contained within an entry's subtree
(e.g. "/tmp" allows "/tmp/file.txt"). Paths are normalized (separators collapsed, made
absolute) before matching, and any "." or ".." traversal segment is always rejected.
When empty, all filesystem access is denied. Blank or whitespace-only entries are rejected,
as they would otherwise match every path and disable the sandbox. | +| `allowedEnvVars` | _string array_ | false | | AllowedEnvVars is the list of environment variable names that Lua scripts are permitted to
access during validation (via os.getenv, os.setenv). Matching is exact and case-sensitive.
When empty, access to all environment variables is denied. Blank or whitespace-only entries
are rejected. | + + #### StringMatch diff --git a/test/cel-validation/envoyproxy_test.go b/test/cel-validation/envoyproxy_test.go index 20e425157c..873bb60cbf 100644 --- a/test/cel-validation/envoyproxy_test.go +++ b/test/cel-validation/envoyproxy_test.go @@ -2485,6 +2485,104 @@ func TestEnvoyProxyProvider(t *testing.T) { }, wantErrors: []string{"If type is Remote, local field must not be set"}, }, + { + desc: "luaValidationConfig-strict-valid", + mutate: func(envoy *egv1a1.EnvoyProxy) { + envoy.Spec = egv1a1.EnvoyProxySpec{ + LuaValidationConfig: &egv1a1.LuaValidationConfig{ + Strict: &egv1a1.StrictValidation{ + AllowedPaths: []string{"/tmp"}, + AllowedEnvVars: []string{"LOG_LEVEL"}, + }, + }, + } + }, + wantErrors: []string{}, + }, + { + desc: "luaValidationConfig-empty-path-rejected", + mutate: func(envoy *egv1a1.EnvoyProxy) { + envoy.Spec = egv1a1.EnvoyProxySpec{ + LuaValidationConfig: &egv1a1.LuaValidationConfig{ + Strict: &egv1a1.StrictValidation{ + AllowedPaths: []string{""}, + }, + }, + } + }, + wantErrors: []string{"should be at least 1 chars long"}, + }, + { + desc: "luaValidationConfig-whitespace-path-rejected", + mutate: func(envoy *egv1a1.EnvoyProxy) { + envoy.Spec = egv1a1.EnvoyProxySpec{ + LuaValidationConfig: &egv1a1.LuaValidationConfig{ + Strict: &egv1a1.StrictValidation{ + AllowedPaths: []string{" "}, + }, + }, + } + }, + wantErrors: []string{"allowedPaths entries must not be blank or whitespace-only"}, + }, + { + desc: "luaValidationConfig-whitespace-envvar-rejected", + mutate: func(envoy *egv1a1.EnvoyProxy) { + envoy.Spec = egv1a1.EnvoyProxySpec{ + LuaValidationConfig: &egv1a1.LuaValidationConfig{ + Strict: &egv1a1.StrictValidation{ + AllowedEnvVars: []string{" "}, + }, + }, + } + }, + wantErrors: []string{"allowedEnvVars entries must not be blank or whitespace-only"}, + }, + { + desc: "luaValidationConfig-with-explicit-strict-type-allowed", + mutate: func(envoy *egv1a1.EnvoyProxy) { + envoy.Spec = egv1a1.EnvoyProxySpec{ + LuaValidationConfig: &egv1a1.LuaValidationConfig{ + Type: new(egv1a1.LuaValidationStrict), + Strict: &egv1a1.StrictValidation{AllowedPaths: []string{"/tmp"}}, + }, + } + }, + wantErrors: []string{}, + }, + { + desc: "luaValidationConfig-with-unset-type-allowed", + mutate: func(envoy *egv1a1.EnvoyProxy) { + envoy.Spec = egv1a1.EnvoyProxySpec{ + LuaValidationConfig: &egv1a1.LuaValidationConfig{ + Strict: &egv1a1.StrictValidation{AllowedPaths: []string{"/tmp"}}, + }, + } + }, + wantErrors: []string{}, + }, + { + desc: "luaValidationConfig-strict-with-insecure-syntax-type-rejected", + mutate: func(envoy *egv1a1.EnvoyProxy) { + envoy.Spec = egv1a1.EnvoyProxySpec{ + LuaValidationConfig: &egv1a1.LuaValidationConfig{ + Type: new(egv1a1.LuaValidationInsecureSyntax), + Strict: &egv1a1.StrictValidation{AllowedPaths: []string{"/tmp"}}, + }, + } + }, + wantErrors: []string{"strict can only be set when type is Strict"}, + }, + { + desc: "luaValidation-and-luaValidationConfig-mutually-exclusive", + mutate: func(envoy *egv1a1.EnvoyProxy) { + envoy.Spec = egv1a1.EnvoyProxySpec{ + LuaValidation: new(egv1a1.LuaValidationStrict), + LuaValidationConfig: &egv1a1.LuaValidationConfig{Strict: &egv1a1.StrictValidation{AllowedPaths: []string{"/tmp"}}}, + } + }, + wantErrors: []string{"only one of luaValidation or luaValidationConfig may be set"}, + }, } for _, tc := range cases { diff --git a/test/helm/gateway-crds-helm/all.out.yaml b/test/helm/gateway-crds-helm/all.out.yaml index f34795ea71..7f5b3c630a 100644 --- a/test/helm/gateway-crds-helm/all.out.yaml +++ b/test/helm/gateway-crds-helm/all.out.yaml @@ -34591,11 +34591,77 @@ spec: description: |- LuaValidation determines strictness of the Lua script validation for Lua EnvoyExtensionPolicies Default: Strict + + Deprecated: Use LuaValidationConfig.Type instead. This field will be removed in a future release. enum: - Strict - InsecureSyntax - Disabled type: string + luaValidationConfig: + description: |- + LuaValidationConfig configures how Lua scripts from EnvoyExtensionPolicy resources are + validated in the gateway controller. It selects the validation mode and, for the Strict + mode, defines the filesystem paths and environment variables the scripts are permitted to + access during validation. + properties: + strict: + description: |- + Strict configures the security sandbox that the Strict validation mode executes Lua scripts + in, defining the filesystem paths and environment variables the scripts are permitted to + access during validation. + + It has no effect for the InsecureSyntax or Disabled modes, which do not execute the security + sandbox. + properties: + allowedEnvVars: + description: |- + AllowedEnvVars is the list of environment variable names that Lua scripts are permitted to + access during validation (via os.getenv, os.setenv). Matching is exact and case-sensitive. + When empty, access to all environment variables is denied. Blank or whitespace-only entries + are rejected. + items: + maxLength: 256 + minLength: 1 + type: string + maxItems: 64 + type: array + x-kubernetes-validations: + - message: allowedEnvVars entries must not be blank or whitespace-only + rule: self.all(e, e.trim() != '') + allowedPaths: + description: |- + AllowedPaths is the list of filesystem path prefixes that Lua scripts are permitted to + access during validation (via io.open, io.input, io.output, io.lines, os.remove, os.rename). + A path is allowed when it equals an entry or is contained within an entry's subtree + (e.g. "/tmp" allows "/tmp/file.txt"). Paths are normalized (separators collapsed, made + absolute) before matching, and any "." or ".." traversal segment is always rejected. + When empty, all filesystem access is denied. Blank or whitespace-only entries are rejected, + as they would otherwise match every path and disable the sandbox. + items: + maxLength: 4096 + minLength: 1 + type: string + maxItems: 64 + type: array + x-kubernetes-validations: + - message: allowedPaths entries must not be blank or whitespace-only + rule: self.all(p, p.trim() != '') + type: object + type: + default: Strict + description: |- + Type determines the strictness of the Lua script validation. + Default: Strict + enum: + - Strict + - InsecureSyntax + - Disabled + type: string + type: object + x-kubernetes-validations: + - message: strict can only be set when type is Strict + rule: '!has(self.strict) || !has(self.type) || self.type == ''Strict''' mergeGateways: description: |- MergeGateways defines if Gateway resources should be merged onto the same Envoy Proxy Infrastructure. @@ -52053,6 +52119,9 @@ spec: rule: '!(has(self.samplingRate) && has(self.samplingFraction))' type: object type: object + x-kubernetes-validations: + - message: only one of luaValidation or luaValidationConfig may be set + rule: '!has(self.luaValidation) || !has(self.luaValidationConfig)' status: description: EnvoyProxyStatus defines the actual state of EnvoyProxy. properties: diff --git a/test/helm/gateway-crds-helm/e2e.out.yaml b/test/helm/gateway-crds-helm/e2e.out.yaml index 8a3d53deb8..316214665e 100644 --- a/test/helm/gateway-crds-helm/e2e.out.yaml +++ b/test/helm/gateway-crds-helm/e2e.out.yaml @@ -10529,11 +10529,77 @@ spec: description: |- LuaValidation determines strictness of the Lua script validation for Lua EnvoyExtensionPolicies Default: Strict + + Deprecated: Use LuaValidationConfig.Type instead. This field will be removed in a future release. enum: - Strict - InsecureSyntax - Disabled type: string + luaValidationConfig: + description: |- + LuaValidationConfig configures how Lua scripts from EnvoyExtensionPolicy resources are + validated in the gateway controller. It selects the validation mode and, for the Strict + mode, defines the filesystem paths and environment variables the scripts are permitted to + access during validation. + properties: + strict: + description: |- + Strict configures the security sandbox that the Strict validation mode executes Lua scripts + in, defining the filesystem paths and environment variables the scripts are permitted to + access during validation. + + It has no effect for the InsecureSyntax or Disabled modes, which do not execute the security + sandbox. + properties: + allowedEnvVars: + description: |- + AllowedEnvVars is the list of environment variable names that Lua scripts are permitted to + access during validation (via os.getenv, os.setenv). Matching is exact and case-sensitive. + When empty, access to all environment variables is denied. Blank or whitespace-only entries + are rejected. + items: + maxLength: 256 + minLength: 1 + type: string + maxItems: 64 + type: array + x-kubernetes-validations: + - message: allowedEnvVars entries must not be blank or whitespace-only + rule: self.all(e, e.trim() != '') + allowedPaths: + description: |- + AllowedPaths is the list of filesystem path prefixes that Lua scripts are permitted to + access during validation (via io.open, io.input, io.output, io.lines, os.remove, os.rename). + A path is allowed when it equals an entry or is contained within an entry's subtree + (e.g. "/tmp" allows "/tmp/file.txt"). Paths are normalized (separators collapsed, made + absolute) before matching, and any "." or ".." traversal segment is always rejected. + When empty, all filesystem access is denied. Blank or whitespace-only entries are rejected, + as they would otherwise match every path and disable the sandbox. + items: + maxLength: 4096 + minLength: 1 + type: string + maxItems: 64 + type: array + x-kubernetes-validations: + - message: allowedPaths entries must not be blank or whitespace-only + rule: self.all(p, p.trim() != '') + type: object + type: + default: Strict + description: |- + Type determines the strictness of the Lua script validation. + Default: Strict + enum: + - Strict + - InsecureSyntax + - Disabled + type: string + type: object + x-kubernetes-validations: + - message: strict can only be set when type is Strict + rule: '!has(self.strict) || !has(self.type) || self.type == ''Strict''' mergeGateways: description: |- MergeGateways defines if Gateway resources should be merged onto the same Envoy Proxy Infrastructure. @@ -27991,6 +28057,9 @@ spec: rule: '!(has(self.samplingRate) && has(self.samplingFraction))' type: object type: object + x-kubernetes-validations: + - message: only one of luaValidation or luaValidationConfig may be set + rule: '!has(self.luaValidation) || !has(self.luaValidationConfig)' status: description: EnvoyProxyStatus defines the actual state of EnvoyProxy. properties: diff --git a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml index 24447e54bb..91ab87e321 100644 --- a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml +++ b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml @@ -10529,11 +10529,77 @@ spec: description: |- LuaValidation determines strictness of the Lua script validation for Lua EnvoyExtensionPolicies Default: Strict + + Deprecated: Use LuaValidationConfig.Type instead. This field will be removed in a future release. enum: - Strict - InsecureSyntax - Disabled type: string + luaValidationConfig: + description: |- + LuaValidationConfig configures how Lua scripts from EnvoyExtensionPolicy resources are + validated in the gateway controller. It selects the validation mode and, for the Strict + mode, defines the filesystem paths and environment variables the scripts are permitted to + access during validation. + properties: + strict: + description: |- + Strict configures the security sandbox that the Strict validation mode executes Lua scripts + in, defining the filesystem paths and environment variables the scripts are permitted to + access during validation. + + It has no effect for the InsecureSyntax or Disabled modes, which do not execute the security + sandbox. + properties: + allowedEnvVars: + description: |- + AllowedEnvVars is the list of environment variable names that Lua scripts are permitted to + access during validation (via os.getenv, os.setenv). Matching is exact and case-sensitive. + When empty, access to all environment variables is denied. Blank or whitespace-only entries + are rejected. + items: + maxLength: 256 + minLength: 1 + type: string + maxItems: 64 + type: array + x-kubernetes-validations: + - message: allowedEnvVars entries must not be blank or whitespace-only + rule: self.all(e, e.trim() != '') + allowedPaths: + description: |- + AllowedPaths is the list of filesystem path prefixes that Lua scripts are permitted to + access during validation (via io.open, io.input, io.output, io.lines, os.remove, os.rename). + A path is allowed when it equals an entry or is contained within an entry's subtree + (e.g. "/tmp" allows "/tmp/file.txt"). Paths are normalized (separators collapsed, made + absolute) before matching, and any "." or ".." traversal segment is always rejected. + When empty, all filesystem access is denied. Blank or whitespace-only entries are rejected, + as they would otherwise match every path and disable the sandbox. + items: + maxLength: 4096 + minLength: 1 + type: string + maxItems: 64 + type: array + x-kubernetes-validations: + - message: allowedPaths entries must not be blank or whitespace-only + rule: self.all(p, p.trim() != '') + type: object + type: + default: Strict + description: |- + Type determines the strictness of the Lua script validation. + Default: Strict + enum: + - Strict + - InsecureSyntax + - Disabled + type: string + type: object + x-kubernetes-validations: + - message: strict can only be set when type is Strict + rule: '!has(self.strict) || !has(self.type) || self.type == ''Strict''' mergeGateways: description: |- MergeGateways defines if Gateway resources should be merged onto the same Envoy Proxy Infrastructure. @@ -27991,6 +28057,9 @@ spec: rule: '!(has(self.samplingRate) && has(self.samplingFraction))' type: object type: object + x-kubernetes-validations: + - message: only one of luaValidation or luaValidationConfig may be set + rule: '!has(self.luaValidation) || !has(self.luaValidationConfig)' status: description: EnvoyProxyStatus defines the actual state of EnvoyProxy. properties: From 0ea2e2df928f0d9b21eaa5076acd1d1bb34fc271 Mon Sep 17 00:00:00 2001 From: Rudrakh Panigrahi Date: Thu, 30 Jul 2026 02:43:11 +0530 Subject: [PATCH 2/3] reject allow root for strict paths Signed-off-by: Rudrakh Panigrahi --- api/v1alpha1/envoyproxy_types.go | 4 ++- .../gateway.envoyproxy.io_envoyproxies.yaml | 6 ++++- .../gateway.envoyproxy.io_envoyproxies.yaml | 6 ++++- site/content/en/latest/api/extension_types.md | 2 +- test/cel-validation/envoyproxy_test.go | 26 +++++++++++++++++++ test/helm/gateway-crds-helm/all.out.yaml | 6 ++++- test/helm/gateway-crds-helm/e2e.out.yaml | 6 ++++- .../envoy-gateway-crds.out.yaml | 6 ++++- 8 files changed, 55 insertions(+), 7 deletions(-) diff --git a/api/v1alpha1/envoyproxy_types.go b/api/v1alpha1/envoyproxy_types.go index 2a203c6ea3..15080ab0d6 100644 --- a/api/v1alpha1/envoyproxy_types.go +++ b/api/v1alpha1/envoyproxy_types.go @@ -297,12 +297,14 @@ type StrictValidation struct { // (e.g. "/tmp" allows "/tmp/file.txt"). Paths are normalized (separators collapsed, made // absolute) before matching, and any "." or ".." traversal segment is always rejected. // When empty, all filesystem access is denied. Blank or whitespace-only entries are rejected, - // as they would otherwise match every path and disable the sandbox. + // as they would otherwise match every path and disable the sandbox. The filesystem root ("/") + // is likewise rejected, as it would allow access to the entire filesystem and defeat the sandbox. // // +kubebuilder:validation:MaxItems=64 // +kubebuilder:validation:items:MinLength=1 // +kubebuilder:validation:items:MaxLength=4096 // +kubebuilder:validation:XValidation:rule="self.all(p, p.trim() != '')",message="allowedPaths entries must not be blank or whitespace-only" + // +kubebuilder:validation:XValidation:rule="self.all(p, !p.matches('^/+$'))",message="allowedPaths entries must not be the filesystem root" // +optional AllowedPaths []string `json:"allowedPaths,omitempty"` diff --git a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyproxies.yaml b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyproxies.yaml index 3769daf474..009c49b80b 100644 --- a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyproxies.yaml +++ b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyproxies.yaml @@ -819,7 +819,8 @@ spec: (e.g. "/tmp" allows "/tmp/file.txt"). Paths are normalized (separators collapsed, made absolute) before matching, and any "." or ".." traversal segment is always rejected. When empty, all filesystem access is denied. Blank or whitespace-only entries are rejected, - as they would otherwise match every path and disable the sandbox. + as they would otherwise match every path and disable the sandbox. The filesystem root ("/") + is likewise rejected, as it would allow access to the entire filesystem and defeat the sandbox. items: maxLength: 4096 minLength: 1 @@ -829,6 +830,9 @@ spec: x-kubernetes-validations: - message: allowedPaths entries must not be blank or whitespace-only rule: self.all(p, p.trim() != '') + - message: allowedPaths entries must not be the filesystem + root + rule: self.all(p, !p.matches('^/+$')) type: object type: default: Strict diff --git a/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml b/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml index 335b4fd356..728b123f4c 100644 --- a/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml +++ b/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml @@ -818,7 +818,8 @@ spec: (e.g. "/tmp" allows "/tmp/file.txt"). Paths are normalized (separators collapsed, made absolute) before matching, and any "." or ".." traversal segment is always rejected. When empty, all filesystem access is denied. Blank or whitespace-only entries are rejected, - as they would otherwise match every path and disable the sandbox. + as they would otherwise match every path and disable the sandbox. The filesystem root ("/") + is likewise rejected, as it would allow access to the entire filesystem and defeat the sandbox. items: maxLength: 4096 minLength: 1 @@ -828,6 +829,9 @@ spec: x-kubernetes-validations: - message: allowedPaths entries must not be blank or whitespace-only rule: self.all(p, p.trim() != '') + - message: allowedPaths entries must not be the filesystem + root + rule: self.all(p, !p.matches('^/+$')) type: object type: default: Strict diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index 0716dc221a..77e308b40a 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -6124,7 +6124,7 @@ _Appears in:_ | Field | Type | Required | Default | Description | | --- | --- | --- | --- | --- | -| `allowedPaths` | _string array_ | false | | AllowedPaths is the list of filesystem path prefixes that Lua scripts are permitted to
access during validation (via io.open, io.input, io.output, io.lines, os.remove, os.rename).
A path is allowed when it equals an entry or is contained within an entry's subtree
(e.g. "/tmp" allows "/tmp/file.txt"). Paths are normalized (separators collapsed, made
absolute) before matching, and any "." or ".." traversal segment is always rejected.
When empty, all filesystem access is denied. Blank or whitespace-only entries are rejected,
as they would otherwise match every path and disable the sandbox. | +| `allowedPaths` | _string array_ | false | | AllowedPaths is the list of filesystem path prefixes that Lua scripts are permitted to
access during validation (via io.open, io.input, io.output, io.lines, os.remove, os.rename).
A path is allowed when it equals an entry or is contained within an entry's subtree
(e.g. "/tmp" allows "/tmp/file.txt"). Paths are normalized (separators collapsed, made
absolute) before matching, and any "." or ".." traversal segment is always rejected.
When empty, all filesystem access is denied. Blank or whitespace-only entries are rejected,
as they would otherwise match every path and disable the sandbox. The filesystem root ("/")
is likewise rejected, as it would allow access to the entire filesystem and defeat the sandbox. | | `allowedEnvVars` | _string array_ | false | | AllowedEnvVars is the list of environment variable names that Lua scripts are permitted to
access during validation (via os.getenv, os.setenv). Matching is exact and case-sensitive.
When empty, access to all environment variables is denied. Blank or whitespace-only entries
are rejected. | diff --git a/test/cel-validation/envoyproxy_test.go b/test/cel-validation/envoyproxy_test.go index 873bb60cbf..05aeffcc41 100644 --- a/test/cel-validation/envoyproxy_test.go +++ b/test/cel-validation/envoyproxy_test.go @@ -2525,6 +2525,32 @@ func TestEnvoyProxyProvider(t *testing.T) { }, wantErrors: []string{"allowedPaths entries must not be blank or whitespace-only"}, }, + { + desc: "luaValidationConfig-root-path-rejected", + mutate: func(envoy *egv1a1.EnvoyProxy) { + envoy.Spec = egv1a1.EnvoyProxySpec{ + LuaValidationConfig: &egv1a1.LuaValidationConfig{ + Strict: &egv1a1.StrictValidation{ + AllowedPaths: []string{"/"}, + }, + }, + } + }, + wantErrors: []string{"allowedPaths entries must not be the filesystem root"}, + }, + { + desc: "luaValidationConfig-multi-slash-root-path-rejected", + mutate: func(envoy *egv1a1.EnvoyProxy) { + envoy.Spec = egv1a1.EnvoyProxySpec{ + LuaValidationConfig: &egv1a1.LuaValidationConfig{ + Strict: &egv1a1.StrictValidation{ + AllowedPaths: []string{"//"}, + }, + }, + } + }, + wantErrors: []string{"allowedPaths entries must not be the filesystem root"}, + }, { desc: "luaValidationConfig-whitespace-envvar-rejected", mutate: func(envoy *egv1a1.EnvoyProxy) { diff --git a/test/helm/gateway-crds-helm/all.out.yaml b/test/helm/gateway-crds-helm/all.out.yaml index 7f5b3c630a..820d325651 100644 --- a/test/helm/gateway-crds-helm/all.out.yaml +++ b/test/helm/gateway-crds-helm/all.out.yaml @@ -34637,7 +34637,8 @@ spec: (e.g. "/tmp" allows "/tmp/file.txt"). Paths are normalized (separators collapsed, made absolute) before matching, and any "." or ".." traversal segment is always rejected. When empty, all filesystem access is denied. Blank or whitespace-only entries are rejected, - as they would otherwise match every path and disable the sandbox. + as they would otherwise match every path and disable the sandbox. The filesystem root ("/") + is likewise rejected, as it would allow access to the entire filesystem and defeat the sandbox. items: maxLength: 4096 minLength: 1 @@ -34647,6 +34648,9 @@ spec: x-kubernetes-validations: - message: allowedPaths entries must not be blank or whitespace-only rule: self.all(p, p.trim() != '') + - message: allowedPaths entries must not be the filesystem + root + rule: self.all(p, !p.matches('^/+$')) type: object type: default: Strict diff --git a/test/helm/gateway-crds-helm/e2e.out.yaml b/test/helm/gateway-crds-helm/e2e.out.yaml index 316214665e..89762005af 100644 --- a/test/helm/gateway-crds-helm/e2e.out.yaml +++ b/test/helm/gateway-crds-helm/e2e.out.yaml @@ -10575,7 +10575,8 @@ spec: (e.g. "/tmp" allows "/tmp/file.txt"). Paths are normalized (separators collapsed, made absolute) before matching, and any "." or ".." traversal segment is always rejected. When empty, all filesystem access is denied. Blank or whitespace-only entries are rejected, - as they would otherwise match every path and disable the sandbox. + as they would otherwise match every path and disable the sandbox. The filesystem root ("/") + is likewise rejected, as it would allow access to the entire filesystem and defeat the sandbox. items: maxLength: 4096 minLength: 1 @@ -10585,6 +10586,9 @@ spec: x-kubernetes-validations: - message: allowedPaths entries must not be blank or whitespace-only rule: self.all(p, p.trim() != '') + - message: allowedPaths entries must not be the filesystem + root + rule: self.all(p, !p.matches('^/+$')) type: object type: default: Strict diff --git a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml index 91ab87e321..eda2238adf 100644 --- a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml +++ b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml @@ -10575,7 +10575,8 @@ spec: (e.g. "/tmp" allows "/tmp/file.txt"). Paths are normalized (separators collapsed, made absolute) before matching, and any "." or ".." traversal segment is always rejected. When empty, all filesystem access is denied. Blank or whitespace-only entries are rejected, - as they would otherwise match every path and disable the sandbox. + as they would otherwise match every path and disable the sandbox. The filesystem root ("/") + is likewise rejected, as it would allow access to the entire filesystem and defeat the sandbox. items: maxLength: 4096 minLength: 1 @@ -10585,6 +10586,9 @@ spec: x-kubernetes-validations: - message: allowedPaths entries must not be blank or whitespace-only rule: self.all(p, p.trim() != '') + - message: allowedPaths entries must not be the filesystem + root + rule: self.all(p, !p.matches('^/+$')) type: object type: default: Strict From 6bd4ceea0a72d1b569c25479995b26635a9164eb Mon Sep 17 00:00:00 2001 From: Rudrakh Panigrahi Date: Mon, 3 Aug 2026 11:07:59 +0530 Subject: [PATCH 3/3] refactor api Signed-off-by: Rudrakh Panigrahi --- api/v1alpha1/envoyproxy_types.go | 22 +++--- api/v1alpha1/zz_generated.deepcopy.go | 12 ++-- .../gateway.envoyproxy.io_envoyproxies.yaml | 48 ++++++------- .../gateway.envoyproxy.io_envoyproxies.yaml | 48 ++++++------- .../gatewayapi/luavalidator/lua_validator.go | 12 ++-- .../luavalidator/lua_validator_test.go | 4 +- internal/gatewayapi/status/envoyproxy_test.go | 6 +- ...h-invalid-lua-validation-disabled.out.yaml | 5 +- ...ith-invalid-lua-validation-syntax.out.yaml | 5 +- ...npolicy-with-lua-validation-config.in.yaml | 4 +- ...policy-with-lua-validation-config.out.yaml | 8 +-- internal/gatewayapi/translator.go | 2 +- ...20-lua-validation-allowlist-fail-closed.md | 2 +- site/content/en/latest/api/extension_types.md | 8 +-- test/cel-validation/envoyproxy_test.go | 68 +++++++++---------- test/helm/gateway-crds-helm/all.out.yaml | 48 ++++++------- test/helm/gateway-crds-helm/e2e.out.yaml | 48 ++++++------- .../envoy-gateway-crds.out.yaml | 48 ++++++------- 18 files changed, 203 insertions(+), 195 deletions(-) diff --git a/api/v1alpha1/envoyproxy_types.go b/api/v1alpha1/envoyproxy_types.go index 15080ab0d6..f1556ec4b5 100644 --- a/api/v1alpha1/envoyproxy_types.go +++ b/api/v1alpha1/envoyproxy_types.go @@ -35,7 +35,7 @@ type EnvoyProxy struct { } // EnvoyProxySpec defines the desired state of EnvoyProxy. -// +kubebuilder:validation:XValidation:rule="!has(self.luaValidation) || !has(self.luaValidationConfig)",message="only one of luaValidation or luaValidationConfig may be set" +// +kubebuilder:validation:XValidation:rule="!has(self.luaValidation) || !has(self.lua)",message="only one of luaValidation or lua may be set" type EnvoyProxySpec struct { // Provider defines the desired resource provider and provider-specific configuration. // If unspecified, the "Kubernetes" resource provider is used with default configuration @@ -188,17 +188,17 @@ type EnvoyProxySpec struct { // LuaValidation determines strictness of the Lua script validation for Lua EnvoyExtensionPolicies // Default: Strict // - // Deprecated: Use LuaValidationConfig.Type instead. This field will be removed in a future release. + // Deprecated: Use Lua.ValidationType instead. This field will be removed in a future release. // +optional LuaValidation *LuaValidation `json:"luaValidation,omitempty"` - // LuaValidationConfig configures how Lua scripts from EnvoyExtensionPolicy resources are + // Lua configures how Lua scripts from EnvoyExtensionPolicy resources are // validated in the gateway controller. It selects the validation mode and, for the Strict // mode, defines the filesystem paths and environment variables the scripts are permitted to // access during validation. // // +optional - LuaValidationConfig *LuaValidationConfig `json:"luaValidationConfig,omitempty"` + Lua *LuaValidationConfig `json:"lua,omitempty"` // DynamicModules defines the set of dynamic modules that are allowed to be // used by EnvoyExtensionPolicy resources and dynamic module load balancer @@ -265,25 +265,25 @@ const ( // in the gateway controller. // // +union -// +kubebuilder:validation:XValidation:rule="!has(self.strict) || !has(self.type) || self.type == 'Strict'",message="strict can only be set when type is Strict" +// +kubebuilder:validation:XValidation:rule="!has(self.strictValidation) || !has(self.validationType) || self.validationType == 'Strict'",message="strictValidation can only be set when validationType is Strict" type LuaValidationConfig struct { - // Type determines the strictness of the Lua script validation. + // ValidationType determines the strictness of the Lua script validation. // Default: Strict // // +unionDiscriminator // +kubebuilder:default=Strict // +optional - Type *LuaValidation `json:"type,omitempty"` + ValidationType *LuaValidation `json:"validationType,omitempty"` - // Strict configures the security sandbox that the Strict validation mode executes Lua scripts - // in, defining the filesystem paths and environment variables the scripts are permitted to - // access during validation. + // StrictValidation configures the security sandbox that the Strict validation mode executes Lua + // scripts in, defining the filesystem paths and environment variables the scripts are permitted + // to access during validation. // // It has no effect for the InsecureSyntax or Disabled modes, which do not execute the security // sandbox. // // +optional - Strict *StrictValidation `json:"strict,omitempty"` + StrictValidation *StrictValidation `json:"strictValidation,omitempty"` } // StrictValidation defines the configuration that Strict Lua validation runs with. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index be3a81df3c..11b6c5c881 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -3294,8 +3294,8 @@ func (in *EnvoyProxySpec) DeepCopyInto(out *EnvoyProxySpec) { *out = new(LuaValidation) **out = **in } - if in.LuaValidationConfig != nil { - in, out := &in.LuaValidationConfig, &out.LuaValidationConfig + if in.Lua != nil { + in, out := &in.Lua, &out.Lua *out = new(LuaValidationConfig) (*in).DeepCopyInto(*out) } @@ -6014,13 +6014,13 @@ func (in *Lua) DeepCopy() *Lua { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LuaValidationConfig) DeepCopyInto(out *LuaValidationConfig) { *out = *in - if in.Type != nil { - in, out := &in.Type, &out.Type + if in.ValidationType != nil { + in, out := &in.ValidationType, &out.ValidationType *out = new(LuaValidation) **out = **in } - if in.Strict != nil { - in, out := &in.Strict, &out.Strict + if in.StrictValidation != nil { + in, out := &in.StrictValidation, &out.StrictValidation *out = new(StrictValidation) (*in).DeepCopyInto(*out) } diff --git a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyproxies.yaml b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyproxies.yaml index 009c49b80b..5217776f16 100644 --- a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyproxies.yaml +++ b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyproxies.yaml @@ -769,29 +769,18 @@ spec: and the log level is the value. If unspecified, defaults to "default: warn". type: object type: object - luaValidation: + lua: description: |- - LuaValidation determines strictness of the Lua script validation for Lua EnvoyExtensionPolicies - Default: Strict - - Deprecated: Use LuaValidationConfig.Type instead. This field will be removed in a future release. - enum: - - Strict - - InsecureSyntax - - Disabled - type: string - luaValidationConfig: - description: |- - LuaValidationConfig configures how Lua scripts from EnvoyExtensionPolicy resources are + Lua configures how Lua scripts from EnvoyExtensionPolicy resources are validated in the gateway controller. It selects the validation mode and, for the Strict mode, defines the filesystem paths and environment variables the scripts are permitted to access during validation. properties: - strict: + strictValidation: description: |- - Strict configures the security sandbox that the Strict validation mode executes Lua scripts - in, defining the filesystem paths and environment variables the scripts are permitted to - access during validation. + StrictValidation configures the security sandbox that the Strict validation mode executes Lua + scripts in, defining the filesystem paths and environment variables the scripts are permitted + to access during validation. It has no effect for the InsecureSyntax or Disabled modes, which do not execute the security sandbox. @@ -834,10 +823,10 @@ spec: root rule: self.all(p, !p.matches('^/+$')) type: object - type: + validationType: default: Strict description: |- - Type determines the strictness of the Lua script validation. + ValidationType determines the strictness of the Lua script validation. Default: Strict enum: - Strict @@ -846,8 +835,21 @@ spec: type: string type: object x-kubernetes-validations: - - message: strict can only be set when type is Strict - rule: '!has(self.strict) || !has(self.type) || self.type == ''Strict''' + - message: strictValidation can only be set when validationType is + Strict + rule: '!has(self.strictValidation) || !has(self.validationType) + || self.validationType == ''Strict''' + luaValidation: + description: |- + LuaValidation determines strictness of the Lua script validation for Lua EnvoyExtensionPolicies + Default: Strict + + Deprecated: Use Lua.ValidationType instead. This field will be removed in a future release. + enum: + - Strict + - InsecureSyntax + - Disabled + type: string mergeGateways: description: |- MergeGateways defines if Gateway resources should be merged onto the same Envoy Proxy Infrastructure. @@ -18306,8 +18308,8 @@ spec: type: object type: object x-kubernetes-validations: - - message: only one of luaValidation or luaValidationConfig may be set - rule: '!has(self.luaValidation) || !has(self.luaValidationConfig)' + - message: only one of luaValidation or lua may be set + rule: '!has(self.luaValidation) || !has(self.lua)' status: description: EnvoyProxyStatus defines the actual state of EnvoyProxy. properties: diff --git a/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml b/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml index 728b123f4c..ac11766a01 100644 --- a/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml +++ b/charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml @@ -768,29 +768,18 @@ spec: and the log level is the value. If unspecified, defaults to "default: warn". type: object type: object - luaValidation: + lua: description: |- - LuaValidation determines strictness of the Lua script validation for Lua EnvoyExtensionPolicies - Default: Strict - - Deprecated: Use LuaValidationConfig.Type instead. This field will be removed in a future release. - enum: - - Strict - - InsecureSyntax - - Disabled - type: string - luaValidationConfig: - description: |- - LuaValidationConfig configures how Lua scripts from EnvoyExtensionPolicy resources are + Lua configures how Lua scripts from EnvoyExtensionPolicy resources are validated in the gateway controller. It selects the validation mode and, for the Strict mode, defines the filesystem paths and environment variables the scripts are permitted to access during validation. properties: - strict: + strictValidation: description: |- - Strict configures the security sandbox that the Strict validation mode executes Lua scripts - in, defining the filesystem paths and environment variables the scripts are permitted to - access during validation. + StrictValidation configures the security sandbox that the Strict validation mode executes Lua + scripts in, defining the filesystem paths and environment variables the scripts are permitted + to access during validation. It has no effect for the InsecureSyntax or Disabled modes, which do not execute the security sandbox. @@ -833,10 +822,10 @@ spec: root rule: self.all(p, !p.matches('^/+$')) type: object - type: + validationType: default: Strict description: |- - Type determines the strictness of the Lua script validation. + ValidationType determines the strictness of the Lua script validation. Default: Strict enum: - Strict @@ -845,8 +834,21 @@ spec: type: string type: object x-kubernetes-validations: - - message: strict can only be set when type is Strict - rule: '!has(self.strict) || !has(self.type) || self.type == ''Strict''' + - message: strictValidation can only be set when validationType is + Strict + rule: '!has(self.strictValidation) || !has(self.validationType) + || self.validationType == ''Strict''' + luaValidation: + description: |- + LuaValidation determines strictness of the Lua script validation for Lua EnvoyExtensionPolicies + Default: Strict + + Deprecated: Use Lua.ValidationType instead. This field will be removed in a future release. + enum: + - Strict + - InsecureSyntax + - Disabled + type: string mergeGateways: description: |- MergeGateways defines if Gateway resources should be merged onto the same Envoy Proxy Infrastructure. @@ -18305,8 +18307,8 @@ spec: type: object type: object x-kubernetes-validations: - - message: only one of luaValidation or luaValidationConfig may be set - rule: '!has(self.luaValidation) || !has(self.luaValidationConfig)' + - message: only one of luaValidation or lua may be set + rule: '!has(self.luaValidation) || !has(self.lua)' status: description: EnvoyProxyStatus defines the actual state of EnvoyProxy. properties: diff --git a/internal/gatewayapi/luavalidator/lua_validator.go b/internal/gatewayapi/luavalidator/lua_validator.go index 3f23e8e034..2fe2bc71ea 100644 --- a/internal/gatewayapi/luavalidator/lua_validator.go +++ b/internal/gatewayapi/luavalidator/lua_validator.go @@ -83,11 +83,11 @@ func (l *LuaValidator) validate(code string) error { } // getLuaValidation returns the Lua validation level, defaulting to strict if not configured. -// The union LuaValidationConfig.Type takes precedence over the deprecated LuaValidation field. +// The union Lua.ValidationType takes precedence over the deprecated LuaValidation field. func (l *LuaValidator) getLuaValidation() egv1a1.LuaValidation { if l.envoyProxy != nil { - if cfg := l.envoyProxy.Spec.LuaValidationConfig; cfg != nil && cfg.Type != nil { - return *cfg.Type + if cfg := l.envoyProxy.Spec.Lua; cfg != nil && cfg.ValidationType != nil { + return *cfg.ValidationType } if l.envoyProxy.Spec.LuaValidation != nil { return *l.envoyProxy.Spec.LuaValidation @@ -102,9 +102,9 @@ func (l *LuaValidator) getLuaValidation() egv1a1.LuaValidation { func (l *LuaValidator) allowlistData() string { var paths, envVars []string if l.envoyProxy != nil { - if cfg := l.envoyProxy.Spec.LuaValidationConfig; cfg != nil && cfg.Strict != nil { - paths = cfg.Strict.AllowedPaths - envVars = cfg.Strict.AllowedEnvVars + if cfg := l.envoyProxy.Spec.Lua; cfg != nil && cfg.StrictValidation != nil { + paths = cfg.StrictValidation.AllowedPaths + envVars = cfg.StrictValidation.AllowedEnvVars } } diff --git a/internal/gatewayapi/luavalidator/lua_validator_test.go b/internal/gatewayapi/luavalidator/lua_validator_test.go index f7839fb6e2..77dfac25c1 100644 --- a/internal/gatewayapi/luavalidator/lua_validator_test.go +++ b/internal/gatewayapi/luavalidator/lua_validator_test.go @@ -213,8 +213,8 @@ func Test_BasicValidation(t *testing.T) { func allowlistProxy(paths, envVars []string) *egv1a1.EnvoyProxy { return &egv1a1.EnvoyProxy{ Spec: egv1a1.EnvoyProxySpec{ - LuaValidationConfig: &egv1a1.LuaValidationConfig{ - Strict: &egv1a1.StrictValidation{ + Lua: &egv1a1.LuaValidationConfig{ + StrictValidation: &egv1a1.StrictValidation{ AllowedPaths: paths, AllowedEnvVars: envVars, }, diff --git a/internal/gatewayapi/status/envoyproxy_test.go b/internal/gatewayapi/status/envoyproxy_test.go index 20ffd4c9d1..7e8b2cadbe 100644 --- a/internal/gatewayapi/status/envoyproxy_test.go +++ b/internal/gatewayapi/status/envoyproxy_test.go @@ -21,7 +21,7 @@ func TestSetEnvoyProxyDeprecatedFieldsWarning(t *testing.T) { t.Run("sets a warning condition when deprecated fields are used", func(t *testing.T) { ep := &egv1a1.EnvoyProxy{} SetEnvoyProxyDeprecatedFieldsWarning(ep, ancestor, map[string]string{ - "spec.luaValidation": "spec.luaValidationConfig.type", + "spec.luaValidation": "spec.lua.validationType", }) assert.Len(t, ep.Status.Ancestors, 1) @@ -30,7 +30,7 @@ func TestSetEnvoyProxyDeprecatedFieldsWarning(t *testing.T) { assert.Equal(t, string(egv1a1.EnvoyProxyConditionWarning), conds[0].Type) assert.Equal(t, metav1.ConditionTrue, conds[0].Status) assert.Equal(t, string(egv1a1.EnvoyProxyReasonDeprecatedField), conds[0].Reason) - assert.Equal(t, "spec.luaValidation is deprecated, use spec.luaValidationConfig.type instead", conds[0].Message) + assert.Equal(t, "spec.luaValidation is deprecated, use spec.lua.validationType instead", conds[0].Message) }) t.Run("no-op when no deprecated fields are used", func(t *testing.T) { @@ -43,7 +43,7 @@ func TestSetEnvoyProxyDeprecatedFieldsWarning(t *testing.T) { ep := &egv1a1.EnvoyProxy{} UpdateEnvoyProxyStatusAccepted(ep, ancestor, egv1a1.EnvoyProxyReasonAccepted, "EnvoyProxy has been accepted.") SetEnvoyProxyDeprecatedFieldsWarning(ep, ancestor, map[string]string{ - "spec.luaValidation": "spec.luaValidationConfig.type", + "spec.luaValidation": "spec.lua.validationType", }) assert.Len(t, ep.Status.Ancestors, 1) 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 36b165ad79..82ae56e314 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 @@ -60,8 +60,7 @@ envoyProxyForGatewayClass: status: "True" type: Accepted - lastTransitionTime: null - message: spec.luaValidation is deprecated, use spec.luaValidationConfig.type - instead + message: spec.luaValidation is deprecated, use spec.lua.validationType instead reason: DeprecatedField status: "True" type: Warning @@ -187,7 +186,7 @@ infraIR: status: "True" type: Accepted - lastTransitionTime: null - message: spec.luaValidation is deprecated, use spec.luaValidationConfig.type + message: spec.luaValidation is deprecated, use spec.lua.validationType instead reason: DeprecatedField status: "True" 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 b8fc2528d0..3e26a3cfd4 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 @@ -113,8 +113,7 @@ envoyProxyForGatewayClass: status: "True" type: Accepted - lastTransitionTime: null - message: spec.luaValidation is deprecated, use spec.luaValidationConfig.type - instead + message: spec.luaValidation is deprecated, use spec.lua.validationType instead reason: DeprecatedField status: "True" type: Warning @@ -277,7 +276,7 @@ infraIR: status: "True" type: Accepted - lastTransitionTime: null - message: spec.luaValidation is deprecated, use spec.luaValidationConfig.type + message: spec.luaValidation is deprecated, use spec.lua.validationType instead reason: DeprecatedField status: "True" diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua-validation-config.in.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua-validation-config.in.yaml index d4d4c31c88..aee16f48bd 100644 --- a/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua-validation-config.in.yaml +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua-validation-config.in.yaml @@ -17,8 +17,8 @@ envoyProxyForGatewayClass: namespace: envoy-gateway-system name: test spec: - luaValidationConfig: - type: Disabled + lua: + validationType: Disabled gateways: - apiVersion: gateway.networking.k8s.io/v1 kind: Gateway diff --git a/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua-validation-config.out.yaml b/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua-validation-config.out.yaml index 9b8029d3b7..a8880d7ab8 100644 --- a/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua-validation-config.out.yaml +++ b/internal/gatewayapi/testdata/envoyextensionpolicy-with-lua-validation-config.out.yaml @@ -46,8 +46,8 @@ envoyProxyForGatewayClass: namespace: envoy-gateway-system spec: logging: {} - luaValidationConfig: - type: Disabled + lua: + validationType: Disabled status: ancestors: - ancestorRef: @@ -168,8 +168,8 @@ infraIR: namespace: envoy-gateway-system spec: logging: {} - luaValidationConfig: - type: Disabled + lua: + validationType: Disabled status: ancestors: - ancestorRef: diff --git a/internal/gatewayapi/translator.go b/internal/gatewayapi/translator.go index 63f1890d56..24c7ad59fa 100644 --- a/internal/gatewayapi/translator.go +++ b/internal/gatewayapi/translator.go @@ -565,7 +565,7 @@ func validateEnvoyProxy(ep *egv1a1.EnvoyProxy) error { func deprecatedFieldsUsedInEnvoyProxy(ep *egv1a1.EnvoyProxy) map[string]string { deprecatedFields := make(map[string]string) if ep.Spec.LuaValidation != nil { - deprecatedFields["spec.luaValidation"] = "spec.luaValidationConfig.type" + deprecatedFields["spec.luaValidation"] = "spec.lua.validationType" } return deprecatedFields diff --git a/release-notes/current/breaking_changes/9220-lua-validation-allowlist-fail-closed.md b/release-notes/current/breaking_changes/9220-lua-validation-allowlist-fail-closed.md index bdbe88e28c..170674b15c 100644 --- a/release-notes/current/breaking_changes/9220-lua-validation-allowlist-fail-closed.md +++ b/release-notes/current/breaking_changes/9220-lua-validation-allowlist-fail-closed.md @@ -1 +1 @@ -Strict Lua validation now enforces a fail-closed allowlist of filesystem paths and environment variables that Lua scripts may access during validation, replacing the previous fixed denylist of critical paths/variables. By default the allowlist is empty, so all filesystem and environment variable access during validation is denied. To permit specific paths or environment variables, configure `EnvoyProxy.spec.luaValidationConfig.strict.allowedPaths` and `allowedEnvVars`. This only affects the `Strict` Lua validation mode; `InsecureSyntax` and `Disabled` are unchanged. +Strict Lua validation now enforces a fail-closed allowlist of filesystem paths and environment variables that Lua scripts may access during validation, replacing the previous fixed denylist of critical paths/variables. By default the allowlist is empty, so all filesystem and environment variable access during validation is denied. To permit specific paths or environment variables, configure `EnvoyProxy.spec.lua.strictValidation.allowedPaths` and `allowedEnvVars`. This only affects the `Strict` Lua validation mode; `InsecureSyntax` and `Disabled` are unchanged. diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index 77e308b40a..9497e1e358 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -2254,8 +2254,8 @@ _Appears in:_ | `backendTLS` | _[BackendTLSConfig](#backendtlsconfig)_ | false | | BackendTLS is the TLS configuration for the Envoy proxy to use when connecting to backends.
These settings are applied on backends for which TLS policies are specified. | | `ipFamily` | _[IPFamily](#ipfamily)_ | false | | IPFamily specifies the IP family for the EnvoyProxy fleet.
This setting only affects the Gateway listener port and does not impact
other aspects of the Envoy proxy configuration.
If not specified, the system will operate as follows:
- It defaults to IPv4 only.
- IPv6 and dual-stack environments are not supported in this default configuration.
Note: To enable IPv6 or dual-stack functionality, explicit configuration is required. | | `preserveRouteOrder` | _boolean_ | false | | PreserveRouteOrder determines if the order of matching for HTTPRoutes is determined by Gateway-API
specification (https://gateway-api.sigs.k8s.io/reference/api-spec/main/spec/#httprouterule)
or preserves the order defined by users in the HTTPRoute's HTTPRouteRule list.
Default: False | -| `luaValidation` | _[LuaValidation](#luavalidation)_ | false | | LuaValidation determines strictness of the Lua script validation for Lua EnvoyExtensionPolicies
Default: Strict
Deprecated: Use LuaValidationConfig.Type instead. This field will be removed in a future release. | -| `luaValidationConfig` | _[LuaValidationConfig](#luavalidationconfig)_ | false | | LuaValidationConfig configures how Lua scripts from EnvoyExtensionPolicy resources are
validated in the gateway controller. It selects the validation mode and, for the Strict
mode, defines the filesystem paths and environment variables the scripts are permitted to
access during validation. | +| `luaValidation` | _[LuaValidation](#luavalidation)_ | false | | LuaValidation determines strictness of the Lua script validation for Lua EnvoyExtensionPolicies
Default: Strict
Deprecated: Use Lua.ValidationType instead. This field will be removed in a future release. | +| `lua` | _[LuaValidationConfig](#luavalidationconfig)_ | false | | Lua configures how Lua scripts from EnvoyExtensionPolicy resources are
validated in the gateway controller. It selects the validation mode and, for the Strict
mode, defines the filesystem paths and environment variables the scripts are permitted to
access during validation. | | `dynamicModules` | _[DynamicModuleEntry](#dynamicmoduleentry) array_ | false | | DynamicModules defines the set of dynamic modules that are allowed to be
used by EnvoyExtensionPolicy resources and dynamic module load balancer
policies. Each entry registers a module by a logical name and specifies
the shared library that Envoy will load.
The EnvoyProxy owner is responsible for ensuring the module .so files are available
on the proxy container's filesystem (e.g., via init containers, custom images,
or shared volumes). | | `geoIP` | _[EnvoyProxyGeoIP](#envoyproxygeoip)_ | false | | GeoIP defines shared GeoIP provider configuration for this EnvoyProxy fleet. | | `mergeType` | _[MergeType](#mergetype)_ | false | | MergeType controls how this EnvoyProxy merges with less specific configurations
in the hierarchy (EnvoyGateway defaults < GatewayClass < Gateway).
If unset, this EnvoyProxy completely replaces less specific settings.
Note: this field has no effect when set in EnvoyGateway's default EnvoyProxySpec. | @@ -4191,8 +4191,8 @@ _Appears in:_ | Field | Type | Required | Default | Description | | --- | --- | --- | --- | --- | -| `type` | _[LuaValidation](#luavalidation)_ | false | Strict | Type determines the strictness of the Lua script validation.
Default: Strict | -| `strict` | _[StrictValidation](#strictvalidation)_ | false | | Strict configures the security sandbox that the Strict validation mode executes Lua scripts
in, defining the filesystem paths and environment variables the scripts are permitted to
access during validation.
It has no effect for the InsecureSyntax or Disabled modes, which do not execute the security
sandbox. | +| `validationType` | _[LuaValidation](#luavalidation)_ | false | Strict | ValidationType determines the strictness of the Lua script validation.
Default: Strict | +| `strictValidation` | _[StrictValidation](#strictvalidation)_ | false | | StrictValidation configures the security sandbox that the Strict validation mode executes Lua
scripts in, defining the filesystem paths and environment variables the scripts are permitted
to access during validation.
It has no effect for the InsecureSyntax or Disabled modes, which do not execute the security
sandbox. | #### LuaValueType diff --git a/test/cel-validation/envoyproxy_test.go b/test/cel-validation/envoyproxy_test.go index 05aeffcc41..e0e0a73de4 100644 --- a/test/cel-validation/envoyproxy_test.go +++ b/test/cel-validation/envoyproxy_test.go @@ -2486,11 +2486,11 @@ func TestEnvoyProxyProvider(t *testing.T) { wantErrors: []string{"If type is Remote, local field must not be set"}, }, { - desc: "luaValidationConfig-strict-valid", + desc: "lua-strict-valid", mutate: func(envoy *egv1a1.EnvoyProxy) { envoy.Spec = egv1a1.EnvoyProxySpec{ - LuaValidationConfig: &egv1a1.LuaValidationConfig{ - Strict: &egv1a1.StrictValidation{ + Lua: &egv1a1.LuaValidationConfig{ + StrictValidation: &egv1a1.StrictValidation{ AllowedPaths: []string{"/tmp"}, AllowedEnvVars: []string{"LOG_LEVEL"}, }, @@ -2500,11 +2500,11 @@ func TestEnvoyProxyProvider(t *testing.T) { wantErrors: []string{}, }, { - desc: "luaValidationConfig-empty-path-rejected", + desc: "lua-empty-path-rejected", mutate: func(envoy *egv1a1.EnvoyProxy) { envoy.Spec = egv1a1.EnvoyProxySpec{ - LuaValidationConfig: &egv1a1.LuaValidationConfig{ - Strict: &egv1a1.StrictValidation{ + Lua: &egv1a1.LuaValidationConfig{ + StrictValidation: &egv1a1.StrictValidation{ AllowedPaths: []string{""}, }, }, @@ -2513,11 +2513,11 @@ func TestEnvoyProxyProvider(t *testing.T) { wantErrors: []string{"should be at least 1 chars long"}, }, { - desc: "luaValidationConfig-whitespace-path-rejected", + desc: "lua-whitespace-path-rejected", mutate: func(envoy *egv1a1.EnvoyProxy) { envoy.Spec = egv1a1.EnvoyProxySpec{ - LuaValidationConfig: &egv1a1.LuaValidationConfig{ - Strict: &egv1a1.StrictValidation{ + Lua: &egv1a1.LuaValidationConfig{ + StrictValidation: &egv1a1.StrictValidation{ AllowedPaths: []string{" "}, }, }, @@ -2526,11 +2526,11 @@ func TestEnvoyProxyProvider(t *testing.T) { wantErrors: []string{"allowedPaths entries must not be blank or whitespace-only"}, }, { - desc: "luaValidationConfig-root-path-rejected", + desc: "lua-root-path-rejected", mutate: func(envoy *egv1a1.EnvoyProxy) { envoy.Spec = egv1a1.EnvoyProxySpec{ - LuaValidationConfig: &egv1a1.LuaValidationConfig{ - Strict: &egv1a1.StrictValidation{ + Lua: &egv1a1.LuaValidationConfig{ + StrictValidation: &egv1a1.StrictValidation{ AllowedPaths: []string{"/"}, }, }, @@ -2539,11 +2539,11 @@ func TestEnvoyProxyProvider(t *testing.T) { wantErrors: []string{"allowedPaths entries must not be the filesystem root"}, }, { - desc: "luaValidationConfig-multi-slash-root-path-rejected", + desc: "lua-multi-slash-root-path-rejected", mutate: func(envoy *egv1a1.EnvoyProxy) { envoy.Spec = egv1a1.EnvoyProxySpec{ - LuaValidationConfig: &egv1a1.LuaValidationConfig{ - Strict: &egv1a1.StrictValidation{ + Lua: &egv1a1.LuaValidationConfig{ + StrictValidation: &egv1a1.StrictValidation{ AllowedPaths: []string{"//"}, }, }, @@ -2552,11 +2552,11 @@ func TestEnvoyProxyProvider(t *testing.T) { wantErrors: []string{"allowedPaths entries must not be the filesystem root"}, }, { - desc: "luaValidationConfig-whitespace-envvar-rejected", + desc: "lua-whitespace-envvar-rejected", mutate: func(envoy *egv1a1.EnvoyProxy) { envoy.Spec = egv1a1.EnvoyProxySpec{ - LuaValidationConfig: &egv1a1.LuaValidationConfig{ - Strict: &egv1a1.StrictValidation{ + Lua: &egv1a1.LuaValidationConfig{ + StrictValidation: &egv1a1.StrictValidation{ AllowedEnvVars: []string{" "}, }, }, @@ -2565,49 +2565,49 @@ func TestEnvoyProxyProvider(t *testing.T) { wantErrors: []string{"allowedEnvVars entries must not be blank or whitespace-only"}, }, { - desc: "luaValidationConfig-with-explicit-strict-type-allowed", + desc: "lua-with-explicit-strict-type-allowed", mutate: func(envoy *egv1a1.EnvoyProxy) { envoy.Spec = egv1a1.EnvoyProxySpec{ - LuaValidationConfig: &egv1a1.LuaValidationConfig{ - Type: new(egv1a1.LuaValidationStrict), - Strict: &egv1a1.StrictValidation{AllowedPaths: []string{"/tmp"}}, + Lua: &egv1a1.LuaValidationConfig{ + ValidationType: new(egv1a1.LuaValidationStrict), + StrictValidation: &egv1a1.StrictValidation{AllowedPaths: []string{"/tmp"}}, }, } }, wantErrors: []string{}, }, { - desc: "luaValidationConfig-with-unset-type-allowed", + desc: "lua-with-unset-type-allowed", mutate: func(envoy *egv1a1.EnvoyProxy) { envoy.Spec = egv1a1.EnvoyProxySpec{ - LuaValidationConfig: &egv1a1.LuaValidationConfig{ - Strict: &egv1a1.StrictValidation{AllowedPaths: []string{"/tmp"}}, + Lua: &egv1a1.LuaValidationConfig{ + StrictValidation: &egv1a1.StrictValidation{AllowedPaths: []string{"/tmp"}}, }, } }, wantErrors: []string{}, }, { - desc: "luaValidationConfig-strict-with-insecure-syntax-type-rejected", + desc: "lua-strict-with-insecure-syntax-type-rejected", mutate: func(envoy *egv1a1.EnvoyProxy) { envoy.Spec = egv1a1.EnvoyProxySpec{ - LuaValidationConfig: &egv1a1.LuaValidationConfig{ - Type: new(egv1a1.LuaValidationInsecureSyntax), - Strict: &egv1a1.StrictValidation{AllowedPaths: []string{"/tmp"}}, + Lua: &egv1a1.LuaValidationConfig{ + ValidationType: new(egv1a1.LuaValidationInsecureSyntax), + StrictValidation: &egv1a1.StrictValidation{AllowedPaths: []string{"/tmp"}}, }, } }, - wantErrors: []string{"strict can only be set when type is Strict"}, + wantErrors: []string{"strictValidation can only be set when validationType is Strict"}, }, { - desc: "luaValidation-and-luaValidationConfig-mutually-exclusive", + desc: "luaValidation-and-lua-mutually-exclusive", mutate: func(envoy *egv1a1.EnvoyProxy) { envoy.Spec = egv1a1.EnvoyProxySpec{ - LuaValidation: new(egv1a1.LuaValidationStrict), - LuaValidationConfig: &egv1a1.LuaValidationConfig{Strict: &egv1a1.StrictValidation{AllowedPaths: []string{"/tmp"}}}, + LuaValidation: new(egv1a1.LuaValidationStrict), + Lua: &egv1a1.LuaValidationConfig{StrictValidation: &egv1a1.StrictValidation{AllowedPaths: []string{"/tmp"}}}, } }, - wantErrors: []string{"only one of luaValidation or luaValidationConfig may be set"}, + wantErrors: []string{"only one of luaValidation or lua may be set"}, }, } diff --git a/test/helm/gateway-crds-helm/all.out.yaml b/test/helm/gateway-crds-helm/all.out.yaml index 820d325651..3770643d27 100644 --- a/test/helm/gateway-crds-helm/all.out.yaml +++ b/test/helm/gateway-crds-helm/all.out.yaml @@ -34587,29 +34587,18 @@ spec: and the log level is the value. If unspecified, defaults to "default: warn". type: object type: object - luaValidation: - description: |- - LuaValidation determines strictness of the Lua script validation for Lua EnvoyExtensionPolicies - Default: Strict - - Deprecated: Use LuaValidationConfig.Type instead. This field will be removed in a future release. - enum: - - Strict - - InsecureSyntax - - Disabled - type: string - luaValidationConfig: + lua: description: |- - LuaValidationConfig configures how Lua scripts from EnvoyExtensionPolicy resources are + Lua configures how Lua scripts from EnvoyExtensionPolicy resources are validated in the gateway controller. It selects the validation mode and, for the Strict mode, defines the filesystem paths and environment variables the scripts are permitted to access during validation. properties: - strict: + strictValidation: description: |- - Strict configures the security sandbox that the Strict validation mode executes Lua scripts - in, defining the filesystem paths and environment variables the scripts are permitted to - access during validation. + StrictValidation configures the security sandbox that the Strict validation mode executes Lua + scripts in, defining the filesystem paths and environment variables the scripts are permitted + to access during validation. It has no effect for the InsecureSyntax or Disabled modes, which do not execute the security sandbox. @@ -34652,10 +34641,10 @@ spec: root rule: self.all(p, !p.matches('^/+$')) type: object - type: + validationType: default: Strict description: |- - Type determines the strictness of the Lua script validation. + ValidationType determines the strictness of the Lua script validation. Default: Strict enum: - Strict @@ -34664,8 +34653,21 @@ spec: type: string type: object x-kubernetes-validations: - - message: strict can only be set when type is Strict - rule: '!has(self.strict) || !has(self.type) || self.type == ''Strict''' + - message: strictValidation can only be set when validationType is + Strict + rule: '!has(self.strictValidation) || !has(self.validationType) + || self.validationType == ''Strict''' + luaValidation: + description: |- + LuaValidation determines strictness of the Lua script validation for Lua EnvoyExtensionPolicies + Default: Strict + + Deprecated: Use Lua.ValidationType instead. This field will be removed in a future release. + enum: + - Strict + - InsecureSyntax + - Disabled + type: string mergeGateways: description: |- MergeGateways defines if Gateway resources should be merged onto the same Envoy Proxy Infrastructure. @@ -52124,8 +52126,8 @@ spec: type: object type: object x-kubernetes-validations: - - message: only one of luaValidation or luaValidationConfig may be set - rule: '!has(self.luaValidation) || !has(self.luaValidationConfig)' + - message: only one of luaValidation or lua may be set + rule: '!has(self.luaValidation) || !has(self.lua)' status: description: EnvoyProxyStatus defines the actual state of EnvoyProxy. properties: diff --git a/test/helm/gateway-crds-helm/e2e.out.yaml b/test/helm/gateway-crds-helm/e2e.out.yaml index 89762005af..35bc63e458 100644 --- a/test/helm/gateway-crds-helm/e2e.out.yaml +++ b/test/helm/gateway-crds-helm/e2e.out.yaml @@ -10525,29 +10525,18 @@ spec: and the log level is the value. If unspecified, defaults to "default: warn". type: object type: object - luaValidation: - description: |- - LuaValidation determines strictness of the Lua script validation for Lua EnvoyExtensionPolicies - Default: Strict - - Deprecated: Use LuaValidationConfig.Type instead. This field will be removed in a future release. - enum: - - Strict - - InsecureSyntax - - Disabled - type: string - luaValidationConfig: + lua: description: |- - LuaValidationConfig configures how Lua scripts from EnvoyExtensionPolicy resources are + Lua configures how Lua scripts from EnvoyExtensionPolicy resources are validated in the gateway controller. It selects the validation mode and, for the Strict mode, defines the filesystem paths and environment variables the scripts are permitted to access during validation. properties: - strict: + strictValidation: description: |- - Strict configures the security sandbox that the Strict validation mode executes Lua scripts - in, defining the filesystem paths and environment variables the scripts are permitted to - access during validation. + StrictValidation configures the security sandbox that the Strict validation mode executes Lua + scripts in, defining the filesystem paths and environment variables the scripts are permitted + to access during validation. It has no effect for the InsecureSyntax or Disabled modes, which do not execute the security sandbox. @@ -10590,10 +10579,10 @@ spec: root rule: self.all(p, !p.matches('^/+$')) type: object - type: + validationType: default: Strict description: |- - Type determines the strictness of the Lua script validation. + ValidationType determines the strictness of the Lua script validation. Default: Strict enum: - Strict @@ -10602,8 +10591,21 @@ spec: type: string type: object x-kubernetes-validations: - - message: strict can only be set when type is Strict - rule: '!has(self.strict) || !has(self.type) || self.type == ''Strict''' + - message: strictValidation can only be set when validationType is + Strict + rule: '!has(self.strictValidation) || !has(self.validationType) + || self.validationType == ''Strict''' + luaValidation: + description: |- + LuaValidation determines strictness of the Lua script validation for Lua EnvoyExtensionPolicies + Default: Strict + + Deprecated: Use Lua.ValidationType instead. This field will be removed in a future release. + enum: + - Strict + - InsecureSyntax + - Disabled + type: string mergeGateways: description: |- MergeGateways defines if Gateway resources should be merged onto the same Envoy Proxy Infrastructure. @@ -28062,8 +28064,8 @@ spec: type: object type: object x-kubernetes-validations: - - message: only one of luaValidation or luaValidationConfig may be set - rule: '!has(self.luaValidation) || !has(self.luaValidationConfig)' + - message: only one of luaValidation or lua may be set + rule: '!has(self.luaValidation) || !has(self.lua)' status: description: EnvoyProxyStatus defines the actual state of EnvoyProxy. properties: diff --git a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml index eda2238adf..a6752b164a 100644 --- a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml +++ b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml @@ -10525,29 +10525,18 @@ spec: and the log level is the value. If unspecified, defaults to "default: warn". type: object type: object - luaValidation: - description: |- - LuaValidation determines strictness of the Lua script validation for Lua EnvoyExtensionPolicies - Default: Strict - - Deprecated: Use LuaValidationConfig.Type instead. This field will be removed in a future release. - enum: - - Strict - - InsecureSyntax - - Disabled - type: string - luaValidationConfig: + lua: description: |- - LuaValidationConfig configures how Lua scripts from EnvoyExtensionPolicy resources are + Lua configures how Lua scripts from EnvoyExtensionPolicy resources are validated in the gateway controller. It selects the validation mode and, for the Strict mode, defines the filesystem paths and environment variables the scripts are permitted to access during validation. properties: - strict: + strictValidation: description: |- - Strict configures the security sandbox that the Strict validation mode executes Lua scripts - in, defining the filesystem paths and environment variables the scripts are permitted to - access during validation. + StrictValidation configures the security sandbox that the Strict validation mode executes Lua + scripts in, defining the filesystem paths and environment variables the scripts are permitted + to access during validation. It has no effect for the InsecureSyntax or Disabled modes, which do not execute the security sandbox. @@ -10590,10 +10579,10 @@ spec: root rule: self.all(p, !p.matches('^/+$')) type: object - type: + validationType: default: Strict description: |- - Type determines the strictness of the Lua script validation. + ValidationType determines the strictness of the Lua script validation. Default: Strict enum: - Strict @@ -10602,8 +10591,21 @@ spec: type: string type: object x-kubernetes-validations: - - message: strict can only be set when type is Strict - rule: '!has(self.strict) || !has(self.type) || self.type == ''Strict''' + - message: strictValidation can only be set when validationType is + Strict + rule: '!has(self.strictValidation) || !has(self.validationType) + || self.validationType == ''Strict''' + luaValidation: + description: |- + LuaValidation determines strictness of the Lua script validation for Lua EnvoyExtensionPolicies + Default: Strict + + Deprecated: Use Lua.ValidationType instead. This field will be removed in a future release. + enum: + - Strict + - InsecureSyntax + - Disabled + type: string mergeGateways: description: |- MergeGateways defines if Gateway resources should be merged onto the same Envoy Proxy Infrastructure. @@ -28062,8 +28064,8 @@ spec: type: object type: object x-kubernetes-validations: - - message: only one of luaValidation or luaValidationConfig may be set - rule: '!has(self.luaValidation) || !has(self.luaValidationConfig)' + - message: only one of luaValidation or lua may be set + rule: '!has(self.luaValidation) || !has(self.lua)' status: description: EnvoyProxyStatus defines the actual state of EnvoyProxy. properties: