Implementing Lua Extensions
1. Understanding Lua Filter Integration
Service meshes like Istio support extending functionality through Lua filters, which allow custom logic to be injected into the data plane. These filters can modify requests or responses, enforce policies, or perform other operations at runtime.
| Aspect | Description | Notes |
|---|---|---|
| Filter Type | lua |
Used in Istio's FilterChain or Listener |
| Configuration Location | filter_chains |
Defined within listener or filter_chain |
| Lua Script | inline_code |
Can be embedded directly in the configuration or loaded from a file |
Example: Basic Lua Filter Configuration
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: example-lua-filter
spec:
hosts:
- httpbin.org
http:
- route:
- destination:
host: httpbin.org
port:
number: 8000
filters:
- name: istio.filters.http.lua
typedConfig:
"@type": type.googleapis.com/istio.filters.http.lua.Lua
inlineCode: |
function envoy_on_request(request_handle)
request_handle:headers():add("X-Lua-Injected", "true")
end
Note: Lua filters are executed in the
envoy proxy, and must be compatible with the version of Envoy used in the service mesh.
Key Concepts
- Lua Script Context: Filters run in the context of an HTTP request or response, providing access to headers, body, and metadata.
- Hook Functions: Lua scripts define hook functions like
envoy_on_requestandenvoy_on_responseto intercept traffic. - Configuration: Lua filters are defined in the
VirtualServiceorDestinationRuleresources.
2. Creating Lua Filter Scripts
| Property | Description | Example |
|---|---|---|
| lua_filter | Defines a Lua filter script to be executed in the service mesh | lua_filter: /path/to/filter.lua |
| filter_name | Name of the filter as registered in the service mesh | filter_name: "my_lua_filter" |
| on_request | Function to execute on incoming requests | function on_request(request) |
| on_response | Function to execute on outgoing responses | function on_response(response) |
Example: Basic Lua Filter Script
-- Example Lua filter script
function on_request(request)
request:add_header("X-Processed-By", "Lua-Filter")
end
function on_response(response)
response:add_header("X-Filtered-At", "Edge")
end
Note: Lua filter scripts must be written in a compatible Lua version (5.1 or 5.3) and must not use unsupported libraries.
3. Configuring Lua EnvoyFilter
| Property | Description | Values | Notes |
|---|---|---|---|
| name | Name of the EnvoyFilter | string |
Must be unique within the namespace |
| namespace | Namespace where the filter is applied | string |
Optional, defaults to the current namespace |
| priority | Priority of the filter | integer |
Higher values are processed first |
| match | Match criteria for applying the filter | object |
Defines where and when to apply the filter |
| config | Configuration for the Lua filter | object |
Contains Lua script and related settings |
Example: Basic Lua EnvoyFilter Configuration
apiVersion: networking.istio.io/v1beta1
kind: EnvoyFilter
metadata:
name: lua-filter
namespace: istio-system
spec:
priority: 100
match:
context: SIDECAR_INBOUND
listener:
portNumber: 80
config:
listener:
filterChain:
filters:
- name: envoy.filters.http.lua
typedConfig:
$type: type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
inlineCode: |
function envoy_on_request(request)
request:headers():add("X-Lua-Processed", "true")
end
Note: The
inlineCode field contains the actual Lua script that will be executed by Envoy.
Warning: Lua scripts run in the context of Envoy and can impact performance or stability if not written carefully.
4. Implementing Request Transformation
| Feature | Description | Usage |
|---|---|---|
| request.transform | Modifies incoming request headers, body, or URI | request.transform.headers['X-Transformed'] = 'true' |
| request.uri | Modifies the URI of the request | request.uri = '/new/path' |
| request.body | Modifies the body of the request | request.body = '{"modified": true}' |
Example: Transforming Request Headers
local headers = ngx.req.get_headers()
headers['X-Transformed'] = 'true'
ngx.req.set_headers(headers)
Example: Modifying Request Body
local body = ngx.req.get_body_data()
if body then
local newBody = string.gsub(body, 'old', 'new')
ngx.req.set_body_data(newBody)
end
5. Implementing Response Transformation
| Feature | Description | Usage |
|---|---|---|
| response_transform | Transforms the response body using Lua scripts | response_transform = "function(response) return string.gsub(response, 'old', 'new') end" |
| response_headers | Modifies response headers | response_headers['X-Transformed'] = 'true' |
Example: Transforming Response Body
function response_transform(response)
local new_response = string.gsub(response, "old_value", "new_value")
return new_response
end
Example: Modifying Response Headers
function response_transform(response)
response_headers['X-Processed'] = 'true'
return response
end
6. Managing Lua Script Lifecycle
| Phase | Description | Actions | Notes |
|---|---|---|---|
| Initialization | Lua script is loaded and parsed | local myscript = require("myscript") |
Executed once per script load |
| Execution | Script functions are invoked | myscript.handle_request() |
Can be called multiple times |
| Cleanup | Resource release and destruction | collectgarbage("collect") |
Manual cleanup optional in Lua |
Example: Lua Script Initialization and Cleanup
-- Initialize script
local myscript = require("my_script")
-- Define cleanup function
function cleanup()
print("Cleaning up resources")
collectgarbage("collect")
end
-- Use script
local result = myscript.handle_request()
-- Cleanup when done
cleanup()
Note: Lua's garbage collector is automatic, but explicit cleanup can help manage resources in long-running environments.
7. Debugging Lua Filters
Example: Basic Debugging with print()
function envoy_on_request(request_handle)
-- Log request info
print("Request method: " .. request_handle:getMethod())
print("Request path: " .. request_handle:getPath())
-- Set a debug header
request_handle:headers():add("X-Debug", "enabled")
end
| Method | Description | Notes |
|---|---|---|
| print() | Outputs to the Envoy log | Use for basic debugging and logging |
| request_handle:getMethod() | Retrieves the HTTP method | Use in debugging request flow |
| request_handle:getPath() | Retrieves the request path | Use to inspect routing behavior |
| request_handle:headers() | Accesses request headers | Use to inspect or modify headers during debug |
Note: Debug logs are written to the Envoy access log and can be viewed via
stdout or file output depending on the logging configuration.
Example: Using Envoy's Debug Headers
function envoy_on_request(request_handle)
local headers = request_handle:headers()
-- Add debug info to headers
headers:add("X-Request-ID", "debug-" .. os.time())
headers:add("X-Filter-Debug", "true")
-- Log to console (if enabled)
print("Processing request with ID: " .. headers:get("X-Request-ID"))
end
Debugging Tips
- Use
print()to output runtime values to the log - Add custom headers to trace request flow through filters
- Check Envoy's access logs for debug output
- Use
os.time()to generate unique identifiers for debugging sessions
8. Optimizing Lua Performance
| Optimization Technique | Description | Notes |
|---|---|---|
| Use local variables | local var = table[key] |
Reduces global lookup overhead |
| Precompute constants | local PI = math.pi |
Avoid repeated function calls |
| Avoid table creation in loops | local t = {} for i=1,100 do t[i] = i end |
Reusing tables improves performance |
| Minimize string operations | local s = "prefix" .. value .. "suffix" |
Use string.format sparingly |
Use table.insert efficiently |
local t = {} table.insert(t, value) |
Pre-allocate if size is known |
Example: Optimized Loop
local count = 1000
local result = {}
local temp = {}
for i = 1, count do
temp[i] = i * 2
end
for i = 1, count do
result[i] = temp[i]
end
Note: Profiling tools like
lua-profiler can help identify bottlenecks in Lua scripts.
Key Tips
- Cache frequently accessed values in local variables
- Avoid unnecessary table creation inside loops
- Use
table.insertwith pre-allocated tables when possible - Minimize string concatenation in performance-critical paths
9. Handling Lua Errors
| Error Type | Description | Handling Method | Notes |
|---|---|---|---|
| lua_error | Generic Lua error | pcall or xpcall |
Use xpcall with custom error handler for better control |
| runtime_error | Runtime errors in Lua code | pcall with error handling |
Errors thrown by error() or runtime issues |
| syntax_error | Syntax errors in Lua script | Caught at compile time | Detected during loadstring or dofile |
Example: Using pcall to Handle Lua Errors
local success, result = pcall(function()
local value = 10 / 0
return value
end)
if not success then
print("Error occurred: " .. result)
else
print("Result: " <= result)
end
Example: Using xpcall for Custom Error Handling
local function customErrorHandler(err)
print("Custom error handler: " .. err)
return "Handled gracefully"
end
local success, result = xpcall(function()
error("Something went wrong")
end, customErrorHandler)
if success then
print("Success: " .. result)
else
print("Failed with error: " .. result)
end
Note:
pcall returns true and the result if successful, or false and the error message if not.
Warning: Always handle Lua errors in service mesh extensions to prevent cascading failures.
10. Testing Lua Extensions
Example: Basic Lua Extension Test
-- test_extension.lua
local extension = require("my_extension")
-- Test function
assert(extension.add(2, 3) == 5)
print("Test passed: add function works")
-- Test with negative numbers
assert(extension.multiply(-2, 3) == -6)
print("Test passed: multiply function works")
| Test Type | Description | Tools |
|---|---|---|
| Unit Testing | Testing individual functions or modules in isolation | luaunit, busted |
| Integration Testing | Testing how Lua extensions interact with the service mesh | lua-resty-http, ngx.testing |
| End-to-End Testing | Testing the full flow from client to service mesh and back | k6, locust |
Note: When testing Lua extensions in a service mesh, ensure that the test environment mimics the production setup as closely as possible to catch runtime issues.
Example: Using LuaUnit for Unit Tests
-- test_my_extension.lua
local luaunit = require("luaunit")
local extension = require("my_extension")
TestExtension = {}
function TestExtension:test_add()
luaunit.assertEquals(extension.add(2, 3), 5)
end
function TestExtension:test_multiply()
luaunit.assertEquals(extension.multiply(-2, 3), -6)
end
os.exit(luaunit.run())
Testing Best Practices
- Use mocking frameworks to simulate external dependencies
- Ensure tests run in a controlled environment that mirrors production
- Validate error handling and edge cases in Lua extensions
- Automate tests as part of the CI/CD pipeline
11. Implementing Lua Header Manipulation
Example: Setting a Custom Header
local headers = {
['X-Custom-Header'] = 'value'
}
for key, value in pairs(headers) do
ngx.req.set_header(key, value)
end
| Method | Description | Usage |
|---|---|---|
| ngx.req.set_header | Sets a request header | ngx.req.set_header('X-Header', 'value') |
| ngx.req.get_header | Gets a request header value | local value = ngx.req.get_header('X-Header') |
| ngx.req.clear_header | Removes a request header | ngx.req.clear_header('X-Header') |
Note: Header manipulation in OpenResty occurs during the
rewrite or access phases.
Example: Modifying Response Headers
local headers = {
['Content-Type'] = 'application/json'
}
for key, value in pairs(headers) do
ngx.header[key] = value
end
| Method | Description | Usage |
|---|---|---|
| ngx.header | Access to response headers | ngx.header['X-Response'] = 'value' |
| ngx.resp.get_headers | Retrieves all response headers | local headers = ngx.resp.get_headers() |
12. Managing Lua Script Security
Note: Lua scripts in service meshes can execute within the mesh's control plane or data plane, posing security risks if not properly managed.
| Security Aspect | Description | Recommendation |
|---|---|---|
| Script Isolation | Lua scripts may run in shared environments, increasing risk of cross-script interference or privilege escalation. | Use sandboxed execution environments to isolate each script and prevent access to sensitive system resources. |
| Input Validation | Lua scripts often process external inputs (e.g., headers, body), which can lead to injection attacks if not sanitized. | Validate and sanitize all inputs before processing in Lua scripts. Avoid direct use of user-provided data without sanitization. |
| Resource Limits | Unrestricted Lua scripts can consume excessive CPU or memory, leading to DoS or performance degradation. | Enforce strict resource limits (e.g., CPU time, memory usage) per script to prevent abuse. |
| Access Control | Lua scripts may have access to sensitive data or APIs if not properly restricted. | Implement strict RBAC and scope-based access control for scripts to limit their permissions. |
Example: Lua Script with Input Sanitization
-- Example of a Lua script that sanitizes input before processing
local function sanitize_input(input)
if type(input) ~= "string" then
return nil
end
-- Remove dangerous characters or patterns
return input:gsub("[^%w%s%-_]", "")
end
local user_data = sanitize_input(request.header["user-id"])
if user_data then
-- Process sanitized input
response.body = "Processed: " .. user_data
else
response.status = 400
end