fresh start

This commit is contained in:
wiaamm
2025-11-26 13:06:36 +02:00
parent 83599c6f1b
commit 9b2054f4d5

View File

@@ -1,4 +1,6 @@
local nano = require "kong.plugins.open-appsec-waf-kong-plugin.nano_ffi" local module_name = ...
local prefix = module_name:match("^(.-)handler$")
local nano = require(prefix .. "nano_ffi")
local kong = kong local kong = kong
local NanoHandler = {} local NanoHandler = {}
@@ -165,141 +167,146 @@ function NanoHandler.header_filter(conf)
end end
ctx.expect_body = not (status_code == 204 or status_code == 304 or (100 <= status_code and status_code < 200) or content_length == 0) ctx.expect_body = not (status_code == 204 or status_code == 304 or (100 <= status_code and status_code < 200) or content_length == 0)
-- Initialize response body processing start time for timeout tracking
if ctx.expect_body then
ctx.res_body_start_time = ngx.now() * 1000 -- Convert to milliseconds
end
end end
function NanoHandler.body_filter(conf) function NanoHandler.body_filter(conf)
local ctx = kong.ctx.plugin local ctx = kong.ctx.plugin
if not ctx or ctx.blocked then if ctx.blocked then
return return
end end
local session_id = ctx.session_id local session_id = ctx.session_id
local session_data = ctx.session_data local session_data = ctx.session_data
if not session_id or not session_data or ctx.session_finalized then if not session_id or not session_data or ctx.session_finalized then
return return
end end
-- Initialize chunk counter on first call
ctx.body_buffer_chunk = ctx.body_buffer_chunk or 0
-- Get the current chunk from ngx.arg[1] (this is how Kong streams body data)
local chunk = ngx.arg[1] local chunk = ngx.arg[1]
local eof = ngx.arg[2] local eof = ngx.arg[2] -- true on last chunk
-- Check if response body processing has timed out -- Try Kong API for small in-memory bodies (backward compatibility)
if ctx.res_body_start_time and not ctx.res_body_timeout_triggered then local full_body = kong.response.get_raw_body()
local current_time = ngx.now() * 1000
local elapsed_time = current_time - ctx.res_body_start_time -- Determine if we're dealing with a full body or streaming chunks
local timeout = conf.res_body_thread_timeout_msec or 150 local is_streaming = (full_body == nil and chunk ~= nil)
if elapsed_time > timeout then if full_body and not ctx.body_seen then
ctx.res_body_timeout_triggered = true -- Small response body - use Kong API (original behavior)
kong.log.warn("[OpenAppSec] Response body processing timeout exceeded (",
string.format("%.2f", elapsed_time), "ms > ", timeout,
"ms). Failing open - skipping body inspection for session ", session_id)
end
end
-- Handle body chunks
if chunk and #chunk > 0 then
ctx.body_seen = true ctx.body_seen = true
kong.log.debug("Processing in-memory response body, size: ", #full_body)
-- Initialize chunk index if not exists
if not ctx.body_buffer_chunk then
ctx.body_buffer_chunk = 0
end
-- If timeout triggered in a previous chunk, skip nano inspection and just pass through
if ctx.res_body_timeout_triggered then
kong.log.debug("[OpenAppSec] Skipping body chunk ", ctx.body_buffer_chunk, " inspection due to timeout")
ctx.body_buffer_chunk = ctx.body_buffer_chunk + 1
-- Just pass the chunk through without inspection
return
end
-- Check time before calling nano.send_body
local before_send = ngx.now() * 1000
-- Use pcall to catch any errors from nano.send_body local ok, result = pcall(function()
local ok, verdict, response, modifications = pcall(function() return {nano.send_body(session_id, session_data, full_body, nano.HttpChunkType.HTTP_RESPONSE_BODY)}
return nano.send_body(session_id, session_data, chunk, nano.HttpChunkType.HTTP_RESPONSE_BODY)
end) end)
local after_send = ngx.now() * 1000 if ok and result and result[1] then
local send_duration = after_send - before_send local verdict = result[1]
local response = result[2]
-- Check if the call failed or took too long local modifications = result[3]
if not ok then
kong.log.err("[OpenAppSec] nano.send_body failed: ", verdict, ". Failing open.")
ctx.res_body_timeout_triggered = true
ctx.body_buffer_chunk = ctx.body_buffer_chunk + 1
return
end
-- Log if the send_body call took a long time and trigger fail-open
local timeout = conf.res_body_thread_timeout_msec or 150
if send_duration > timeout then
kong.log.warn("[OpenAppSec] nano.send_body took ", string.format("%.2f", send_duration),
"ms (> ", timeout, "ms) for chunk ", ctx.body_buffer_chunk,
". Triggering fail-open for remaining chunks.")
ctx.res_body_timeout_triggered = true
-- Still process this chunk's verdict since we already have it
elseif send_duration > (timeout * 0.5) then
-- Warning if approaching timeout
kong.log.info("[OpenAppSec] nano.send_body took ", string.format("%.2f", send_duration),
"ms for chunk ", ctx.body_buffer_chunk, " (approaching timeout threshold)")
end
-- Handle body modifications if any if modifications then
if modifications then full_body = nano.handle_body_modifications(full_body, modifications, ctx.body_buffer_chunk)
chunk = nano.handle_body_modifications(chunk, modifications, ctx.body_buffer_chunk) kong.response.set_raw_body(full_body)
ngx.arg[1] = chunk
end
ctx.body_buffer_chunk = ctx.body_buffer_chunk + 1
if verdict == nano.AttachmentVerdict.DROP then
nano.fini_session(session_data)
ctx.session_finalized = true
local result = nano.handle_custom_response(session_data, response)
nano.cleanup_all()
-- Stop current streaming
ngx.arg[1] = ""
ngx.arg[2] = true
return result
end
end
-- Handle end of response
if eof then
if ctx.body_seen or ctx.expect_body == false then
-- If timeout was triggered, finalize without sending end inspection
if ctx.res_body_timeout_triggered then
kong.log.warn("[OpenAppSec] Response body inspection skipped due to timeout. Session ",
session_id, " finalized without end_inspection.")
nano.fini_session(session_data)
nano.cleanup_all()
ctx.session_finalized = true
return
end end
local verdict, response = nano.end_inspection(session_id, session_data, nano.HttpChunkType.HTTP_RESPONSE_END) ctx.body_buffer_chunk = ctx.body_buffer_chunk + 1
if verdict == nano.AttachmentVerdict.DROP then if verdict == nano.AttachmentVerdict.DROP then
nano.fini_session(session_data) nano.fini_session(session_data)
ctx.session_finalized = true ctx.session_finalized = true
local result = nano.handle_custom_response(session_data, response) local custom_result = nano.handle_custom_response(session_data, response)
nano.cleanup_all() nano.cleanup_all()
ngx.arg[1] = "" return custom_result
ngx.arg[2] = true
return result
end end
else
nano.fini_session(session_data) kong.log.warn("nano.send_body failed for in-memory body: ", result)
nano.cleanup_all() ctx.body_buffer_chunk = ctx.body_buffer_chunk + 1
ctx.session_finalized = true
end end
elseif is_streaming and chunk then
-- Large response body - streaming chunks (file-buffered or large in-memory)
ctx.body_seen = true
-- Process chunk even if empty (empty chunks are valid in streaming responses)
local chunk_size = (type(chunk) == "string") and #chunk or 0
if chunk_size > 0 then
kong.log.debug("Processing response body chunk #", ctx.body_buffer_chunk, ", size: ", chunk_size, ", eof: ", eof)
local ok, result = pcall(function()
return {nano.send_body(session_id, session_data, chunk, nano.HttpChunkType.HTTP_RESPONSE_BODY)}
end)
if ok and result and result[1] then
local verdict = result[1]
local response = result[2]
local modifications = result[3]
-- Apply modifications to this chunk
if modifications then
chunk = nano.handle_body_modifications(chunk, modifications, ctx.body_buffer_chunk)
end
ctx.body_buffer_chunk = ctx.body_buffer_chunk + 1
if verdict == nano.AttachmentVerdict.DROP then
nano.fini_session(session_data)
ctx.session_finalized = true
ngx.arg[1] = nil -- Clear the output
ngx.arg[2] = true -- Force EOF
local custom_result = nano.handle_custom_response(session_data, response)
nano.cleanup_all()
return custom_result
end
-- Update the chunk that will be sent to client (CRITICAL for streaming)
ngx.arg[1] = chunk
else
kong.log.warn("nano.send_body failed for chunk #", ctx.body_buffer_chunk, ": ", result)
ctx.body_buffer_chunk = ctx.body_buffer_chunk + 1
-- Fail-open: pass chunk through unmodified
-- ngx.arg[1] is already set to the original chunk
end
else
-- Empty chunk - just track it, don't send to nano
kong.log.debug("Empty chunk #", ctx.body_buffer_chunk, ", eof: ", eof)
ctx.body_buffer_chunk = ctx.body_buffer_chunk + 1
end
end
-- Finalize session on last chunk (EOF) or when no body expected
if eof or (ctx.expect_body == false and not ctx.body_seen) then
kong.log.debug("Finalizing response inspection, body_seen: ", ctx.body_seen, ", eof: ", eof)
local ok, result = pcall(function()
return {nano.end_inspection(session_id, session_data, nano.HttpChunkType.HTTP_RESPONSE_END)}
end)
if ok and result and result[1] then
local verdict = result[1]
local response = result[2]
if verdict == nano.AttachmentVerdict.DROP then
nano.fini_session(session_data)
ctx.session_finalized = true
ngx.arg[1] = nil -- Clear any remaining output
ngx.arg[2] = true -- Force EOF
local custom_result = nano.handle_custom_response(session_data, response)
nano.cleanup_all()
return custom_result
end
else
kong.log.warn("nano.end_inspection failed: ", result)
end
nano.fini_session(session_data)
nano.cleanup_all()
ctx.session_finalized = true
end end
end end