Skip to content

Commit 27bf1cc

Browse files
authored
[release/v1.6] cherry-pick security patch for v1.6.2 (#7920)
* Merge commit from fork * Runs Lua `Strict` validation in the gateway along with a security hardening module. This module blocks dangerous Lua functionality that may lead to arbitrary code execution on the controller pods. * Renamed `Syntax` to `InsecureSyntax` validation mode to signify that in this mode Lua won't be validated for possible security gaps. Won't be breaking as `Syntax` mode was not available for use yet. Added a similar warning to `Disabled` validation mode as well. * Supports option to `disableLua` EnvoyExtensionPolicies feature in the gateway to eliminate arbitrary Lua execution as an attack surface. Signed-off-by: Rudrakh Panigrahi <rudrakh97@gmail.com> * [release/v1.6] v1.6.2 release notes update (#7923) Signed-off-by: Rudrakh Panigrahi <rudrakh97@gmail.com> --------- Signed-off-by: Rudrakh Panigrahi <rudrakh97@gmail.com>
1 parent 57dd6ad commit 27bf1cc

22 files changed

+903
-93
lines changed

api/v1alpha1/envoygateway_types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,9 @@ type ExtensionAPISettings struct {
251251
// EnableBackend enables Envoy Gateway to
252252
// reconcile and implement the Backend resources.
253253
EnableBackend bool `json:"enableBackend"`
254+
// DisableLua determines if Lua EnvoyExtensionPolicies should be disabled.
255+
// If set to true, the Lua EnvoyExtensionPolicy feature will be disabled.
256+
DisableLua bool `json:"disableLua"`
254257
}
255258

256259
// EnvoyGatewayProvider defines the desired configuration of a provider.

api/v1alpha1/envoyproxy_types.go

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -176,23 +176,29 @@ type EnvoyProxySpec struct {
176176
LuaValidation *LuaValidation `json:"luaValidation,omitempty"`
177177
}
178178

179-
// +kubebuilder:validation:Enum=Strict;Disabled
179+
// +kubebuilder:validation:Enum=Strict;InsecureSyntax;Disabled
180180
type LuaValidation string
181181

182182
const (
183183
// LuaValidationStrict is the default level and checks for issues during script execution.
184-
// Recommended if your scripts only use the standard Envoy Lua stream handle API.
184+
// Recommended if your scripts only use the standard Envoy Lua stream handle API and no external libraries.
185185
// For supported APIs, see: https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/lua_filter#stream-handle-api
186+
// INFO: This validation mode executes Lua scripts from EnvoyExtensionPolicy (EEP) resources in the gateway controller.
187+
// Since the Gateway controller watches EEPs across all namespaces (or namespaces matching the configured selector),
188+
// unprivileged users can create EEPs in their namespaces and cause arbitrary Lua code to execute in the Gateway controller process.
189+
// Security measures are in place to prevent unsafe Lua code from accessing critical system resources on the controller
190+
// and fail validation, preventing the unsafe code from flowing to the data plane proxy.
186191
LuaValidationStrict LuaValidation = "Strict"
187192

188-
// LuaValidationSyntax checks for syntax errors in the Lua script.
189-
// Note that this is not a full runtime validation and does not check for issues during script execution.
190-
// This is recommended if your scripts use external libraries that are not supported by Lua runtime validation.
191-
LuaValidationSyntax LuaValidation = "Syntax"
193+
// LuaValidationInsecureSyntax checks for Lua syntax errors only.
194+
// Useful if your scripts use external libraries other than the standard Envoy Lua stream handle API.
195+
// WARNING: This mode does NOT offer any runtime validations, so no security measures are applied to validate Lua code safety.
196+
// Not recommended unless you completely trust all EnvoyExtensionPolicy resources.
197+
LuaValidationInsecureSyntax LuaValidation = "InsecureSyntax"
192198

193-
// LuaValidationDisabled disables all validations of Lua scripts.
194-
// Scripts will be accepted and executed without any validation checks.
195-
// This is not recommended unless both runtime and syntax validations are failing unexpectedly.
199+
// LuaValidationDisabled disables all Lua script validations.
200+
// WARNING: This mode does NOT offer any runtime or syntax validations, so no security measures are applied to validate Lua code safety.
201+
// Not recommended unless you completely trust all EnvoyExtensionPolicy resources.
196202
LuaValidationDisabled LuaValidation = "Disabled"
197203
)
198204

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,7 @@ spec:
466466
Default: Strict
467467
enum:
468468
- Strict
469+
- InsecureSyntax
469470
- Disabled
470471
type: string
471472
mergeGateways:

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,7 @@ spec:
465465
Default: Strict
466466
enum:
467467
- Strict
468+
- InsecureSyntax
468469
- Disabled
469470
type: string
470471
mergeGateways:

internal/gatewayapi/envoyextensionpolicy.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,11 @@ func (t *Translator) buildLuas(policy *egv1a1.EnvoyExtensionPolicy, resources *r
645645
return nil, nil
646646
}
647647

648+
// If Lua EnvoyExtensionPolicies are disabled, skip building Lua filters.
649+
if len(policy.Spec.Lua) > 0 && t.LuaEnvoyExtensionPolicyDisabled {
650+
return nil, fmt.Errorf("Skipping Lua EnvoyExtensionPolicy as feature is disabled in the Gateway")
651+
}
652+
648653
luaIRList := make([]ir.Lua, 0, len(policy.Spec.Lua))
649654

650655
for idx, ep := range policy.Spec.Lua {

internal/gatewayapi/luavalidator/lua_validator.go

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
package luavalidator
77

88
import (
9+
"context"
910
_ "embed"
1011
"fmt"
1112
"strings"
13+
"time"
1214

1315
lua "github.com/yuin/gopher-lua"
1416

@@ -18,6 +20,7 @@ import (
1820
const (
1921
envoyOnRequestFunctionName = "envoy_on_request"
2022
envoyOnResponseFunctionName = "envoy_on_response"
23+
luaExecutionTimeout = 5 * time.Second
2124
)
2225

2326
// mockData contains mocks of Envoy supported APIs for Lua filters.
@@ -26,6 +29,14 @@ const (
2629
//go:embed mocks.lua
2730
var mockData string
2831

32+
// securityData contains Lua security wrappers that restrict access to sensitive filesystem paths and
33+
// critical environment variables during Lua code validation in the gateway controller.
34+
//
35+
// TODO: Create a configurable set of filesystem paths and environment variables to check for in the proxy apart from default configured here.
36+
//
37+
//go:embed security.lua
38+
var securityData string
39+
2940
// LuaValidator validates user provided Lua for compatibility with Envoy supported Lua HTTP filter
3041
// Validation strictness is controlled by the validation field
3142
type LuaValidator struct {
@@ -47,12 +58,12 @@ func (l *LuaValidator) Validate() error {
4758
return fmt.Errorf("expected one of %s() or %s() to be defined", envoyOnRequestFunctionName, envoyOnResponseFunctionName)
4859
}
4960
if strings.Contains(l.code, envoyOnRequestFunctionName) {
50-
if err := l.validate(mockData + "\n" + l.code + "\n" + envoyOnRequestFunctionName + "(StreamHandle)"); err != nil {
61+
if err := l.validate(l.code + "\n" + envoyOnRequestFunctionName + "(StreamHandle)"); err != nil {
5162
return fmt.Errorf("failed to validate with %s: %w", envoyOnRequestFunctionName, err)
5263
}
5364
}
5465
if strings.Contains(l.code, envoyOnResponseFunctionName) {
55-
if err := l.validate(mockData + "\n" + l.code + "\n" + envoyOnResponseFunctionName + "(StreamHandle)"); err != nil {
66+
if err := l.validate(l.code + "\n" + envoyOnResponseFunctionName + "(StreamHandle)"); err != nil {
5667
return fmt.Errorf("failed to validate with %s: %w", envoyOnResponseFunctionName, err)
5768
}
5869
}
@@ -62,7 +73,7 @@ func (l *LuaValidator) Validate() error {
6273
// validate runs the validation on given code
6374
func (l *LuaValidator) validate(code string) error {
6475
switch l.getLuaValidation() {
65-
case egv1a1.LuaValidationSyntax:
76+
case egv1a1.LuaValidationInsecureSyntax:
6677
return l.loadLua(code)
6778
case egv1a1.LuaValidationDisabled:
6879
return nil
@@ -79,22 +90,61 @@ func (l *LuaValidator) getLuaValidation() egv1a1.LuaValidation {
7990
return egv1a1.LuaValidationStrict
8091
}
8192

82-
// newLuaState creates a new Lua state with global settings applied
83-
func (l *LuaValidator) newLuaState() *lua.LState {
84-
L := lua.NewState()
93+
// newLuaState creates a new Lua state with global settings and resource limits applied
94+
// Returns the Lua state and a cancel function that must be called when done
95+
func (l *LuaValidator) newLuaState() (*lua.LState, context.CancelFunc) {
96+
// Configure Lua VM with resource limits to prevent DoS
97+
L := lua.NewState(lua.Options{
98+
CallStackSize: 256, // Default call stack depth
99+
RegistrySize: 5120, // Default registry size (256 * 20)
100+
RegistryMaxSize: 5120, // Disable registry growth for security
101+
RegistryGrowStep: 32, // Default registry growth step (not used since max = initial)
102+
SkipOpenLibs: false, // We need standard libraries (filtered by security.lua)
103+
IncludeGoStackTrace: false, // Don't leak Go stack traces to user errors
104+
})
105+
106+
// Create context with timeout to prevent infinite loops or long-running code
107+
ctx, cancel := context.WithTimeout(context.Background(), luaExecutionTimeout)
108+
L.SetContext(ctx)
109+
85110
// Suppress all print statements
86111
L.SetGlobal("print", L.NewFunction(func(L *lua.LState) int {
87112
return 0
88113
}))
89-
return L
114+
return L, cancel
90115
}
91116

92117
// runLua interprets and runs the provided Lua code in runtime using gopher-lua
93118
// Refer: https://github.com/yuin/gopher-lua?tab=readme-ov-file#differences-between-lua-and-gopherlua
94119
func (l *LuaValidator) runLua(code string) error {
95-
L := l.newLuaState()
120+
L, cancel := l.newLuaState()
121+
defer cancel()
96122
defer L.Close()
97-
if err := L.DoString(code); err != nil {
123+
124+
// Execute mocks first (trusted code, needs setmetatable, defines StreamHandle, etc.)
125+
_ = L.DoString(mockData)
126+
// Execute Lua security wrappers (trusted code) to protect the gateway controller
127+
// See security advisory: https://github.com/envoyproxy/gateway/security/advisories/GHSA-xrwg-mqj6-6m22
128+
_ = L.DoString(securityData)
129+
130+
// Execute user-provided code with panic recovery to prevent controller crashes
131+
// Although gopher-lua returns errors if it internally panics,
132+
// this is a defensive measure to prevent the controller from crashing.
133+
var err error
134+
func() {
135+
defer func() {
136+
if r := recover(); r != nil {
137+
err = fmt.Errorf("lua execution panic: %v", r)
138+
}
139+
}()
140+
err = L.DoString(code)
141+
}()
142+
143+
if err != nil {
144+
// Check if timeout occurred
145+
if L.Context().Err() == context.DeadlineExceeded {
146+
return fmt.Errorf("lua execution timeout: code took longer than %v", luaExecutionTimeout)
147+
}
98148
return err
99149
}
100150
return nil
@@ -103,7 +153,8 @@ func (l *LuaValidator) runLua(code string) error {
103153
// loadLua loads the Lua code into the Lua state, does not run it
104154
// This is used to check for syntax errors in the Lua code
105155
func (l *LuaValidator) loadLua(code string) error {
106-
L := l.newLuaState()
156+
L, cancel := l.newLuaState()
157+
defer cancel()
107158
defer L.Close()
108159
if _, err := L.LoadString(code); err != nil {
109160
return err

0 commit comments

Comments
 (0)