Skip to content

Commit b373b29

Browse files
committed
feat: whitelist for allowed env and paths for lua
Signed-off-by: Rudrakh Panigrahi <rudrakh97@gmail.com>
1 parent 265fdeb commit b373b29

13 files changed

Lines changed: 742 additions & 380 deletions

File tree

api/v1alpha1/envoyproxy_types.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ type EnvoyProxy struct {
3535
}
3636

3737
// EnvoyProxySpec defines the desired state of EnvoyProxy.
38+
// +kubebuilder:validation:XValidation:rule="!has(self.luaStrictValidation) || !has(self.luaValidation) || self.luaValidation == 'Strict'",message="luaStrictValidation can only be set when luaValidation is Strict"
3839
type EnvoyProxySpec struct {
3940
// Provider defines the desired resource provider and provider-specific configuration.
4041
// If unspecified, the "Kubernetes" resource provider is used with default configuration
@@ -189,6 +190,20 @@ type EnvoyProxySpec struct {
189190
// +optional
190191
LuaValidation *LuaValidation `json:"luaValidation,omitempty"`
191192

193+
// LuaStrictValidation defines the filesystem paths and environment variables that Lua
194+
// scripts from EnvoyExtensionPolicy resources are permitted to access during Strict
195+
// validation in the gateway controller.
196+
//
197+
// The allowlist is fail-closed: when unset or empty, Lua scripts are denied access to ALL
198+
// filesystem paths and environment variables during validation. Only entries that match the
199+
// allowlist are permitted.
200+
//
201+
// This field only takes effect when LuaValidation is Strict (the default). It has no effect
202+
// for the InsecureSyntax or Disabled validation modes, which do not execute the security sandbox.
203+
//
204+
// +optional
205+
LuaStrictValidation *LuaStrictValidation `json:"luaStrictValidation,omitempty"`
206+
192207
// DynamicModules defines the set of dynamic modules that are allowed to be
193208
// used by EnvoyExtensionPolicy resources and dynamic module load balancer
194209
// policies. Each entry registers a module by a logical name and specifies
@@ -250,6 +265,39 @@ const (
250265
LuaValidationDisabled LuaValidation = "Disabled"
251266
)
252267

268+
// LuaStrictValidation defines the configuration that Strict Lua validation runs with.
269+
//
270+
// This configuration only applies to the Strict validation mode; it has no effect on the
271+
// InsecureSyntax and Disabled modes.
272+
type LuaStrictValidation struct {
273+
// AllowedPaths is the list of filesystem path prefixes that Lua scripts are permitted to
274+
// access during validation (via io.open, io.input, io.output, io.lines, os.remove, os.rename).
275+
// A path is allowed when it equals an entry or is contained within an entry's subtree
276+
// (e.g. "/tmp" allows "/tmp/file.txt"). Paths are normalized (separators collapsed, made
277+
// absolute) before matching, and any "." or ".." traversal segment is always rejected.
278+
// When empty, all filesystem access is denied. Blank or whitespace-only entries are rejected,
279+
// as they would otherwise match every path and disable the sandbox.
280+
//
281+
// +kubebuilder:validation:MaxItems=64
282+
// +kubebuilder:validation:items:MinLength=1
283+
// +kubebuilder:validation:items:MaxLength=4096
284+
// +kubebuilder:validation:XValidation:rule="self.all(p, p.trim() != '')",message="allowedPaths entries must not be blank or whitespace-only"
285+
// +optional
286+
AllowedPaths []string `json:"allowedPaths,omitempty"`
287+
288+
// AllowedEnvVars is the list of environment variable names that Lua scripts are permitted to
289+
// access during validation (via os.getenv, os.setenv). Matching is exact and case-sensitive.
290+
// When empty, access to all environment variables is denied. Blank or whitespace-only entries
291+
// are rejected.
292+
//
293+
// +kubebuilder:validation:MaxItems=64
294+
// +kubebuilder:validation:items:MinLength=1
295+
// +kubebuilder:validation:items:MaxLength=256
296+
// +kubebuilder:validation:XValidation:rule="self.all(e, e.trim() != '')",message="allowedEnvVars entries must not be blank or whitespace-only"
297+
// +optional
298+
AllowedEnvVars []string `json:"allowedEnvVars,omitempty"`
299+
}
300+
253301
// RoutingType defines the type of routing of this Envoy proxy.
254302
type RoutingType string
255303

api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 30 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyproxies.yaml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,53 @@ spec:
769769
and the log level is the value. If unspecified, defaults to "default: warn".
770770
type: object
771771
type: object
772+
luaStrictValidation:
773+
description: |-
774+
LuaStrictValidation defines the filesystem paths and environment variables that Lua
775+
scripts from EnvoyExtensionPolicy resources are permitted to access during Strict
776+
validation in the gateway controller.
777+
778+
The allowlist is fail-closed: when unset or empty, Lua scripts are denied access to ALL
779+
filesystem paths and environment variables during validation. Only entries that match the
780+
allowlist are permitted.
781+
782+
This field only takes effect when LuaValidation is Strict (the default). It has no effect
783+
for the InsecureSyntax or Disabled validation modes, which do not execute the security sandbox.
784+
properties:
785+
allowedEnvVars:
786+
description: |-
787+
AllowedEnvVars is the list of environment variable names that Lua scripts are permitted to
788+
access during validation (via os.getenv, os.setenv). Matching is exact and case-sensitive.
789+
When empty, access to all environment variables is denied. Blank or whitespace-only entries
790+
are rejected.
791+
items:
792+
maxLength: 256
793+
minLength: 1
794+
type: string
795+
maxItems: 64
796+
type: array
797+
x-kubernetes-validations:
798+
- message: allowedEnvVars entries must not be blank or whitespace-only
799+
rule: self.all(e, e.trim() != '')
800+
allowedPaths:
801+
description: |-
802+
AllowedPaths is the list of filesystem path prefixes that Lua scripts are permitted to
803+
access during validation (via io.open, io.input, io.output, io.lines, os.remove, os.rename).
804+
A path is allowed when it equals an entry or is contained within an entry's subtree
805+
(e.g. "/tmp" allows "/tmp/file.txt"). Paths are normalized (separators collapsed, made
806+
absolute) before matching, and any "." or ".." traversal segment is always rejected.
807+
When empty, all filesystem access is denied. Blank or whitespace-only entries are rejected,
808+
as they would otherwise match every path and disable the sandbox.
809+
items:
810+
maxLength: 4096
811+
minLength: 1
812+
type: string
813+
maxItems: 64
814+
type: array
815+
x-kubernetes-validations:
816+
- message: allowedPaths entries must not be blank or whitespace-only
817+
rule: self.all(p, p.trim() != '')
818+
type: object
772819
luaValidation:
773820
description: |-
774821
LuaValidation determines strictness of the Lua script validation for Lua EnvoyExtensionPolicies
@@ -18112,6 +18159,10 @@ spec:
1811218159
rule: '!(has(self.samplingRate) && has(self.samplingFraction))'
1811318160
type: object
1811418161
type: object
18162+
x-kubernetes-validations:
18163+
- message: luaStrictValidation can only be set when luaValidation is Strict
18164+
rule: '!has(self.luaStrictValidation) || !has(self.luaValidation) ||
18165+
self.luaValidation == ''Strict'''
1811518166
status:
1811618167
description: EnvoyProxyStatus defines the actual state of EnvoyProxy.
1811718168
properties:

charts/gateway-helm/charts/crds/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,53 @@ spec:
768768
and the log level is the value. If unspecified, defaults to "default: warn".
769769
type: object
770770
type: object
771+
luaStrictValidation:
772+
description: |-
773+
LuaStrictValidation defines the filesystem paths and environment variables that Lua
774+
scripts from EnvoyExtensionPolicy resources are permitted to access during Strict
775+
validation in the gateway controller.
776+
777+
The allowlist is fail-closed: when unset or empty, Lua scripts are denied access to ALL
778+
filesystem paths and environment variables during validation. Only entries that match the
779+
allowlist are permitted.
780+
781+
This field only takes effect when LuaValidation is Strict (the default). It has no effect
782+
for the InsecureSyntax or Disabled validation modes, which do not execute the security sandbox.
783+
properties:
784+
allowedEnvVars:
785+
description: |-
786+
AllowedEnvVars is the list of environment variable names that Lua scripts are permitted to
787+
access during validation (via os.getenv, os.setenv). Matching is exact and case-sensitive.
788+
When empty, access to all environment variables is denied. Blank or whitespace-only entries
789+
are rejected.
790+
items:
791+
maxLength: 256
792+
minLength: 1
793+
type: string
794+
maxItems: 64
795+
type: array
796+
x-kubernetes-validations:
797+
- message: allowedEnvVars entries must not be blank or whitespace-only
798+
rule: self.all(e, e.trim() != '')
799+
allowedPaths:
800+
description: |-
801+
AllowedPaths is the list of filesystem path prefixes that Lua scripts are permitted to
802+
access during validation (via io.open, io.input, io.output, io.lines, os.remove, os.rename).
803+
A path is allowed when it equals an entry or is contained within an entry's subtree
804+
(e.g. "/tmp" allows "/tmp/file.txt"). Paths are normalized (separators collapsed, made
805+
absolute) before matching, and any "." or ".." traversal segment is always rejected.
806+
When empty, all filesystem access is denied. Blank or whitespace-only entries are rejected,
807+
as they would otherwise match every path and disable the sandbox.
808+
items:
809+
maxLength: 4096
810+
minLength: 1
811+
type: string
812+
maxItems: 64
813+
type: array
814+
x-kubernetes-validations:
815+
- message: allowedPaths entries must not be blank or whitespace-only
816+
rule: self.all(p, p.trim() != '')
817+
type: object
771818
luaValidation:
772819
description: |-
773820
LuaValidation determines strictness of the Lua script validation for Lua EnvoyExtensionPolicies
@@ -18111,6 +18158,10 @@ spec:
1811118158
rule: '!(has(self.samplingRate) && has(self.samplingFraction))'
1811218159
type: object
1811318160
type: object
18161+
x-kubernetes-validations:
18162+
- message: luaStrictValidation can only be set when luaValidation is Strict
18163+
rule: '!has(self.luaStrictValidation) || !has(self.luaValidation) ||
18164+
self.luaValidation == ''Strict'''
1811418165
status:
1811518166
description: EnvoyProxyStatus defines the actual state of EnvoyProxy.
1811618167
properties:

internal/gatewayapi/luavalidator/lua_validator.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,59 @@ func (l *LuaValidator) getLuaValidation() egv1a1.LuaValidation {
9090
return egv1a1.LuaValidationStrict
9191
}
9292

93+
// allowlistData generates the Lua source consumed by security.lua: the path allowlist as a list and
94+
// the env var allowlist as a map (name -> true). The allowlist is fail-closed; an unconfigured
95+
// EnvoyProxy yields empty tables, denying all access.
96+
func (l *LuaValidator) allowlistData() string {
97+
var paths, envVars []string
98+
if l.envoyProxy != nil && l.envoyProxy.Spec.LuaStrictValidation != nil {
99+
paths = l.envoyProxy.Spec.LuaStrictValidation.AllowedPaths
100+
envVars = l.envoyProxy.Spec.LuaStrictValidation.AllowedEnvVars
101+
}
102+
103+
var b strings.Builder
104+
105+
b.WriteString("__lua_allowed_paths = {")
106+
for i, p := range paths {
107+
if i > 0 {
108+
b.WriteString(", ")
109+
}
110+
b.WriteString(luaStringLiteral(p))
111+
}
112+
b.WriteString("}\n")
113+
114+
b.WriteString("__lua_allowed_env_vars = {")
115+
for i, e := range envVars {
116+
if i > 0 {
117+
b.WriteString(", ")
118+
}
119+
// Spaces around the key are required: "[[[..." would lex as a long-string opener.
120+
b.WriteString("[ ")
121+
b.WriteString(luaStringLiteral(e))
122+
b.WriteString(" ] = true")
123+
}
124+
b.WriteString("}\n")
125+
126+
return b.String()
127+
}
128+
129+
// luaStringLiteral encodes s as a Lua long-bracket literal ([==[...]==]) with an equals level that
130+
// avoids any closing delimiter in s. Long brackets don't interpret escapes, so spec values are
131+
// passed verbatim as data and cannot inject Lua.
132+
func luaStringLiteral(s string) string {
133+
level := 0
134+
for strings.Contains(s, "]"+strings.Repeat("=", level)+"]") {
135+
level++
136+
}
137+
eq := strings.Repeat("=", level)
138+
// Lua strips a leading newline after the opening bracket; re-add it to preserve such values.
139+
prefix := ""
140+
if strings.HasPrefix(s, "\n") {
141+
prefix = "\n"
142+
}
143+
return "[" + eq + "[" + prefix + s + "]" + eq + "]"
144+
}
145+
93146
// newLuaState creates a new Lua state with global settings and resource limits applied
94147
// Returns the Lua state and a cancel function that must be called when done
95148
func (l *LuaValidator) newLuaState() (*lua.LState, context.CancelFunc) {
@@ -123,6 +176,8 @@ func (l *LuaValidator) runLua(code string) error {
123176

124177
// Execute mocks first (trusted code, needs setmetatable, defines StreamHandle, etc.)
125178
_ = L.DoString(mockData)
179+
// Inject the allowlists before security.lua, which reads and then clears them.
180+
_ = L.DoString(l.allowlistData())
126181
// Execute Lua security wrappers (trusted code) to protect the gateway controller
127182
// See security advisory: https://github.com/envoyproxy/gateway/security/advisories/GHSA-xrwg-mqj6-6m22
128183
_ = L.DoString(securityData)

0 commit comments

Comments
 (0)