Skip to main content

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_request and envoy_on_response to intercept traffic.
  • Configuration: Lua filters are defined in the VirtualService or DestinationRule resources.

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 URI

local uri = ngx.var.uri
ngx.var.uri = '/transformed' .. uri

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.insert with 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

Security Best Practices for Lua Scripts

  • Always sanitize and validate inputs to prevent injection attacks.
  • Use isolated execution environments to avoid privilege escalation.
  • Enforce resource limits to prevent abuse or DoS attacks.
  • Apply strict access control to limit script permissions.

Was this article helpful?