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 3000e9c..2db2edf 100755 --- a/attachments/kong/plugins/open-appsec-waf-kong-plugin/handler.lua +++ b/attachments/kong/plugins/open-appsec-waf-kong-plugin/handler.lua @@ -7,9 +7,8 @@ NanoHandler.PRIORITY = 3000 NanoHandler.VERSION = "1.0.0" NanoHandler.sessions = {} -NanoHandler.processed_requests = {} -- Track processed requests +NanoHandler.processed_requests = {} --- **Handles worker initialization** function NanoHandler.init_worker() nano.init_attachment() end @@ -35,7 +34,11 @@ function NanoHandler.access(conf) kong.ctx.plugin.session_id = session_id local meta_data = nano.handle_start_transaction() + if not meta_data then kong.log.err("Failed to handle start transaction - failing open") + return + end + local req_headers = nano.handleHeaders(headers) local has_content_length = tonumber(ngx.var.http_content_length) and tonumber(ngx.var.http_content_length) > 0 @@ -62,10 +65,8 @@ function NanoHandler.access(conf) return result end else - -- Body might be buffered to file, try to read it using nginx variables kong.log.debug("Request body not in memory, attempting to read from buffer/file") - -- Try to read the request body using nginx.var 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) @@ -76,13 +77,11 @@ function NanoHandler.access(conf) return nano.handle_custom_response(session_data, response) end else - -- Try to read from the temporary file if available local body_file = ngx.var.request_body_file if body_file then kong.log.debug("Reading request body from file: ", body_file) local file = io.open(body_file, "rb") if file then - -- Read entire body at once local entire_body = file:read("*all") file:close() diff --git a/attachments/kong/plugins/open-appsec-waf-kong-plugin/lua_attachment_wrapper.c b/attachments/kong/plugins/open-appsec-waf-kong-plugin/lua_attachment_wrapper.c index 138936c..83c1b1d 100755 --- a/attachments/kong/plugins/open-appsec-waf-kong-plugin/lua_attachment_wrapper.c +++ b/attachments/kong/plugins/open-appsec-waf-kong-plugin/lua_attachment_wrapper.c @@ -8,7 +8,6 @@ #define MAX_HEADERS 10000 -// Initialize NanoAttachment for worker static int lua_init_nano_attachment(lua_State *L) { int worker_id = luaL_checkinteger(L, 1); int num_workers = luaL_checkinteger(L, 2); @@ -70,13 +69,20 @@ static int lua_get_block_page(lua_State *L) { } int offset = 0; - memcpy(result + offset, page.title_prefix.data, page.title_prefix.len); offset += page.title_prefix.len; - memcpy(result + offset, page.title.data, page.title.len); offset += page.title.len; - memcpy(result + offset, page.body_prefix.data, page.body_prefix.len); offset += page.body_prefix.len; - memcpy(result + offset, page.body.data, page.body.len); offset += page.body.len; - memcpy(result + offset, page.uuid_prefix.data, page.uuid_prefix.len); offset += page.uuid_prefix.len; - memcpy(result + offset, page.uuid.data, page.uuid.len); offset += page.uuid.len; - memcpy(result + offset, page.uuid_suffix.data, page.uuid_suffix.len); offset += page.uuid_suffix.len; + memcpy(result + offset, page.title_prefix.data, page.title_prefix.len); + offset += page.title_prefix.len; + memcpy(result + offset, page.title.data, page.title.len); + offset += page.title.len; + memcpy(result + offset, page.body_prefix.data, page.body_prefix.len); + offset += page.body_prefix.len; + memcpy(result + offset, page.body.data, page.body.len); + offset += page.body.len; + memcpy(result + offset, page.uuid_prefix.data, page.uuid_prefix.len); + offset += page.uuid_prefix.len; + memcpy(result + offset, page.uuid.data, page.uuid.len); + offset += page.uuid.len; + memcpy(result + offset, page.uuid_suffix.data, page.uuid_suffix.len); + offset += page.uuid_suffix.len; result[size] = '\0'; lua_pushlstring(L, result, size); @@ -98,7 +104,6 @@ static int lua_get_redirect_page(lua_State *L) { return 1; } -// Free HttpMetaData memory static int lua_free_http_metadata(lua_State *L) { HttpMetaData *metadata = (HttpMetaData *)lua_touserdata(L, 1); if (!metadata) return 0; @@ -117,7 +122,6 @@ static int lua_free_http_metadata(lua_State *L) { } -// Allocate memory for nano_str_t (long-lived, must be freed later) static int lua_createNanoStrAlloc(lua_State *L) { const char* str = luaL_checkstring(L, 1); if (!str) { @@ -131,11 +135,11 @@ static int lua_createNanoStrAlloc(lua_State *L) { lua_pushnil(L); lua_pushstring(L, "Failed to allocate memory for string"); return 2; -} + } nano_str_t* nanoStr = (nano_str_t*)malloc(sizeof(nano_str_t)); if (!nanoStr) { - free(c_str); // Clean up already allocated string + free(c_str); lua_pushnil(L); lua_pushstring(L, "Failed to allocate memory for nano_str_t"); return 2; @@ -148,7 +152,6 @@ static int lua_createNanoStrAlloc(lua_State *L) { return 1; } -// Free nano_str_t (Memory cleanup) static int lua_freeNanoStr(lua_State *L) { nano_str_t* nanoStr = (nano_str_t*)lua_touserdata(L, 1); if (nanoStr) { @@ -158,8 +161,6 @@ static int lua_freeNanoStr(lua_State *L) { return 0; } -// Allocate memory for HttpHeaders -// Allocate memory for HttpHeaders static int lua_allocHttpHeaders(lua_State *L) { size_t max_headers = 10000; @@ -180,7 +181,6 @@ static int lua_allocHttpHeaders(lua_State *L) { return 1; } -// Free HttpHeaders memory static int lua_freeHttpHeaders(lua_State *L) { HttpHeaders* headers = (HttpHeaders*)lua_touserdata(L, 1); if (headers) { @@ -190,7 +190,6 @@ static int lua_freeHttpHeaders(lua_State *L) { return 0; } -// Set the headers_count in HttpHeaders static int lua_setHeaderCount(lua_State *L) { HttpHeaders* headers = (HttpHeaders*)lua_touserdata(L, 1); int count = luaL_checkinteger(L, 2); @@ -203,7 +202,6 @@ static int lua_setHeaderCount(lua_State *L) { return 0; } -// Helper function to convert Lua string to nano_str_t static void lua_fill_nano_str(lua_State *L, int index, nano_str_t *nano_str) { size_t len; const char *str = luaL_checklstring(L, index, &len); @@ -214,20 +212,18 @@ static void lua_fill_nano_str(lua_State *L, int index, nano_str_t *nano_str) { return; } - // Allocate memory + 1 for null termination nano_str->data = (char *)malloc(len + 1); - if (!nano_str->data) { // Check if allocation failed + if (!nano_str->data) { nano_str->len = 0; return; } - memcpy(nano_str->data, str, len); // Copy exact `len` bytes - nano_str->data[len] = '\0'; // Manually null-terminate + memcpy(nano_str->data, str, len); + nano_str->data[len] = '\0'; nano_str->len = len; } -// Set a header element in HttpHeaderData static int lua_setHeaderElement(lua_State *L) { HttpHeaders *headers = (HttpHeaders *)lua_touserdata(L, 1); int index = luaL_checkinteger(L, 2); @@ -237,7 +233,6 @@ static int lua_setHeaderElement(lua_State *L) { return 1; } - // Safely allocate and set header key/value lua_fill_nano_str(L, 3, &headers->data[index].key); lua_fill_nano_str(L, 4, &headers->data[index].value); @@ -245,7 +240,6 @@ static int lua_setHeaderElement(lua_State *L) { return 1; } -// Initialize session static int lua_init_session(lua_State *L) { NanoAttachment* attachment = (NanoAttachment*) lua_touserdata(L, 1); SessionID session_id = luaL_checkinteger(L, 2); @@ -267,7 +261,6 @@ static int lua_init_session(lua_State *L) { return 1; } -// Finalize session static int lua_fini_session(lua_State *L) { NanoAttachment* attachment = (NanoAttachment*) lua_touserdata(L, 1); HttpSessionData* session_data = lua_touserdata(L, 2); @@ -283,7 +276,6 @@ static int lua_fini_session(lua_State *L) { return 1; } -// Check if session is finalized static int lua_is_session_finalized(lua_State *L) { NanoAttachment* attachment = (NanoAttachment*) lua_touserdata(L, 1); HttpSessionData* session_data = (HttpSessionData*) lua_touserdata(L, 2); @@ -298,7 +290,6 @@ static int lua_is_session_finalized(lua_State *L) { return 1; } -// Function to extract request metadata and create HttpMetaData struct static int lua_create_http_metadata(lua_State *L) { HttpMetaData *metadata = (HttpMetaData *)malloc(sizeof(HttpMetaData)); if (!metadata) { @@ -316,41 +307,33 @@ static int lua_create_http_metadata(lua_State *L) { lua_fill_nano_str(L, 9, &metadata->parsed_host); lua_fill_nano_str(L, 10, &metadata->parsed_uri); - // Store pointer in Lua lua_pushlightuserdata(L, metadata); - return 1; // Return userdata + return 1; } -// Send data to NanoAttachment static int lua_send_data(lua_State *L) { - // Retrieve function arguments from Lua NanoAttachment* attachment = (NanoAttachment*) lua_touserdata(L, 1); SessionID session_id = luaL_checkinteger(L, 2); HttpSessionData *session_data = (HttpSessionData*) lua_touserdata(L, 3); HttpChunkType chunk_type = luaL_checkinteger(L, 4); HttpMetaData* meta_data = (HttpMetaData*) lua_touserdata(L, 5); HttpHeaders* req_headers = (HttpHeaders*) lua_touserdata(L, 6); - int contains_body = luaL_checkinteger(L, 7); // Convert Lua boolean to C int - //int contains_body = 0; + int contains_body = luaL_checkinteger(L, 7); - // Validate inputs if (!attachment || !session_data || !meta_data || !req_headers) { lua_pushstring(L, "Error: received NULL data in lua_send_data"); return lua_error(L); } - // Allocate memory for HttpRequestFilterData HttpRequestFilterData *filter_data = (HttpRequestFilterData *)malloc(sizeof(HttpRequestFilterData)); if (!filter_data) { return luaL_error(L, "Memory allocation failed for HttpRequestFilterData"); } - // Populate HttpRequestFilterData struct filter_data->meta_data = meta_data; filter_data->req_headers = req_headers; filter_data->contains_body = contains_body; - // Create attachment data AttachmentData attachment_data; attachment_data.session_id = session_id; attachment_data.session_data = session_data; @@ -360,7 +343,6 @@ static int lua_send_data(lua_State *L) { AttachmentVerdictResponse* res_ptr = malloc(sizeof(AttachmentVerdictResponse)); *res_ptr = SendDataNanoAttachment(attachment, &attachment_data); - // Free allocated memory free(filter_data); lua_pushinteger(L, res_ptr->verdict); @@ -381,7 +363,6 @@ static int lua_send_body(lua_State *L) { return lua_error(L); } - // For small bodies, send as a single chunk if (body_len <= 8 * 1024) { HttpBody http_chunks; http_chunks.bodies_count = 1; @@ -400,7 +381,6 @@ static int lua_send_body(lua_State *L) { AttachmentVerdictResponse* res_ptr = malloc(sizeof(AttachmentVerdictResponse)); *res_ptr = SendDataNanoAttachment(attachment, &attachment_data); - // Push verdict lua_pushinteger(L, res_ptr->verdict); lua_pushlightuserdata(L, res_ptr); @@ -478,7 +458,6 @@ static int lua_end_inspection(lua_State *L) { attachment_data.chunk_type = chunk_type; attachment_data.data = NULL; - // Send NULL to indicate end of data AttachmentVerdictResponse* res_ptr = malloc(sizeof(AttachmentVerdictResponse)); *res_ptr = SendDataNanoAttachment(attachment, &attachment_data); @@ -488,7 +467,6 @@ static int lua_end_inspection(lua_State *L) { return 2; } -// Send response headers to NanoAttachment static int lua_send_response_headers(lua_State *L) { NanoAttachment* attachment = (NanoAttachment*) lua_touserdata(L, 1); SessionID session_id = luaL_checkinteger(L, 2); @@ -502,7 +480,6 @@ static int lua_send_response_headers(lua_State *L) { return lua_error(L); } - // Create ResHttpHeaders structure exactly as in filter.go ResHttpHeaders res_headers; res_headers.headers = headers; res_headers.response_code = status_code; @@ -521,18 +498,15 @@ static int lua_send_response_headers(lua_State *L) { return 2; } -// Free verdict response memory static int lua_free_verdict_response(lua_State *L) { AttachmentVerdictResponse *response = (AttachmentVerdictResponse *)lua_touserdata(L, 1); if (!response) return 0; - // Free the AttachmentVerdictResponse structure free(response); return 0; } -// Register functions in Lua static const struct luaL_Reg nano_attachment_lib[] = { {"init_nano_attachment", lua_init_nano_attachment}, {"get_web_response_type", lua_get_web_response_type}, @@ -558,7 +532,6 @@ static const struct luaL_Reg nano_attachment_lib[] = { {NULL, NULL} }; -// Load library int luaopen_lua_attachment_wrapper(lua_State *L) { luaL_newlib(L, nano_attachment_lib); return 1; 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 85cb48c..93dad16 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 @@ -4,16 +4,16 @@ local kong = kong local nano = {} nano.session_counter = 0 -nano.attachments = {} -- Store attachments per worker -nano.num_workers = ngx.worker.count() or 1 -- Detect number of workers +nano.attachments = {} +nano.num_workers = ngx.worker.count() or 1 nano.allocated_strings = {} nano.allocate_headers = {} -nano.allocated_metadata = {} -- Track metadata allocations -nano.allocated_responses = {} -- Track response allocations +nano.allocated_metadata = {} +nano.allocated_responses = {} nano.AttachmentVerdict = { INSPECT = 0, ACCEPT = 1, - DROP = 2, -- Matches `ATTACHMENT_VERDICT_DROP` + DROP = 2, INJECT = 3 } nano.HttpChunkType = { @@ -63,8 +63,8 @@ typedef struct __attribute__((__packed__)) HttpInjectData { } HttpInjectData; typedef struct NanoHttpModificationList { - struct NanoHttpModificationList *next; ///< Next node. - HttpInjectData modification; ///< Modification data. + struct NanoHttpModificationList *next; + HttpInjectData modification; char *modification_buffer; } NanoHttpModificationList; ]] @@ -79,7 +79,6 @@ local NanoHttpModificationListPtr = ffi.typeof("NanoHttpModificationList*") function nano.generate_session_id() nano.session_counter = nano.session_counter + 1 local worker_id = ngx.worker.id() - -- Compose session_id as "", e.g. "20001" return tonumber(string.format("%d%05d", worker_id, nano.session_counter)) end @@ -96,7 +95,6 @@ function nano.handle_custom_response(session_data, response) if response_type == nano.WebResponseType.RESPONSE_CODE_ONLY then local code = nano_attachment.get_response_code(response) - -- Validate HTTP status code if not code or code < 100 or code > 599 then kong.log.warn("Invalid response code received: ", code, " - using 403 instead") code = 403 @@ -115,8 +113,7 @@ function nano.handle_custom_response(session_data, response) kong.log.err("Failed to retrieve custom block page for session ", session_data) return kong.response.exit(500, { message = "Internal Server Error" }) end - local code = nano_attachment.get_response_code(response) -- Get the intended status code - -- Validate HTTP status code + 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 @@ -128,40 +125,36 @@ end --- Allocates memory (must be freed later) function nano.create_nano_str_alloc(str) if not str then return nil end local nano_str = nano_attachment.createNanoStrAlloc(str) - table.insert(nano.allocated_strings, nano_str) -- Track allocation + table.insert(nano.allocated_strings, nano_str) return nano_str end --- Free nano_str_t to prevent memory leaks function nano.free_nano_str(nano_str) if nano_str then nano_attachment.freeNanoStr(nano_str) end end --- Free all allocated nano_str_t to prevent memory leaks function nano.free_all_nano_str() for _, nano_str in ipairs(nano.allocated_strings) do - nano_attachment.freeNanoStr(nano_str) -- Free memory in C + nano_attachment.freeNanoStr(nano_str) end - nano.allocated_strings = {} -- Reset the list + nano.allocated_strings = {} end function nano.free_http_headers(header_data) for _, nano_header in ipairs(nano.allocate_headers) do - nano_attachment.freeHttpHeaders(nano_header) -- Free memory in C + nano_attachment.freeHttpHeaders(nano_header) end - nano.allocate_headers = {} -- Reset the list + nano.allocate_headers = {} end --- Free all allocated metadata function nano.free_all_metadata() for _, metadata in ipairs(nano.allocated_metadata) do nano_attachment.free_http_metadata(metadata) @@ -169,7 +162,6 @@ function nano.free_all_metadata() nano.allocated_metadata = {} end --- Free all allocated responses function nano.free_all_responses() for _, response in ipairs(nano.allocated_responses) do nano_attachment.free_verdict_response(response) @@ -177,7 +169,6 @@ function nano.free_all_responses() nano.allocated_responses = {} end --- Free all allocations (call this on cleanup) function nano.cleanup_all() nano.free_all_nano_str() nano.free_all_metadata() @@ -185,7 +176,6 @@ function nano.cleanup_all() nano.free_http_headers() end --- Initialize worker attachment function nano.init_attachment() local worker_id = ngx.worker.id() local attachment, err @@ -194,6 +184,7 @@ function nano.init_attachment() for attempt = 1, retries do attachment, err = nano_attachment.init_nano_attachment(worker_id, nano.num_workers) if attachment then + kong.log.info("Worker ", worker_id, " successfully initialized attachment on attempt ", attempt) break end @@ -205,10 +196,10 @@ function nano.init_attachment() else nano.attachments[worker_id] = attachment kong.log.info("Worker ", worker_id, " successfully initialized nano_attachment.") + return true end end --- Initialize a session for a given request function nano.init_session(session_id) local worker_id = ngx.worker.id() local attachment = nano.attachments[worker_id] @@ -233,7 +224,6 @@ function nano.init_session(session_id) return session_data end --- Check if session is finalized function nano.is_session_finalized(session_data) local worker_id = ngx.worker.id() local attachment = nano.attachments[worker_id] @@ -246,7 +236,6 @@ function nano.is_session_finalized(session_data) return nano_attachment.is_session_finalized(attachment, session_data) end --- Extract metadata for request function nano.handle_start_transaction() local stream_info = kong.request @@ -274,20 +263,15 @@ function nano.handle_start_transaction() return metadata end --- Handle request headers and convert them to nano_str_t function nano.handleHeaders(headers) - local envoy_headers_prefix = "x-envoy" - - -- Allocate memory for headers in C local header_data = nano_attachment.allocHttpHeaders() - table.insert(nano.allocate_headers, header_data) -- Track allocation + table.insert(nano.allocate_headers, header_data) local index = 0 for key, value in pairs(headers) do if index > 10000 then break end - -- Filter out unwanted headers - if key:find("^" .. envoy_headers_prefix) or key == "x-request-id" or + if key == "x-request-id" or key == ":method" or key == ":path" or key == ":scheme" or key == "x-forwarded-proto" then goto continue @@ -303,13 +287,11 @@ function nano.handleHeaders(headers) ::continue:: end - -- Store the count nano_attachment.setHeaderCount(header_data, index) return header_data end --- Send data to NanoAttachment 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] @@ -319,12 +301,11 @@ function nano.send_data(session_id, session_data, meta_data, header_data, contai return nano.AttachmentVerdict.INSPECT end - contains_body = tonumber(contains_body) or 0 -- Ensure it's a number - contains_body = (contains_body > 0) and 1 or 0 -- Force strict 0 or 1 + contains_body = tonumber(contains_body) or 0 + contains_body = (contains_body > 0) and 1 or 0 local verdict, response = nano_attachment.send_data(attachment, session_id, session_data, chunk_type, meta_data, header_data, contains_body) - -- Track response for cleanup if response then table.insert(nano.allocated_responses, response) end @@ -341,10 +322,8 @@ function nano.send_body(session_id, session_data, body_chunk, chunk_type) return nano.AttachmentVerdict.INSPECT end - -- Send the body chunk directly as a string local verdict, response, modifications = nano_attachment.send_body(attachment, session_id, session_data, body_chunk, chunk_type) - -- Track response for cleanup if response then table.insert(nano.allocated_responses, response) end @@ -352,7 +331,6 @@ function nano.send_body(session_id, session_data, body_chunk, chunk_type) return verdict, response, modifications end --- Function to inject content into a string at a specific position function nano.inject_at_position(buffer, injection, pos) if pos < 0 or pos > #buffer then kong.log.err("Invalid injection position: ", pos, ", buffer length: ", #buffer) @@ -361,12 +339,10 @@ function nano.inject_at_position(buffer, injection, pos) return buffer:sub(1, pos) .. injection .. buffer:sub(pos + 1) end --- Function to handle body modifications function nano.handle_body_modifications(body, modifications, body_buffer_chunk) if modifications == nil then return body end - -- cast the userdata to a pointer local curr_modification = ffi.cast(NanoHttpModificationListPtr, modifications) while curr_modification ~= nil do @@ -385,13 +361,11 @@ function nano.handle_body_modifications(body, modifications, body_buffer_chunk) return body end --- Add a new function for handling response bodies function nano.send_response_body(session_id, session_data, body_chunk) local verdict, response, modifications = nano.send_body(session_id, session_data, body_chunk, nano.HttpChunkType.HTTP_RESPONSE_BODY) return verdict, response, modifications end --- Finalize session cleanup function nano.fini_session(session_data) local worker_id = ngx.worker.id() local attachment = nano.attachments[worker_id] @@ -406,7 +380,6 @@ function nano.fini_session(session_data) return true end --- Send response headers for inspection 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] @@ -425,7 +398,6 @@ function nano.send_response_headers(session_id, session_data, headers, status_co content_length ) - -- Track response for cleanup if response then table.insert(nano.allocated_responses, response) end @@ -433,7 +405,6 @@ function nano.send_response_headers(session_id, session_data, headers, status_co return verdict, response end --- Function to handle header modifications function nano.handle_header_modifications(headers, modifications) if not modifications then return headers @@ -469,7 +440,7 @@ function nano.handle_header_modifications(headers, modifications) kong.log.debug("Injecting into header: ", header_name) modified_headers[header_name] = nano.inject_at_position(header_value, value, mod.injection_pos) end - elseif type == 2 then -- REPLACE + elseif type == 2 then kong.log.debug("Replacing header: ", key) modified_headers[key] = value end @@ -480,7 +451,6 @@ function nano.handle_header_modifications(headers, modifications) return modified_headers end --- End inspection for a session function nano.end_inspection(session_id, session_data, chunk_type) local worker_id = ngx.worker.id() local attachment = nano.attachments[worker_id] @@ -497,7 +467,6 @@ function nano.end_inspection(session_id, session_data, chunk_type) local verdict, response = nano_attachment.end_inspection(attachment, session_id, session_data, chunk_type) - -- Track response for cleanup if allocated if response then table.insert(nano.allocated_responses, response) end @@ -505,5 +474,4 @@ function nano.end_inspection(session_id, session_data, chunk_type) return verdict, response end --- Helper function to ensure attachment is available return nano