In this blog, we will be setting envoyfilter that uses lua to intercept and log payload,
Ground work
We will be using httpbin that is shipped as part of istio samples for our sidecar setup.
kubectl apply -f gateway.yaml
kubectl apply -f httpbin.yaml
And then we can create our envoyfilter. This is what our envoyfilter looks like this. Points to note : we are enabling buffering. Without buffering, the lua won't log and there won't be any output in the istio-proxy container log.
Before apply the script below ensure you have set the logging level - otherwise you might not see the logs details. You need to configure the logging level with the following command:-
istioctl proxy-config log httpbin-65bbd9c89d-n449w --level lua:info
Next we will apply the following script
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: log-payload
namespace: default
spec:
workloadSelector:
labels:
app: httpbin
configPatches:
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
listener:
filterChain:
filter:
name: envoy.filters.network.http_connection_manager
subFilter:
name: envoy.filters.http.router
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.lua
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
#inlineCode: |
default_source_code:
inline_string: |
function envoy_on_request(request_handle)
request_handle:logInfo("[Lua] Intercepted a request.")
request_handle:headers():add("dm3ch-test", "dm3ch wins")
end
function envoy_on_response(response_handle)
response_handle:headers():add("dm3ch-test", "dm3ch wins")
response_handle:logInfo("[Lua] Intercepted a response.")
end
A mmore advance example
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: log-payload
namespace: default
spec:
workloadSelector:
labels:
app: httpbin
configPatches:
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
listener:
filterChain:
filter:
name: envoy.filters.network.http_connection_manager
subFilter:
name: envoy.filters.http.router
patch:
operation: INSERT_BEFORE
value:
name: envoy.filters.http.lua
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
inlineCode: |
function envoy_on_request(request_handle)
-- Get HTTP method
local method = request_handle:headers():get(":method")
request_handle:logInfo("[Lua] HTTP Method: " .. (method or "unknown"))
-- Get other headers
local authority = request_handle:headers():get(":authority")
local path = request_handle:headers():get(":path")
request_handle:logInfo("[Lua] Authority: " .. (authority or "unknown"))
request_handle:logInfo("[Lua] Path: " .. (path or "unknown"))
-- Get request body (requires buffering)
local body = request_handle:body()
local body_length = 0
if body then
body_length = body:length()
request_handle:logInfo("[Lua] Request body length: " .. body_length)
-- Log first 1024 characters of body to avoid huge logs
if body_length > 0 then
local body_string = body:getBytes(0, math.min(1024, body_length))
request_handle:logInfo("[Lua] Request body (first 1KB): " .. body_string)
end
else
request_handle:logInfo("[Lua] No request body")
end
-- Add custom header
request_handle:headers():add("dm3ch-test", "dm3ch wins - Method: " .. (method or "unknown"))
end
function envoy_on_response(response_handle)
-- Get response status
local status = response_handle:headers():get(":status")
response_handle:logInfo("[Lua] Response Status: " .. (status or "unknown"))
-- Get response body
local body = response_handle:body()
local body_length = 0
if body then
body_length = body:length()
response_handle:logInfo("[Lua] Response body length: " .. body_length)
-- Log first 512 characters of response body
if body_length > 0 then
local body_string = body:getBytes(0, math.min(512, body_length))
response_handle:logInfo("[Lua] Response body (first 512B): " .. body_string)
end
end
-- Add custom header
response_handle:headers():add("dm3ch-test", "dm3ch wins - Status: " .. (status or "unknown"))
end
Important to note:-
1. Outputs is coming from istio-proxy container not on the application log and it should look like this:
2. Envoy filter does take about 1-2 minutes to gets applied or sync to your sidecar - depending on how many envoyfilter you hav in the cluster. If this is a local cluster with 1 envoyfilter, then it should takes 5 seconds. You don't have to restart your pod.
3. default_source_code vs inline_code?
Both can be used for inlining your Lua code.
4. Script parameter name matching. Lua parameter for a method needs to be matched as well. For example
Having 'handle' as a parameter name will cause issue and an error message - "index global response_handle" is nil. This refers to the fact that we're not using a matching parameter called 'request_handle'
function envoy_on_request(handle)
handle:logInfo("request")
handle:headers():add("dm3ch-test", "dm3ch wins")
handle:headers().get
end
To fix it,
function envoy_on_request(request_handle)
request_handle:logInfo("[Lua] Intercepted a request.")
request_handle:headers():add("dm3ch-test", "dm3ch wins")
end
For response, the parameter name needs to match as well. For example
function envoy_on_response(response_handle)
response_handle:headers():add("dm3ch-test", "dm3ch wins")
response_handle:logInfo("[Lua] Intercepted a response.")
end
Comments