diff --git a/attachments/kong/plugins/open-appsec-waf-kong-plugin/handler.lua b/attachments/kong/plugins/open-appsec-waf-kong-plugin/handler.lua index 9836521..3307ddb 100755 --- a/attachments/kong/plugins/open-appsec-waf-kong-plugin/handler.lua +++ b/attachments/kong/plugins/open-appsec-waf-kong-plugin/handler.lua @@ -5,6 +5,21 @@ local kong = kong local NanoHandler = {} +local function handle_verdict(ctx, verdict, response, session_data) + if verdict ~= nano.AttachmentVerdict.INSPECT then + ctx.cleanup_needed = true + if verdict == nano.AttachmentVerdict.DROP then + return nano.handle_custom_response(session_data, response) + end + return true + end + return false +end + +local function validate_context(ctx) + return ctx and ctx.session_data and ctx.session_id +end + NanoHandler.PRIORITY = 3000 NanoHandler.VERSION = "1.0.0" @@ -20,7 +35,7 @@ function NanoHandler.access(conf) local session_data = nano.init_session(session_id) if not session_data then - kong.ctx.plugin.cleanup_needed = false + ctx.cleanup_needed = false return end @@ -49,11 +64,7 @@ function NanoHandler.access(conf) local contains_body = has_content_length and 1 or 0 local verdict, response = nano.send_data(session_id, session_data, meta_data, req_headers, contains_body, nano.HttpChunkType.HTTP_REQUEST_FILTER) - if verdict ~= nano.AttachmentVerdict.INSPECT then - ctx.cleanup_needed = true - if verdict == nano.AttachmentVerdict.DROP then - return nano.handle_custom_response(session_data, response) - end + if handle_verdict(ctx, verdict, response, session_data) then return end @@ -61,11 +72,7 @@ function NanoHandler.access(conf) local body = kong.request.get_raw_body() if body and #body > 0 then verdict, response = nano.send_body(session_id, session_data, body, nano.HttpChunkType.HTTP_REQUEST_BODY) - if verdict ~= nano.AttachmentVerdict.INSPECT then - ctx.cleanup_needed = true - if verdict == nano.AttachmentVerdict.DROP then - return nano.handle_custom_response(session_data, response) - end + if handle_verdict(ctx, verdict, response, session_data) then return end else @@ -73,7 +80,7 @@ function NanoHandler.access(conf) if body_file then local file = io.open(body_file, "rb") if file then - local chunk_size = 8192 + local chunk_size = nano.CHUNK_SIZE local chunk_count = 0 local start_time = ngx.now() local timeout_sec = nano.get_request_processing_timeout_sec() @@ -101,17 +108,16 @@ function NanoHandler.access(conf) kong.log.debug("Sending request body chunk ", chunk_count, " of size ", #chunk, " bytes to C module") verdict, response = nano.send_body(session_id, session_data, chunk, nano.HttpChunkType.HTTP_REQUEST_BODY) - if verdict ~= nano.AttachmentVerdict.INSPECT then + if handle_verdict(ctx, verdict, response, session_data) then file:close() - ctx.cleanup_needed = true - if verdict == nano.AttachmentVerdict.DROP then - return nano.handle_custom_response(session_data, response) - end return end end file:close() kong.log.debug("Sent ", chunk_count, " chunks from request body file") + else + kong.log.err("Failed to open request body file: ", err or "unknown error") + ctx.cleanup_needed = true end else kong.log.err("Request body expected but no body data or file available") @@ -128,11 +134,7 @@ function NanoHandler.access(conf) return end - if verdict ~= nano.AttachmentVerdict.INSPECT then - ctx.cleanup_needed = true - if verdict == nano.AttachmentVerdict.DROP then - return nano.handle_custom_response(session_data, response) - end + if handle_verdict(ctx, verdict, response, session_data) then return end end @@ -140,6 +142,11 @@ end function NanoHandler.header_filter(conf) local ctx = kong.ctx.plugin + + if not validate_context(ctx) then + return + end + if nano.is_session_finalized(ctx.session_data) then kong.log.debug("Session has already been inspected, no need for further inspection") return @@ -183,6 +190,11 @@ end function NanoHandler.body_filter(conf) local ctx = kong.ctx.plugin + + if not validate_context(ctx) then + return + end + local chunk = ngx.arg[1] local eof = ngx.arg[2] @@ -241,8 +253,16 @@ function NanoHandler.body_filter(conf) if eof then if ctx.body_seen or ctx.expect_body == false then - ctx.cleanup_needed = true - local verdict, response = nano.end_inspection(session_id, session_data, nano.HttpChunkType.HTTP_RESPONSE_END) + local ok, verdict, response = pcall(function() + return nano.end_inspection(session_id, session_data, nano.HttpChunkType.HTTP_RESPONSE_END) + end) + + if not ok then + kong.log.err("Error ending response inspection: ", verdict, " - failing open") + ctx.cleanup_needed = true + return + end + if verdict ~= nano.AttachmentVerdict.INSPECT then kong.log.debug("Final verdict after end_inspection: ", verdict) ctx.cleanup_needed = true @@ -255,15 +275,21 @@ function NanoHandler.body_filter(conf) end end end - + ctx.cleanup_needed = true end end function NanoHandler.log(conf) local ctx = kong.ctx.plugin - if ctx.cleanup_needed then - nano.fini_session(ctx.session_data) - nano.cleanup_all() + if not ctx then + return + end + + if ctx.session_data then + if ctx.cleanup_needed then + nano.fini_session(ctx.session_data) + nano.cleanup_all() + end ctx.session_data = nil ctx.session_id = nil collectgarbage("collect") diff --git a/attachments/kong/plugins/open-appsec-waf-kong-plugin/nano_ffi.lua b/attachments/kong/plugins/open-appsec-waf-kong-plugin/nano_ffi.lua index b88c73d..20e811e 100755 --- a/attachments/kong/plugins/open-appsec-waf-kong-plugin/nano_ffi.lua +++ b/attachments/kong/plugins/open-appsec-waf-kong-plugin/nano_ffi.lua @@ -3,6 +3,16 @@ local nano_attachment = require "lua_attachment_wrapper" local kong = kong local nano = {} +local MAX_HEADERS = 10000 +local DEFAULT_TIMEOUT_SEC = 3 +local CHUNK_SIZE = 8192 + +local HTTP_STATUS_MIN = 100 +local HTTP_STATUS_MAX = 599 +local HTTP_STATUS_FORBIDDEN = 403 +local HTTP_STATUS_OK = 200 +local HTTP_STATUS_TEMPORARY_REDIRECT = 307 + nano.session_counter = 0 nano.attachments = {} nano.allocated_strings = {} @@ -68,57 +78,59 @@ typedef struct NanoHttpModificationList { } NanoHttpModificationList; ]] --- Assuming you already defined the C struct somewhere: --- ffi.cdef[[ --- typedef struct NanoHttpModificationList { ... } NanoHttpModificationList; --- ]] - local NanoHttpModificationListPtr = ffi.typeof("NanoHttpModificationList*") +local function get_worker_attachment() + local worker_id = ngx.worker.id() + return worker_id, nano.attachments[worker_id] +end + +local function validate_status_code(code, default) + if not code or code < HTTP_STATUS_MIN or code > HTTP_STATUS_MAX then + kong.log.warn("Invalid response code received: ", code, " - using ", default, " instead") + return default + end + return code +end + function nano.generate_session_id() nano.session_counter = nano.session_counter + 1 local worker_id = ngx.worker.id() return tonumber(string.format("%d%05d", worker_id, nano.session_counter)) end +nano.CHUNK_SIZE = CHUNK_SIZE + -- Returns: status_code, body, headers function nano.get_custom_response_data(session_data, response) - local worker_id = ngx.worker.id() - local attachment = nano.attachments[worker_id] + local worker_id, attachment = get_worker_attachment() if not attachment then kong.log.warn("Cannot handle custom response: Attachment not available for worker ", worker_id, " - failing open") - return 200, "Request allowed due to attachment unavailability", {} + return HTTP_STATUS_OK, "Request allowed due to attachment unavailability", {} end local response_type = nano_attachment.get_web_response_type(attachment, session_data, response) if response_type == nano.WebResponseType.RESPONSE_CODE_ONLY then - local code = nano_attachment.get_response_code(response) - if not code or code < 100 or code > 599 then - kong.log.warn("Invalid response code received: ", code, " - using 403 instead") - code = 403 - end + local code = validate_status_code(nano_attachment.get_response_code(response), HTTP_STATUS_FORBIDDEN) kong.log.debug("Response code only: ", code) return code, "", {} end if response_type == nano.WebResponseType.REDIRECT_WEB_RESPONSE then local location = nano_attachment.get_redirect_page(attachment, session_data, response) - local code = nano_attachment.get_response_code(response) or 307 + local code = validate_status_code(nano_attachment.get_response_code(response), HTTP_STATUS_TEMPORARY_REDIRECT) return code, "", { ["Location"] = location } end local block_page = nano_attachment.get_block_page(attachment, session_data, response) if not block_page then kong.log.debug("Failed to retrieve custom block page for session ", session_data) - return 403, "", {} - end - local code = nano_attachment.get_response_code(response) - if not code or code < 100 or code > 599 then - kong.log.warn("Invalid response code received: ", code, " - using 403 instead") - code = 403 + return HTTP_STATUS_FORBIDDEN, "", {} end + + local code = validate_status_code(nano_attachment.get_response_code(response), HTTP_STATUS_FORBIDDEN) kong.log.debug("Block page response with code: ", code) return code, block_page, { ["Content-Type"] = "text/html" } end @@ -154,11 +166,11 @@ function nano.free_all_nano_str() end function nano.free_http_headers(header_data) - for _, nano_header in ipairs(nano.allocate_headers) do - nano_attachment.freeHttpHeaders(nano_header) - end + for _, nano_header in ipairs(nano.allocate_headers) do + nano_attachment.freeHttpHeaders(nano_header) + end - nano.allocate_headers = {} + nano.allocate_headers = {} end function nano.free_all_metadata() @@ -206,8 +218,7 @@ function nano.init_attachment() end function nano.init_session(session_id) - local worker_id = ngx.worker.id() - local attachment = nano.attachments[worker_id] + local worker_id, attachment = get_worker_attachment() if not attachment then kong.log.warn("Attachment not found for worker ", worker_id, ", attempting to reinitialize...") @@ -230,8 +241,7 @@ function nano.init_session(session_id) end function nano.is_session_finalized(session_data) - local worker_id = ngx.worker.id() - local attachment = nano.attachments[worker_id] + local worker_id, attachment = get_worker_attachment() if not attachment or not session_data then kong.log.debug("Cannot check session finalization: Invalid attachment or session_data") @@ -245,6 +255,7 @@ function nano.handle_start_transaction() local stream_info = kong.request local full_host = stream_info.get_host() + local host = full_host:match("([^:]+)") local method = stream_info.get_method() @@ -261,6 +272,11 @@ function nano.handle_start_transaction() uri, client_ip, tonumber(client_port) or 0, "", "" ) + if not metadata then + kong.log.err("Failed to create HTTP metadata") + return nil + end + table.insert(nano.allocated_metadata, metadata) collectgarbage("stop") @@ -273,7 +289,10 @@ function nano.handleHeaders(headers) local index = 0 for key, value in pairs(headers) do - if index > 10000 then break end + if index >= MAX_HEADERS then + kong.log.warn("Header limit reached (", MAX_HEADERS, "), skipping remaining headers") + break + end if key == "x-request-id" or key == ":method" or key == ":path" or key == ":scheme" or @@ -304,8 +323,7 @@ function nano.handleHeaders(headers) end function nano.send_data(session_id, session_data, meta_data, header_data, contains_body, chunk_type) - local worker_id = ngx.worker.id() - local attachment = nano.attachments[worker_id] + local worker_id, attachment = get_worker_attachment() if not attachment then kong.log.warn("Attachment not available for worker ", worker_id, " - failing open") @@ -325,8 +343,7 @@ function nano.send_data(session_id, session_data, meta_data, header_data, contai end function nano.send_body(session_id, session_data, body_chunk, chunk_type) - local worker_id = ngx.worker.id() - local attachment = nano.attachments[worker_id] + local worker_id, attachment = get_worker_attachment() if not attachment then kong.log.warn("Attachment not available for worker ", worker_id, " - failing open") @@ -378,8 +395,7 @@ function nano.send_response_body(session_id, session_data, body_chunk) end function nano.fini_session(session_data) - local worker_id = ngx.worker.id() - local attachment = nano.attachments[worker_id] + local worker_id, attachment = get_worker_attachment() if not attachment or not session_data then kong.log.warn("Cannot finalize session: Invalid attachment or session_data for worker ", worker_id) @@ -392,8 +408,7 @@ function nano.fini_session(session_data) end function nano.send_response_headers(session_id, session_data, headers, status_code, content_length) - local worker_id = ngx.worker.id() - local attachment = nano.attachments[worker_id] + local worker_id, attachment = get_worker_attachment() if not attachment then kong.log.warn("Attachment not available for worker ", worker_id, " - failing open") @@ -463,8 +478,7 @@ function nano.handle_header_modifications(headers, modifications) end function nano.end_inspection(session_id, session_data, chunk_type) - local worker_id = ngx.worker.id() - local attachment = nano.attachments[worker_id] + local worker_id, attachment = get_worker_attachment() if not attachment then kong.log.warn("Attachment not available for worker ", worker_id, " - failing open during end_inspection") @@ -485,30 +499,24 @@ function nano.end_inspection(session_id, session_data, chunk_type) return verdict, response end -function nano.get_request_processing_timeout_sec() - local worker_id = ngx.worker.id() - local attachment = nano.attachments[worker_id] +local function get_timeout_sec(timeout_func) + local worker_id, attachment = get_worker_attachment() if not attachment then kong.log.warn("Attachment not available for worker ", worker_id, " - using default timeout") - return 3 + return DEFAULT_TIMEOUT_SEC end - local timeout_msec = nano_attachment.get_request_processing_timeout_msec(attachment) + local timeout_msec = timeout_func(attachment) return timeout_msec / 1000.0 end +function nano.get_request_processing_timeout_sec() + return get_timeout_sec(nano_attachment.get_request_processing_timeout_msec) +end + function nano.get_response_processing_timeout_sec() - local worker_id = ngx.worker.id() - local attachment = nano.attachments[worker_id] - - if not attachment then - kong.log.warn("Attachment not available for worker ", worker_id, " - using default timeout") - return 3 - end - - local timeout_msec = nano_attachment.get_response_processing_timeout_msec(attachment) - return timeout_msec / 1000.0 + return get_timeout_sec(nano_attachment.get_response_processing_timeout_msec) end return nano \ No newline at end of file