This is an automated email from the ASF dual-hosted git repository.
Baoyuantop pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push:
new b31e828c6f feat(chaitin-waf): add allow_degradation option for
fail-open behavior (#13311)
b31e828c6f is described below
commit b31e828c6f6b588ae954bf69c8432c08fb73bb7e
Author: jlongWang <[email protected]>
AuthorDate: Wed Oct 28 10:44:45 2026 +0800
feat(chaitin-waf): add allow_degradation option for fail-open behavior
(#13311)
---
apisix/plugins/chaitin-waf.lua | 31 ++++
docs/en/latest/plugins/chaitin-waf.md | 4 +
docs/zh/latest/plugins/chaitin-waf.md | 4 +
t/plugin/chaitin-waf-degradation.t | 340 ++++++++++++++++++++++++++++++++++
t/plugin/chaitin-waf-timeout.t | 95 ++++++++++
5 files changed, 474 insertions(+)
diff --git a/apisix/plugins/chaitin-waf.lua b/apisix/plugins/chaitin-waf.lua
index b87b40e426..ee45ee5d8f 100644
--- a/apisix/plugins/chaitin-waf.lua
+++ b/apisix/plugins/chaitin-waf.lua
@@ -65,6 +65,10 @@ local plugin_schema = {
type = "boolean",
default = false
},
+ allow_degradation = {
+ -- Leave unset to inherit the plugin metadata value.
+ type = "boolean"
+ },
config = {
type = "object",
properties = {
@@ -111,6 +115,10 @@ local metadata_schema = {
type = "string",
enum = { "off", "monitor", "block" }
},
+ allow_degradation = {
+ type = "boolean",
+ default = false
+ },
nodes = {
type = "array",
items = {
@@ -343,9 +351,21 @@ local function do_access(conf, ctx)
local extra_headers = {}
local metadata = plugin.plugin_metadata(plugin_name)
+ local allow_degradation = conf.allow_degradation
+ if allow_degradation == nil then
+ allow_degradation = core.table.try_read_attr(metadata, "value",
"allow_degradation")
+ or false
+ end
+
if not core.table.try_read_attr(metadata, "value", "nodes") then
extra_headers[HEADER_CHAITIN_WAF] = "err"
extra_headers[HEADER_CHAITIN_WAF_ERROR] = "missing metadata"
+
+ if allow_degradation then
+ core.log.warn("chaitin-waf: missing metadata, degradation enabled,
passing request")
+ return nil, nil, extra_headers
+ end
+
return 500, nil, extra_headers
end
@@ -354,6 +374,11 @@ local function do_access(conf, ctx)
extra_headers[HEADER_CHAITIN_WAF] = "unhealthy"
extra_headers[HEADER_CHAITIN_WAF_ERROR] = tostring(err)
+ if allow_degradation then
+ core.log.warn("chaitin-waf: no healthy nodes, degradation enabled,
passing request")
+ return nil, nil, extra_headers
+ end
+
return 500, nil, extra_headers
end
@@ -402,6 +427,12 @@ local function do_access(conf, ctx)
end
extra_headers[HEADER_CHAITIN_WAF_ERROR] = tostring(err)
+ if allow_degradation then
+ core.log.warn("chaitin-waf: WAF error (", err_msg,
+ "), degradation enabled, passing request")
+ return nil, nil, extra_headers
+ end
+
if mode == "monitor" then
core.log.warn("chaitin-waf monitor mode: detected waf error - ",
err_msg)
return nil, nil, extra_headers
diff --git a/docs/en/latest/plugins/chaitin-waf.md
b/docs/en/latest/plugins/chaitin-waf.md
index 39f0577baa..5f71906379 100644
--- a/docs/en/latest/plugins/chaitin-waf.md
+++ b/docs/en/latest/plugins/chaitin-waf.md
@@ -57,6 +57,7 @@ The Plugin can add the following response headers, depending
on the configuratio
| match.vars | array[array] | false | |
| An array of one or more matching conditions in the form of
[lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list) to
conditionally execute the plugin. |
| append_waf_resp_header | boolean | false | true |
| If true, add response headers `X-APISIX-CHAITIN-WAF`,
`X-APISIX-CHAITIN-WAF-TIME`, `X-APISIX-CHAITIN-WAF-ACTION`, and
`X-APISIX-CHAITIN-WAF-STATUS`. |
| append_waf_debug_header | boolean | false | false |
| If true, add debugging headers `X-APISIX-CHAITIN-WAF-ERROR` and
`X-APISIX-CHAITIN-WAF-SERVER` to the response. Effective only when
`append_waf_resp_header` is `true`. |
+| allow_degradation | boolean | false | |
| If true, allow requests to continue when the WAF integration
fails. Overrides Plugin Metadata when explicitly set, including `false`. If
omitted, inherits Plugin Metadata and falls back to `false`. |
| config | object | false | |
| Chaitin WAF service configurations. These settings override the
corresponding metadata defaults when specified. |
| config.connect_timeout | integer | false | 1000 |
| The connection timeout to the WAF service, in milliseconds. |
| config.send_timeout | integer | false | 1000 |
| The sending timeout for transmitting data to the WAF service, in
milliseconds. |
@@ -69,6 +70,8 @@ The Plugin can add the following response headers, depending
on the configuratio
| 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`. |
+When `allow_degradation` is `true`, requests continue if Plugin Metadata or
`nodes` are missing, WAF node selection fails, or the WAF detection call fails
(including timeouts). Successful WAF verdicts still follow `mode`. Requests can
still wait for the configured WAF timeout before being allowed to continue.
+
## Plugin Metadata
| Name | Type | Required | Default | Valid values
| Description |
@@ -77,6 +80,7 @@ The Plugin can add the following response headers, depending
on the configuratio
| nodes.host | string | True | |
| Address of Chaitin WAF service. Supports IPv4, IPv6, Unix Socket, etc. |
| nodes.port | integer | False | 80 |
| Port of Chaitin WAF service. |
| mode | string | False | | block
| Mode to determine how the Plugin behaves for matched requests. In `off` mode,
WAF checks are skipped. In `monitor` mode, requests with potential threats are
logged but not blocked. In `block` mode, requests with threats are blocked as
determined by the WAF service. |
+| allow_degradation | boolean | False | false |
| Default value for `allow_degradation` when the Plugin configuration does not
set it. |
| config | object | False | |
| Chaitin WAF service configurations. |
| config.connect_timeout | integer | False | 1000 |
| The connection timeout to the WAF service, in milliseconds. |
| config.send_timeout | integer | False | 1000 |
| The sending timeout for transmitting data to the WAF service, in
milliseconds. |
diff --git a/docs/zh/latest/plugins/chaitin-waf.md
b/docs/zh/latest/plugins/chaitin-waf.md
index 2c7c17bde5..ef2c8323e5 100644
--- a/docs/zh/latest/plugins/chaitin-waf.md
+++ b/docs/zh/latest/plugins/chaitin-waf.md
@@ -57,6 +57,7 @@ description: chaitin-waf 插件与长亭雷池 WAF 集成,以检测和阻止
| match.vars | array[array] | 否 | |
| 一个或多个匹配条件数组,使用
[lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list)
表达式来有条件地执行插件。 |
| append_waf_resp_header | boolean | 否 | true |
| 若为 true,则在响应中添加
`X-APISIX-CHAITIN-WAF`、`X-APISIX-CHAITIN-WAF-TIME`、`X-APISIX-CHAITIN-WAF-ACTION`
和 `X-APISIX-CHAITIN-WAF-STATUS` 响应头。 |
| append_waf_debug_header | boolean | 否 | false |
| 若为 true,则在响应中添加调试用响应头 `X-APISIX-CHAITIN-WAF-ERROR` 和
`X-APISIX-CHAITIN-WAF-SERVER`。仅当 `append_waf_resp_header` 为 true 时生效。 |
+| allow_degradation | boolean | 否 | |
| 若为 true,则在 WAF 集成发生故障时允许请求继续处理。显式设置时覆盖插件元数据,包括显式设置为
`false`。未设置时继承插件元数据,两处均未设置时为 `false`。 |
| config | object | 否 | |
| 长亭 WAF 服务配置。这些配置在指定时会覆盖对应的元数据默认值。 |
| config.connect_timeout | integer | 否 | 1000 |
| 与 WAF 服务的连接超时时间,单位为毫秒。 |
| config.send_timeout | integer | 否 | 1000 |
| 向 WAF 服务发送数据的超时时间,单位为毫秒。 |
@@ -69,6 +70,8 @@ description: chaitin-waf 插件与长亭雷池 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 时生效。 |
+当 `allow_degradation` 为 `true` 时,缺少插件元数据或 `nodes`、WAF 节点选择失败、WAF
检测调用失败(包括超时)均会放行请求。WAF 正常返回的检测结果仍按 `mode` 处理。请求仍可能需要等待配置的 WAF 超时时间后才会放行。
+
## 插件元数据
| 名称 | 类型 | 必选项 | 默认值 | 有效值 | 描述 |
@@ -77,6 +80,7 @@ description: chaitin-waf 插件与长亭雷池 WAF 集成,以检测和阻止
| nodes.host | string | 是 | |
| 长亭雷池 WAF 服务的地址,支持 IPv4、IPv6、Unix Socket 等。 |
| nodes.port | integer | 否 | 80 |
| 长亭雷池 WAF 服务的端口。 |
| mode | string | 否 | | block
| 用于决定插件在匹配请求时的行为模式。在 `off` 模式下,跳过 WAF 检查。在 `monitor` 模式下,记录潜在威胁请求但不拦截。在
`block` 模式下,根据 WAF 服务的判断拦截存在威胁的请求。 |
+| allow_degradation | boolean | 否 | false |
| 插件配置未设置 `allow_degradation` 时使用的默认值。 |
| config | object | 否 | |
| 长亭雷池 WAF 服务配置。 |
| config.connect_timeout | integer | 否 | 1000 |
| 与 WAF 服务的连接超时时间,单位为毫秒。 |
| config.send_timeout | integer | 否 | 1000 |
| 向 WAF 服务发送数据的超时时间,单位为毫秒。 |
diff --git a/t/plugin/chaitin-waf-degradation.t
b/t/plugin/chaitin-waf-degradation.t
new file mode 100644
index 0000000000..2a1e199646
--- /dev/null
+++ b/t/plugin/chaitin-waf-degradation.t
@@ -0,0 +1,340 @@
+#
+# 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) = @_;
+
+ $block->set_value("extra_stream_config", <<_EOC_);
+ server {
+ listen 8088;
+ content_by_lua_block {
+ require("lib.chaitin_waf_server").reject()
+ }
+ }
+_EOC_
+ $block->set_value("stream_conf_enable", 1);
+ $block->set_value("extra_yaml_config", <<_EOC_);
+plugins:
+ - chaitin-waf
+_EOC_
+
+ if (!$block->request) {
+ $block->set_value("request", "GET /do");
+ }
+});
+
+run_tests;
+
+__DATA__
+
+=== TEST 1: configure routes and enable degradation in metadata
+--- config
+ location /do {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local cases = {
+ {name = "inherit", mode = "block"},
+ {name = "disabled", mode = "block", allow_degradation = false},
+ {name = "enabled", mode = "block", allow_degradation = true},
+ {name = "monitor", mode = "monitor", allow_degradation =
false},
+ }
+
+ for _, case in ipairs(cases) do
+ local code, body = t('/apisix/admin/routes/waf-' .. case.name,
+ ngx.HTTP_PUT, {
+ uri = "/hello",
+ vars = {{"arg_case", "==", case.name}},
+ plugins = {
+ ["chaitin-waf"] = {
+ mode = case.mode,
+ allow_degradation = case.allow_degradation,
+ append_waf_debug_header = true,
+ },
+ },
+ upstream = {
+ nodes = {["127.0.0.1:1980"] = 1},
+ type = "roundrobin",
+ },
+ })
+ if code >= 300 then
+ ngx.status = code
+ return ngx.say(body)
+ end
+ end
+
+ -- No detection service listens on this port.
+ local code, body = t('/apisix/admin/plugin_metadata/chaitin-waf',
+ ngx.HTTP_PUT, {
+ allow_degradation = true,
+ nodes = {{host = "127.0.0.1", port = 1}},
+ })
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 2: an unset route inherits metadata true on connection failure
+--- request
+GET /hello?case=inherit
+--- error_code: 200
+--- response_body
+hello world
+--- response_headers_like
+X-APISIX-CHAITIN-WAF: waf-err
+X-APISIX-CHAITIN-WAF-ERROR: .*connection refused
+--- error_log
+degradation enabled, passing request
+
+
+
+=== TEST 3: route false overrides metadata true on connection failure
+--- request
+GET /hello?case=disabled
+--- error_code: 500
+--- response_headers_like
+X-APISIX-CHAITIN-WAF: waf-err
+X-APISIX-CHAITIN-WAF-ERROR: .*connection refused
+--- no_error_log
+degradation enabled, passing request
+
+
+
+=== TEST 4: monitor still passes connection failure with degradation disabled
+--- request
+GET /hello?case=monitor
+--- error_code: 200
+--- response_body
+hello world
+--- response_headers_like
+X-APISIX-CHAITIN-WAF: waf-err
+X-APISIX-CHAITIN-WAF-ERROR: .*connection refused
+--- error_log
+chaitin-waf monitor mode: detected waf error
+--- no_error_log
+degradation enabled, passing request
+
+
+
+=== TEST 5: disable degradation in metadata
+--- 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, {
+ allow_degradation = false,
+ nodes = {{host = "127.0.0.1", port = 1}},
+ })
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 6: route true overrides metadata false on connection failure
+--- request
+GET /hello?case=enabled
+--- error_code: 200
+--- response_body
+hello world
+--- response_headers_like
+X-APISIX-CHAITIN-WAF: waf-err
+X-APISIX-CHAITIN-WAF-ERROR: .*connection refused
+--- error_log
+degradation enabled, passing request
+
+
+
+=== TEST 7: an unset route inherits metadata false on connection failure
+--- request
+GET /hello?case=inherit
+--- error_code: 500
+--- response_headers_like
+X-APISIX-CHAITIN-WAF: waf-err
+X-APISIX-CHAITIN-WAF-ERROR: .*connection refused
+--- no_error_log
+degradation enabled, passing request
+
+
+
+=== TEST 8: omit degradation in metadata
+--- 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 = 1}},
+ })
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 9: degradation remains disabled when both route and metadata omit the
option
+--- request
+GET /hello?case=inherit
+--- error_code: 500
+--- response_headers
+X-APISIX-CHAITIN-WAF: waf-err
+--- no_error_log
+degradation enabled, passing request
+
+
+
+=== TEST 10: remove metadata
+--- 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_DELETE)
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 11: missing metadata honors route true
+--- request
+GET /hello?case=enabled
+--- error_code: 200
+--- response_body
+hello world
+--- response_headers
+X-APISIX-CHAITIN-WAF: err
+X-APISIX-CHAITIN-WAF-ERROR: missing metadata
+--- error_log
+degradation enabled, passing request
+--- no_error_log
+[error]
+
+
+
+=== TEST 12: missing metadata honors route false
+--- request
+GET /hello?case=disabled
+--- error_code: 500
+--- response_headers
+X-APISIX-CHAITIN-WAF: err
+X-APISIX-CHAITIN-WAF-ERROR: missing metadata
+--- no_error_log
+[error]
+degradation enabled, passing request
+
+
+
+=== TEST 13: missing metadata remains blocked when the route omits degradation
+--- request
+GET /hello?case=inherit
+--- error_code: 500
+--- response_headers
+X-APISIX-CHAITIN-WAF: err
+X-APISIX-CHAITIN-WAF-ERROR: missing metadata
+--- no_error_log
+[error]
+degradation enabled, passing request
+
+
+
+=== TEST 14: configure a detection service that rejects requests
+--- 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
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 15: degradation does not bypass a WAF rejection in block mode
+--- request
+GET /hello?case=enabled
+--- error_code: 403
+--- response_body_like: blocked by Chaitin SafeLine Web Application Firewall
+--- response_headers
+X-APISIX-CHAITIN-WAF: yes
+X-APISIX-CHAITIN-WAF-ACTION: reject
+X-APISIX-CHAITIN-WAF-STATUS: 403
+--- error_log
+request rejected by chaitin-waf
+--- no_error_log
+degradation enabled, passing request
+
+
+
+=== TEST 16: monitor still passes a WAF rejection with degradation disabled
+--- request
+GET /hello?case=monitor
+--- error_code: 200
+--- response_body
+hello world
+--- response_headers
+X-APISIX-CHAITIN-WAF: yes
+X-APISIX-CHAITIN-WAF-ACTION: reject
+X-APISIX-CHAITIN-WAF-STATUS: 403
+--- error_log
+chaitin-waf monitor mode: request would have been rejected
+--- no_error_log
+degradation enabled, passing request
diff --git a/t/plugin/chaitin-waf-timeout.t b/t/plugin/chaitin-waf-timeout.t
index 063f1bc4e1..8d23fe6b8a 100644
--- a/t/plugin/chaitin-waf-timeout.t
+++ b/t/plugin/chaitin-waf-timeout.t
@@ -137,3 +137,98 @@ hello world
X-APISIX-CHAITIN-WAF: timeout
--- response_headers_like
X-APISIX-CHAITIN-WAF-TIME:
+
+
+
+=== TEST 3: allow_degradation=true on timeout
+--- 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": {
+ "mode": "block",
+ "allow_degradation": true
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/*"
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 4: timeout with allow_degradation=true, request passed
+--- request
+GET /hello
+--- error_code: 200
+--- response_body
+hello world
+--- error_log
+degradation enabled, passing request
+--- response_headers
+X-APISIX-CHAITIN-WAF: timeout
+
+
+
+=== TEST 5: allow_degradation=false on timeout (default behavior)
+--- 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": {
+ "mode": "block",
+ "allow_degradation": false
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/*"
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 6: timeout with allow_degradation=false, request blocked
+--- request
+GET /hello
+--- error_code: 500
+--- error_log
+--- response_headers
+X-APISIX-CHAITIN-WAF: timeout