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 4f641e6..27457d7 100755 --- a/attachments/kong/plugins/open-appsec-waf-kong-plugin/handler.lua +++ b/attachments/kong/plugins/open-appsec-waf-kong-plugin/handler.lua @@ -51,7 +51,6 @@ function NanoHandler.access(conf) 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 - kong.log.err("Failed to send request headers to C module - failing open") ctx.cleanup_needed = true if verdict == nano.AttachmentVerdict.DROP then return nano.handle_custom_response(session_data, response) @@ -62,7 +61,7 @@ function NanoHandler.access(conf) if contains_body == 1 then local body = kong.request.get_raw_body() if body and #body > 0 then - kong.log.debug("Request body found in memory, size: ", #body) + kong.log.err("Request body found in memory, size: ", #body) 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 @@ -72,7 +71,7 @@ function NanoHandler.access(conf) return end else - kong.log.debug("Request body not in memory, attempting to read from buffer/file") + kong.log.err("Request body not in memory, attempting to read from buffer/file") local body_data = ngx.var.request_body if body_data and #body_data > 0 then kong.log.debug("Found request body in nginx var, size: ", #body_data) @@ -85,17 +84,21 @@ function NanoHandler.access(conf) return end else + kong.log.err("reading request body from file as last resort") + local body_file = ngx.var.request_body_file if body_file then - kong.log.debug("Reading request body from file: ", body_file) + kong.log.err("Reading request body from file: ", body_file) local file = io.open(body_file, "rb") if file then local chunk_size = 8192 local chunk_count = 0 while true do + kong.log.err("Reading next chunk from request body file") local chunk = file:read(chunk_size) if not chunk or #chunk == 0 then + kong .log.err("End of request body file reached") break end @@ -113,10 +116,10 @@ function NanoHandler.access(conf) end end file:close() - kong.log.debug("Sent ", chunk_count, " chunks from request body file") + kong.log.err("Sent ", chunk_count, " chunks from request body file") end else - kong.log.debug("Request body expected but no body data or file available") + kong.log.err("Request body expected but no body data or file available") end end end @@ -172,7 +175,9 @@ function NanoHandler.header_filter(conf) if verdict ~= nano.AttachmentVerdict.INSPECT then ctx.cleanup_needed = true if verdict == nano.AttachmentVerdict.DROP then - return nano.handle_custom_response(session_data, response) + kong.log.warn("DROP verdict in header_filter - will replace response in body_filter") + ctx.blocked = true + ctx.block_response = response end return end @@ -184,7 +189,40 @@ function NanoHandler.body_filter(conf) local ctx = kong.ctx.plugin local chunk = ngx.arg[1] local eof = ngx.arg[2] - if nano.is_session_finalized(ctx.session_data) then + + + local session_id = ctx.session_id + local session_data = ctx.session_data + + if ctx.blocked then + if ctx.block_response then + local code, body, headers = nano.get_custom_response_data(session_data, ctx.block_response) + kong.log.info("Replacing response with custom response, code: ", code) + ngx.status = code + + -- Set custom headers + for header_name, header_value in pairs(headers) do + ngx.header[header_name] = header_value + end + + if body and #body > 0 then + ngx.header["Content-Length"] = #body + ngx.arg[1] = body + else + ngx.header["Content-Length"] = 0 + ngx.arg[1] = "" + end + else + kong.log.err("Missing session_data or block_response, sending empty response") + ngx.status = 403 + ngx.arg[1] = "" + end + ngx.arg[2] = true + ctx.cleanup_needed = true + return + end + + if nano.is_session_finalized(session_data) then kong.log.info("Session has already been inspected, no need for further inspection") return end @@ -194,13 +232,6 @@ function NanoHandler.body_filter(conf) return end - local session_id = ctx.session_id - local session_data = ctx.session_data - if not session_id or not session_data then - kong.log.debug("No session data found in body_filter") - return - end - if not ctx.body_filter_start_time then ctx.body_filter_start_time = ngx.now() end @@ -227,9 +258,20 @@ function NanoHandler.body_filter(conf) kong.log.debug("Final verdict for response body chunk: ", verdict) ctx.cleanup_needed = true if verdict == nano.AttachmentVerdict.DROP then - ngx.arg[1] = "" + kong.log.warn("DROP verdict in body_filter - replacing response immediately") + local code, body, headers = nano.get_custom_response_data(session_data, response) + ngx.status = code + for header_name, header_value in pairs(headers) do + ngx.header[header_name] = header_value + end + if body and #body > 0 then + ngx.header["Content-Length"] = #body + ngx.arg[1] = body + else + ngx.header["Content-Length"] = 0 + ngx.arg[1] = "" + end ngx.arg[2] = true - return nano.handle_custom_response(session_data, response) end return end @@ -243,9 +285,20 @@ function NanoHandler.body_filter(conf) kong.log.debug("Final verdict after end_inspection: ", verdict) ctx.cleanup_needed = true if verdict == nano.AttachmentVerdict.DROP then - ngx.arg[1] = "" + kong.log.warn("DROP verdict at EOF - replacing response immediately") + local code, body, headers = nano.get_custom_response_data(session_data, response) + ngx.status = code + for header_name, header_value in pairs(headers) do + ngx.header[header_name] = header_value + end + if body and #body > 0 then + ngx.header["Content-Length"] = #body + ngx.arg[1] = body + else + ngx.header["Content-Length"] = 0 + ngx.arg[1] = "" + end ngx.arg[2] = true - return nano.handle_custom_response(session_data, response) end return end 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 0fa2ee2..a688b1c 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 @@ -81,13 +81,14 @@ function nano.generate_session_id() return tonumber(string.format("%d%05d", worker_id, nano.session_counter)) end -function nano.handle_custom_response(session_data, response) +-- 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] if not attachment then kong.log.warn("Cannot handle custom response: Attachment not available for worker ", worker_id, " - failing open") - return kong.response.exit(200, "Request allowed due to attachment unavailability") + return 200, "Request allowed due to attachment unavailability", {} end local response_type = nano_attachment.get_web_response_type(attachment, session_data, response) @@ -99,18 +100,19 @@ function nano.handle_custom_response(session_data, response) code = 403 end kong.log.debug("Response code only: ", code) - return kong.response.exit(code, "") + return code, "", {} end if response_type == nano.WebResponseType.REDIRECT_WEB_RESPONSE then local location = nano_attachment.get_redirect_page(attachment, session_data, response) - return kong.response.exit(307, "", { ["Location"] = location }) + local code = nano_attachment.get_response_code(response) or 307 + return code, "", { ["Location"] = location } end local block_page = nano_attachment.get_block_page(attachment, session_data, response) if not block_page then kong.log.err("Failed to retrieve custom block page for session ", session_data) - return kong.response.exit(500, { message = "Internal Server Error" }) + return 403, "", {} end local code = nano_attachment.get_response_code(response) if not code or code < 100 or code > 599 then @@ -118,8 +120,13 @@ function nano.handle_custom_response(session_data, response) code = 403 end kong.log.debug("Block page response with code: ", code) - return kong.response.exit(code, block_page, { ["Content-Type"] = "text/html" }) + return code, block_page, { ["Content-Type"] = "text/html" } +end +-- Wrapper for backward compatibility - calls kong.response.exit() in access phase +function nano.handle_custom_response(session_data, response) + local code, body, headers = nano.get_custom_response_data(session_data, response) + return kong.response.exit(code, body, headers) end