Skip to content

Commit 1a9750a

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

13 files changed

Lines changed: 692 additions & 380 deletions

File tree

api/v1alpha1/envoyproxy_types.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,20 @@ type EnvoyProxySpec struct {
189189
// +optional
190190
LuaValidation *LuaValidation `json:"luaValidation,omitempty"`
191191

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

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

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

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

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

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)