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

Reply via email to