From 24f7cd1ea2ceb67363f64f9e4235409d6552a732 Mon Sep 17 00:00:00 2001 From: "xudong.wang" Date: Thu, 30 Jul 2026 16:37:21 +0800 Subject: [PATCH 1/3] feat(chaitin-waf): report responses to the WAF detection service lua-resty-t1k 1.2.0 can report the response back to the SafeLine WAF detection service in addition to the request. Bump the dependency and expose it through three new `config` options: - `log_resp` enables response reporting, off by default so existing configurations keep their current behavior - `resp_body_size` caps how much of the response body is buffered and reported, in KB, defaulting to 4 - `extra_ignored_content_types` skips further response content types on top of the built-in ignored list Wire up the `body_filter` and `log` phases to the corresponding library entry points. The report is sent from an ngx.timer during the log phase, so it does not delay the response, and the detection result is only visible in the SafeLine console: a response is never blocked or modified based on it. `get_conf` applied the metadata and route level config blocks with two copies of the same field-by-field assignment, which three more options would have made harder to keep in sync. Fold both into a local helper, keeping the existing precedence of route level config over metadata. Co-Authored-By: Claude Opus 5 --- apisix-master-0.rockspec | 2 +- apisix/plugins/chaitin-waf.lua | 80 ++++++++++++++++++++------- docs/en/latest/plugins/chaitin-waf.md | 16 ++++++ docs/zh/latest/plugins/chaitin-waf.md | 16 ++++++ 4 files changed, 94 insertions(+), 20 deletions(-) diff --git a/apisix-master-0.rockspec b/apisix-master-0.rockspec index 2b5b290e4d04..35c74afc403e 100644 --- a/apisix-master-0.rockspec +++ b/apisix-master-0.rockspec @@ -80,7 +80,7 @@ dependencies = { "xml2lua = 1.6-2", "lua-resty-mediador = 0.1.2-1", "lua-resty-ldap = 0.3.1-0", - "lua-resty-t1k = 1.1.6-0", + "lua-resty-t1k = 1.2.0-0", "brotli-ffi = 0.3-1", "lua-ffi-zlib = 0.6-0", "jsonpath = 1.0-1", diff --git a/apisix/plugins/chaitin-waf.lua b/apisix/plugins/chaitin-waf.lua index ed364bb9ab42..17b5433e7d47 100644 --- a/apisix/plugins/chaitin-waf.lua +++ b/apisix/plugins/chaitin-waf.lua @@ -88,6 +88,16 @@ local plugin_schema = { }, real_client_ip = { type = "boolean" + }, + log_resp = { + type = "boolean" + }, + resp_body_size = { + type = "integer", + minimum = 0 + }, + extra_ignored_content_types = { + type = "string" } }, }, @@ -152,6 +162,23 @@ local metadata_schema = { real_client_ip = { type = "boolean", default = true + }, + -- report the response to the WAF detection service + log_resp = { + type = "boolean", + default = false + }, + -- amount of the response body to report, in KB, + -- 0 disables body buffering + resp_body_size = { + type = "integer", + minimum = 0, + default = 4 + }, + -- extra response content types (comma separated) to skip + -- on top of the built-in ignored list + extra_ignored_content_types = { + type = "string" } }, default = {}, @@ -273,30 +300,35 @@ local function get_conf(conf, metadata) real_client_ip = true, } - if metadata.config then - t.connect_timeout = metadata.config.connect_timeout - t.send_timeout = metadata.config.send_timeout - t.read_timeout = metadata.config.read_timeout - t.req_body_size = metadata.config.req_body_size - t.keepalive_size = metadata.config.keepalive_size - t.keepalive_timeout = metadata.config.keepalive_timeout - if metadata.config.real_client_ip ~= nil then - t.real_client_ip = metadata.config.real_client_ip + local function apply(config) + if not config then + return end - end - if conf.config then - t.connect_timeout = conf.config.connect_timeout - t.send_timeout = conf.config.send_timeout - t.read_timeout = conf.config.read_timeout - t.req_body_size = conf.config.req_body_size - t.keepalive_size = conf.config.keepalive_size - t.keepalive_timeout = conf.config.keepalive_timeout - if conf.config.real_client_ip ~= nil then - t.real_client_ip = conf.config.real_client_ip + t.connect_timeout = config.connect_timeout + t.send_timeout = config.send_timeout + t.read_timeout = config.read_timeout + t.req_body_size = config.req_body_size + t.keepalive_size = config.keepalive_size + t.keepalive_timeout = config.keepalive_timeout + if config.real_client_ip ~= nil then + t.real_client_ip = config.real_client_ip + end + if config.log_resp ~= nil then + t.log_resp = config.log_resp + end + if config.resp_body_size ~= nil then + t.resp_body_size = config.resp_body_size + end + if config.extra_ignored_content_types ~= nil then + t.extra_ignored_content_types = config.extra_ignored_content_types end end + -- metadata config first, then route/service level config overrides it + apply(metadata.config) + apply(conf.config) + t.mode = conf.mode or metadata.mode or t.mode return t @@ -421,4 +453,14 @@ function _M.header_filter(conf, ctx) end +function _M.body_filter(conf, ctx) + t1k.do_body_filter() +end + + +function _M.log(conf, ctx) + t1k.do_log() +end + + return _M diff --git a/docs/en/latest/plugins/chaitin-waf.md b/docs/en/latest/plugins/chaitin-waf.md index dd8ef5a6a026..39f0577baa73 100644 --- a/docs/en/latest/plugins/chaitin-waf.md +++ b/docs/en/latest/plugins/chaitin-waf.md @@ -65,6 +65,9 @@ The Plugin can add the following response headers, depending on the configuratio | config.keepalive_size | integer | false | 256 | | The maximum number of idle connections to the WAF detection service that can be maintained concurrently. | | config.keepalive_timeout | integer | false | 60000 | | The idle connection timeout for the WAF service, in milliseconds. | | config.real_client_ip | boolean | false | true | | If true, the Plugin sends APISIX's resolved client IP to the WAF service (the same value used elsewhere in APISIX, derived from the connection and `apisix.trusted_addresses`). If false, the Plugin sends the IP of the peer directly connected to APISIX. | +| config.log_resp | boolean | false | false | | If true, the Plugin also reports the response to the WAF service. See [Response Logging](#response-logging). | +| config.resp_body_size | integer | false | 4 | >= 0 | The amount of the response body to report, in KB. Set to `0` to report only the status line and response headers. Effective only when `config.log_resp` is `true`. | +| config.extra_ignored_content_types | string | false | | | A comma separated list of response `Content-Type` values to skip, in addition to the built-in ignored list. Effective only when `config.log_resp` is `true`. | ## Plugin Metadata @@ -82,6 +85,19 @@ The Plugin can add the following response headers, depending on the configuratio | config.keepalive_size | integer | False | 256 | | The maximum number of idle connections to the WAF detection service that can be maintained concurrently. | | config.keepalive_timeout | integer | False | 60000 | | The idle connection timeout for the WAF service, in milliseconds. | | config.real_client_ip | boolean | False | true | | If true, the Plugin sends APISIX's resolved client IP to the WAF service (the same value used elsewhere in APISIX, derived from the connection and `apisix.trusted_addresses`). If false, the Plugin sends the IP of the peer directly connected to APISIX. | +| config.log_resp | boolean | False | false | | If true, the Plugin also reports the response to the WAF service. See [Response Logging](#response-logging). | +| config.resp_body_size | integer | False | 4 | >= 0 | The amount of the response body to report, in KB. Set to `0` to report only the status line and response headers. Effective only when `config.log_resp` is `true`. | +| config.extra_ignored_content_types | string | False | | | A comma separated list of response `Content-Type` values to skip, in addition to the built-in ignored list. Effective only when `config.log_resp` is `true`. | + +## Response Logging + +By default the Plugin only reports the request to the WAF service. Setting `config.log_resp` to `true` makes it report the response as well: the status line, the response headers, and up to `config.resp_body_size` KB of the response body. + +The report is sent from an `ngx.timer` during the log phase, after the response has been handed back to the client, so it does not block the response or add latency to it. Detection results are visible in the SafeLine WAF console rather than acted on by APISIX, meaning a response is never blocked or modified based on this report. + +Responses are not reported when the request itself was already blocked, or when the response `Content-Type` matches an ignored type. Audio, video, font, image and other binary media types are ignored out of the box; use `config.extra_ignored_content_types` to skip more, for example `text/csv,application/pdf`. + +Note that buffering the response body costs memory per in-flight request, so raise `config.resp_body_size` with care on routes serving large responses. ## Examples diff --git a/docs/zh/latest/plugins/chaitin-waf.md b/docs/zh/latest/plugins/chaitin-waf.md index 73a29b60d403..2c7c17bde595 100644 --- a/docs/zh/latest/plugins/chaitin-waf.md +++ b/docs/zh/latest/plugins/chaitin-waf.md @@ -65,6 +65,9 @@ description: chaitin-waf 插件与长亭雷池 WAF 集成,以检测和阻止 | config.keepalive_size | integer | 否 | 256 | | 可同时维持的与 WAF 检测服务的空闲连接数上限。 | | config.keepalive_timeout | integer | 否 | 60000 | | 与 WAF 服务的空闲连接超时时间,单位为毫秒。 | | config.real_client_ip | boolean | 否 | true | | 若为 true,则插件将 APISIX 解析得到的客户端 IP(与 APISIX 其他地方使用的相同,由连接信息以及 `apisix.trusted_addresses` 共同决定)发送给 WAF 服务。若为 false,则插件发送与 APISIX 直接建立连接的对端 IP。 | +| config.log_resp | boolean | 否 | false | | 若为 true,则插件同时将响应上报给 WAF 服务。参见 [响应上报](#响应上报)。 | +| config.resp_body_size | integer | 否 | 4 | >= 0 | 上报的响应体大小,单位为 KB。设置为 `0` 表示仅上报状态行与响应头。仅当 `config.log_resp` 为 true 时生效。 | +| config.extra_ignored_content_types | string | 否 | | | 以逗号分隔的响应 `Content-Type` 列表,在内置忽略列表之外额外跳过这些类型。仅当 `config.log_resp` 为 true 时生效。 | ## 插件元数据 @@ -82,6 +85,19 @@ description: chaitin-waf 插件与长亭雷池 WAF 集成,以检测和阻止 | config.keepalive_size | integer | 否 | 256 | | 可同时维持的与 WAF 检测服务的空闲连接数上限。 | | config.keepalive_timeout | integer | 否 | 60000 | | 与 WAF 服务的空闲连接超时时间,单位为毫秒。 | | config.real_client_ip | boolean | 否 | true | | 若为 true,则插件将 APISIX 解析得到的客户端 IP(与 APISIX 其他地方使用的相同,由连接信息以及 `apisix.trusted_addresses` 共同决定)发送给 WAF 服务。若为 false,则插件发送与 APISIX 直接建立连接的对端 IP。 | +| config.log_resp | boolean | 否 | false | | 若为 true,则插件同时将响应上报给 WAF 服务。参见 [响应上报](#响应上报)。 | +| config.resp_body_size | integer | 否 | 4 | >= 0 | 上报的响应体大小,单位为 KB。设置为 `0` 表示仅上报状态行与响应头。仅当 `config.log_resp` 为 true 时生效。 | +| config.extra_ignored_content_types | string | 否 | | | 以逗号分隔的响应 `Content-Type` 列表,在内置忽略列表之外额外跳过这些类型。仅当 `config.log_resp` 为 true 时生效。 | + +## 响应上报 + +插件默认只将请求上报给 WAF 服务。将 `config.log_resp` 设置为 true 后,插件会同时上报响应:状态行、响应头,以及最多 `config.resp_body_size` KB 的响应体。 + +上报在 log 阶段通过 `ngx.timer` 发出,此时响应已经返回给客户端,因此不会阻塞响应、也不会增加响应延迟。检测结果在雷池 WAF 控制台查看,APISIX 不会依据该上报结果拦截或修改响应。 + +以下情况不会上报响应:请求本身已被拦截,或响应 `Content-Type` 属于被忽略的类型。音频、视频、字体、图片等二进制媒体类型默认被忽略,可通过 `config.extra_ignored_content_types` 追加,例如 `text/csv,application/pdf`。 + +请注意,缓冲响应体会为每个进行中的请求占用内存,在返回大响应的路由上调高 `config.resp_body_size` 需谨慎。 ## 示例 From 19296b506a03501db0237fa6d8d587e44ccfa99f Mon Sep 17 00:00:00 2001 From: "xudong.wang" Date: Thu, 30 Jul 2026 16:44:23 +0800 Subject: [PATCH 2/3] test(chaitin-waf): cover the response logging options Add schema validation cases for the `resp_body_size` minimum and the `extra_ignored_content_types` type, plus round-trip cases asserting that a route configured with response logging still serves its response unchanged, and that the default configuration behaves the same way. Co-Authored-By: Claude Opus 5 --- t/plugin/chaitin-waf.t | 181 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) diff --git a/t/plugin/chaitin-waf.t b/t/plugin/chaitin-waf.t index df7f7ed366f5..77d934a8c4d6 100644 --- a/t/plugin/chaitin-waf.t +++ b/t/plugin/chaitin-waf.t @@ -519,3 +519,184 @@ trigger: true chaitin-waf client_ip: 127.0.0.1 --- no_error_log chaitin-waf client_ip: 192.0.2.10 + + + +=== TEST 16: wrong schema: negative resp_body_size +--- config + location /do { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/plugin_metadata/chaitin-waf', + ngx.HTTP_PUT, + [[{ + "nodes": [ + { + "host": "127.0.0.1", + "port": 8088 + } + ], + "config": { + "log_resp": true, + "resp_body_size": -1 + } + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.print(body) + } + } +--- error_code: 400 +--- response_body +{"error_msg":"invalid configuration: property \"config\" validation failed: property \"resp_body_size\" validation failed: expected -1 to be at least 0"} + + + +=== TEST 17: wrong schema: extra_ignored_content_types wrong type +--- config + location /do { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/plugin_metadata/chaitin-waf', + ngx.HTTP_PUT, + [[{ + "nodes": [ + { + "host": "127.0.0.1", + "port": 8088 + } + ], + "config": { + "log_resp": true, + "extra_ignored_content_types": ["text/csv"] + } + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.print(body) + } + } +--- error_code: 400 +--- response_body +{"error_msg":"invalid configuration: property \"config\" validation failed: property \"extra_ignored_content_types\" validation failed: wrong type: expected string, got table"} + + + +=== TEST 18: response logging enabled prepare +--- config + location /do { + content_by_lua_block { + local t = require("lib.test_admin").test + + local code, body = t('/apisix/admin/plugin_metadata/chaitin-waf', + ngx.HTTP_PUT, + [[{ + "nodes": [ + { + "host": "127.0.0.1", + "port": 8088 + } + ] + }]] + ) + if code >= 300 then + ngx.status = code + return ngx.print(body) + end + + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "methods": ["GET"], + "plugins": { + "chaitin-waf": { + "config": { + "log_resp": true, + "resp_body_size": 1, + "extra_ignored_content_types": "text/csv" + } + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/*" + }]] + ) + if code >= 300 then + ngx.status = code + return ngx.print(body) + end + ngx.say("passed") + } + } +--- response_body +passed + + + +=== TEST 19: response logging does not affect the response +--- request +GET /hello +--- error_code: 200 +--- response_body +hello world +--- response_headers +X-APISIX-CHAITIN-WAF: yes +X-APISIX-CHAITIN-WAF-ACTION: pass +X-APISIX-CHAITIN-WAF-STATUS: 200 + + + +=== TEST 20: response logging off by default prepare +--- config + location /do { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "methods": ["GET"], + "plugins": { + "chaitin-waf": {} + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/*" + }]] + ) + if code >= 300 then + ngx.status = code + return ngx.print(body) + end + ngx.say("passed") + } + } +--- response_body +passed + + + +=== TEST 21: response logging off by default +--- request +GET /hello +--- error_code: 200 +--- response_body +hello world +--- response_headers +X-APISIX-CHAITIN-WAF: yes +X-APISIX-CHAITIN-WAF-ACTION: pass +X-APISIX-CHAITIN-WAF-STATUS: 200 From 73b9035a0cf5d10d3dc2909aa8b2f56c4e19df8d Mon Sep 17 00:00:00 2001 From: "xudong.wang" Date: Wed, 5 Aug 2026 19:07:04 +0800 Subject: [PATCH 3/3] test(chaitin-waf): assert the response is reported to the WAF service The previous tests only checked that the plugin accepted the new options and that the response reached the client unchanged. They never showed that anything was reported, so response logging could have been broken outright and they would still have passed. Assert instead on what lua-resty-t1k logs while it reports: that a report was sent, how much of the response body it buffered, and that an ignored content type or a disabled log_resp skips the report entirely. The truncation case is the sharpest one, since the client receives 4096 bytes while only 1024 are buffered for the report. Reporting happens in a timer after the response has been handed back, so a failure to deliver it cannot surface in the response. Cover that too, now that the library logs it. pass_keepalive() is needed because the library pools the connection and sends the response report over the same one, which pass() closes as soon as it has answered the request report. Bump lua-resty-t1k to 1.2.1-0 for the log lines these tests match. --- apisix-master-0.rockspec | 2 +- t/lib/chaitin_waf_server.lua | 27 ++ t/plugin/chaitin-waf-log-resp.t | 471 ++++++++++++++++++++++++++++++++ t/plugin/chaitin-waf.t | 46 ---- 4 files changed, 499 insertions(+), 47 deletions(-) create mode 100644 t/plugin/chaitin-waf-log-resp.t diff --git a/apisix-master-0.rockspec b/apisix-master-0.rockspec index 35c74afc403e..80109d93ca81 100644 --- a/apisix-master-0.rockspec +++ b/apisix-master-0.rockspec @@ -80,7 +80,7 @@ dependencies = { "xml2lua = 1.6-2", "lua-resty-mediador = 0.1.2-1", "lua-resty-ldap = 0.3.1-0", - "lua-resty-t1k = 1.2.0-0", + "lua-resty-t1k = 1.2.1-0", "brotli-ffi = 0.3-1", "lua-ffi-zlib = 0.6-0", "jsonpath = 1.0-1", diff --git a/t/lib/chaitin_waf_server.lua b/t/lib/chaitin_waf_server.lua index 4130bd019edb..7e2fb8623872 100644 --- a/t/lib/chaitin_waf_server.lua +++ b/t/lib/chaitin_waf_server.lua @@ -36,6 +36,33 @@ function _M.pass() ngx.exit(200) end +-- Like pass(), but keeps answering until the client goes away. The client pools +-- the connection and sends the response report over the same one, so a handler +-- that answers once and exits would make that second report fail. +function _M.pass_keepalive() + local sock = get_socket() + + while true do + sock:send({ string.char(65), string.char(1), string.char(0), + string.char(0), string.char(0) }) + sock:send(".") + sock:send({ string.char(165), string.char(77), string.char(0), + string.char(0), string.char(0) }) + sock:send("{\"event_id\":\"1e902e84bf5a4ead8f7760a0fe2c7719\"," .. + "\"request_hit_whitelist\":false}") + + -- block until the next report arrives, so the connection stays usable; + -- reading one header byte is enough to tell a new report from a close + local data, err = sock:receive(1) + if not data then + ngx.log(ngx.INFO, "no further t1k message: ", tostring(err)) + break + end + end + + ngx.exit(200) +end + function _M.reject() local sock = get_socket() sock:send({ string.char(65), string.char(1), string.char(0), string.char(0), string.char(0) }) diff --git a/t/plugin/chaitin-waf-log-resp.t b/t/plugin/chaitin-waf-log-resp.t new file mode 100644 index 000000000000..1ae0630a0e27 --- /dev/null +++ b/t/plugin/chaitin-waf-log-resp.t @@ -0,0 +1,471 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +use t::APISIX 'no_plan'; + +repeat_each(1); +no_long_string(); +no_root_location(); + +add_block_preprocessor(sub { + my ($block) = @_; + + # pass_keepalive() keeps the connection usable for the response report, + # which the client sends over the same pooled connection + my $handler = $block->waf_server_handler // "pass_keepalive"; + my $stream_default_server = <<_EOC_; + server { + listen 8088; + content_by_lua_block { + require("lib.chaitin_waf_server").$handler() + } + } +_EOC_ + + $block->set_value("extra_stream_config", $stream_default_server); + $block->set_value("stream_conf_enable", 1); + + # setup default conf.yaml + my $extra_yaml_config = $block->extra_yaml_config // <<_EOC_; +apisix: + stream_proxy: # TCP/UDP L4 proxy + only: true # Enable L4 proxy only without L7 proxy. + tcp: + - addr: 9100 # Set the TCP proxy listening ports. + tls: true + - addr: "127.0.0.1:9101" + udp: # Set the UDP proxy listening ports. + - 9200 + - "127.0.0.1:9201" +plugins: + - chaitin-waf +_EOC_ + + $block->set_value("extra_yaml_config", $extra_yaml_config); + + if (!$block->request) { + # use /do instead of /t because stream server will inject a default /t location + $block->set_value("request", "GET /do"); + } + + if ((!defined $block->error_log) && (!defined $block->no_error_log)) { + $block->set_value("no_error_log", "[error]"); + } +}); + +run_tests; + +__DATA__ + +=== TEST 1: configure the WAF service +--- config + location /do { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/plugin_metadata/chaitin-waf', + ngx.HTTP_PUT, + [[{ + "nodes": [ + { + "host": "127.0.0.1", + "port": 8088 + } + ] + }]] + ) + if code >= 300 then + ngx.status = code + return ngx.print(body) + end + ngx.say("passed") + } + } +--- response_body +passed + + + +=== TEST 2: the response is reported to the WAF service +--- config + location /do { + content_by_lua_block { + local t = require("lib.test_admin").test + local http = require("resty.http") + + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "methods": ["GET"], + "plugins": { + "chaitin-waf": { + "config": { + "log_resp": true + } + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/*" + }]] + ) + if code >= 300 then + ngx.status = code + return ngx.print(body) + end + + local httpc = http.new() + local res, err = httpc:request_uri("http://127.0.0.1:1984/hello") + if not res then + return ngx.say("request failed: ", err) + end + ngx.say("upstream response: ", res.body) + ngx.say("waf header: ", res.headers["X-APISIX-CHAITIN-WAF"]) + + -- give the timer that reports the response a chance to run + ngx.sleep(0.3) + } + } +--- response_body +upstream response: hello world +waf header: yes +--- error_log +lua-resty-t1k: reported response +--- log_level: debug + + + +=== TEST 3: buffer the whole response body when it fits +--- config + location /do { + content_by_lua_block { + local t = require("lib.test_admin").test + local http = require("resty.http") + + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "methods": ["POST"], + "plugins": { + "chaitin-waf": { + "config": { + "log_resp": true + } + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/*" + }]] + ) + if code >= 300 then + ngx.status = code + return ngx.print(body) + end + + -- /echo answers with the request body, so this yields a 100 byte + -- response, well under the 4 KB default + local httpc = http.new() + local res, err = httpc:request_uri("http://127.0.0.1:1984/echo", { + method = "POST", + body = string.rep("a", 100), + }) + if not res then + return ngx.say("request failed: ", err) + end + ngx.say("client body size: ", #res.body) + + ngx.sleep(0.3) + } + } +--- response_body +client body size: 100 +--- error_log +lua-resty-t1k: response body received completely, total size: 100 bytes, truncated size: 100 bytes +--- log_level: debug + + + +=== TEST 4: truncate the buffered response body to resp_body_size +--- config + location /do { + content_by_lua_block { + local t = require("lib.test_admin").test + local http = require("resty.http") + + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "methods": ["POST"], + "plugins": { + "chaitin-waf": { + "config": { + "log_resp": true, + "resp_body_size": 1 + } + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/*" + }]] + ) + if code >= 300 then + ngx.status = code + return ngx.print(body) + end + + local httpc = http.new() + local res, err = httpc:request_uri("http://127.0.0.1:1984/echo", { + method = "POST", + body = string.rep("a", 4096), + }) + if not res then + return ngx.say("request failed: ", err) + end + -- the client still receives the whole response + ngx.say("client body size: ", #res.body) + + ngx.sleep(0.3) + } + } +--- response_body +client body size: 4096 +--- error_log +lua-resty-t1k: response body received completely, total size: 4096 bytes, truncated size: 1024 bytes +--- log_level: debug + + + +=== TEST 5: buffer no response body when resp_body_size is zero +--- config + location /do { + content_by_lua_block { + local t = require("lib.test_admin").test + local http = require("resty.http") + + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "methods": ["GET"], + "plugins": { + "chaitin-waf": { + "config": { + "log_resp": true, + "resp_body_size": 0 + } + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/*" + }]] + ) + if code >= 300 then + ngx.status = code + return ngx.print(body) + end + + local httpc = http.new() + local res, err = httpc:request_uri("http://127.0.0.1:1984/hello") + if not res then + return ngx.say("request failed: ", err) + end + ngx.say("upstream response: ", res.body) + + ngx.sleep(0.3) + } + } +--- response_body +upstream response: hello world +--- error_log +lua-resty-t1k: skip response body buffering for non-positive limit: 0 +--- log_level: debug + + + +=== TEST 6: skip an extra ignored content type +--- config + location /do { + content_by_lua_block { + local t = require("lib.test_admin").test + local http = require("resty.http") + + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "methods": ["POST"], + "plugins": { + "chaitin-waf": { + "config": { + "log_resp": true, + "extra_ignored_content_types": "text/csv" + } + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/*" + }]] + ) + if code >= 300 then + ngx.status = code + return ngx.print(body) + end + + -- /echo reflects a resp-* request header as a response header, so + -- this makes the upstream answer with an ignored content type + local httpc = http.new() + local res, err = httpc:request_uri("http://127.0.0.1:1984/echo", { + method = "POST", + body = "a,b,c", + headers = { ["resp-content-type"] = "text/csv" }, + }) + if not res then + return ngx.say("request failed: ", err) + end + ngx.say("content type: ", res.headers["Content-Type"]) + + ngx.sleep(0.3) + } + } +--- response_body +content type: text/csv +--- error_log +lua-resty-t1k: skip response logging +--- log_level: debug + + + +=== TEST 7: do not report the response when log_resp is off +--- config + location /do { + content_by_lua_block { + local t = require("lib.test_admin").test + local http = require("resty.http") + + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "methods": ["GET"], + "plugins": { + "chaitin-waf": {} + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/*" + }]] + ) + if code >= 300 then + ngx.status = code + return ngx.print(body) + end + + local httpc = http.new() + local res, err = httpc:request_uri("http://127.0.0.1:1984/hello") + if not res then + return ngx.say("request failed: ", err) + end + ngx.say("waf header: ", res.headers["X-APISIX-CHAITIN-WAF"]) + + ngx.sleep(0.3) + } + } +--- response_body +waf header: yes +--- error_log +lua-resty-t1k: skip response logging +--- log_level: debug + + + +=== TEST 8: a failure to report the response is logged +--- waf_server_handler: pass +--- config + location /do { + content_by_lua_block { + local t = require("lib.test_admin").test + local http = require("resty.http") + + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "methods": ["GET"], + "plugins": { + "chaitin-waf": { + "config": { + "log_resp": true + } + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/*" + }]] + ) + if code >= 300 then + ngx.status = code + return ngx.print(body) + end + + -- pass() answers the request report and then goes away, so the + -- response report finds the pooled connection unusable and fails. + -- Before the failure was logged, this was silent. + local httpc = http.new() + local res, err = httpc:request_uri("http://127.0.0.1:1984/hello") + if not res then + return ngx.say("request failed: ", err) + end + -- the response still reaches the client unharmed + ngx.say("upstream response: ", res.body) + ngx.say("waf header: ", res.headers["X-APISIX-CHAITIN-WAF"]) + + ngx.sleep(0.3) + } + } +--- response_body +upstream response: hello world +waf header: yes +--- error_log +lua-resty-t1k: failed to report response +--- log_level: error diff --git a/t/plugin/chaitin-waf.t b/t/plugin/chaitin-waf.t index 77d934a8c4d6..b1bdb021530f 100644 --- a/t/plugin/chaitin-waf.t +++ b/t/plugin/chaitin-waf.t @@ -654,49 +654,3 @@ hello world X-APISIX-CHAITIN-WAF: yes X-APISIX-CHAITIN-WAF-ACTION: pass X-APISIX-CHAITIN-WAF-STATUS: 200 - - - -=== TEST 20: response logging off by default prepare ---- config - location /do { - content_by_lua_block { - local t = require("lib.test_admin").test - local code, body = t('/apisix/admin/routes/1', - ngx.HTTP_PUT, - [[{ - "methods": ["GET"], - "plugins": { - "chaitin-waf": {} - }, - "upstream": { - "nodes": { - "127.0.0.1:1980": 1 - }, - "type": "roundrobin" - }, - "uri": "/*" - }]] - ) - if code >= 300 then - ngx.status = code - return ngx.print(body) - end - ngx.say("passed") - } - } ---- response_body -passed - - - -=== TEST 21: response logging off by default ---- request -GET /hello ---- error_code: 200 ---- response_body -hello world ---- response_headers -X-APISIX-CHAITIN-WAF: yes -X-APISIX-CHAITIN-WAF-ACTION: pass -X-APISIX-CHAITIN-WAF-STATUS: 200