From 47ff33c653cacf429460ad378477ac77ca0effe7 Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 4 Jun 2026 12:35:52 +0800 Subject: [PATCH] =?UTF-8?q?[=E4=BC=98=E5=8C=96]=20=E7=99=BD=E5=90=8D?= =?UTF-8?q?=E5=8D=95=E4=BC=98=E5=85=88=E7=BA=A7=E9=AB=98=E4=BA=8E=E9=BB=91?= =?UTF-8?q?=E5=90=8D=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/changelog/index.md | 2 ++ docs/design/waf-design.md | 7 ++++--- docs/guide/waf-usage.md | 6 ++++-- .../internal/nginx/manager_test.go | 12 +++++++++++ openflare_agent/internal/nginx/waf_assets.go | 20 +++++++++++++++++++ 5 files changed, 42 insertions(+), 5 deletions(-) diff --git a/docs/changelog/index.md b/docs/changelog/index.md index be151053..fb194b3d 100644 --- a/docs/changelog/index.md +++ b/docs/changelog/index.md @@ -22,6 +22,8 @@ sidebar: false ### 变更 +- WAF 白名单调整为准入名单语义:存在白名单规则时,未命中白名单的请求会被拦截 + --- ## [v2.3.2] - 2026-06-04 diff --git a/docs/design/waf-design.md b/docs/design/waf-design.md index b15693f0..5a81c46b 100644 --- a/docs/design/waf-design.md +++ b/docs/design/waf-design.md @@ -110,7 +110,8 @@ flowchart TD D -- 是 (匹配成功) --> E[放行请求 - ALLOW] D -- 否 --> F{匹配到国家/地区地域白名单?} F -- 是 (匹配成功) --> E - F -- 否 --> G{匹配到 IP 黑名单 / 黑名单 IP 组?} + F -- 否且已配置任意白名单 --> H + F -- 否且未配置白名单 --> G{匹配到 IP 黑名单 / 黑名单 IP 组?} G -- 是 (匹配成功) --> H[阻断请求 - BLOCK] G -- 否 --> I{匹配到国家/地区地域黑名单?} I -- 是 (匹配成功) --> H @@ -123,8 +124,8 @@ flowchart TD ### 2. 判决步骤细则 1. **白名单前置**: - 为了防止误杀以及保障核心回源流量(如搜索引擎蜘蛛、CDN 回源 IP、办公区出口)的顺畅,WAF **优先匹配 IP 白名单与地域白名单**。一旦白名单匹配成功,直接绕过后续的所有黑名单检测和 CC 挑战,立刻放行。 + 为了防止误杀以及保障核心回源流量(如搜索引擎蜘蛛、CDN 回源 IP、办公区出口)的顺畅,WAF **优先匹配 IP 白名单与地域白名单**。一旦白名单匹配成功,直接绕过后续的所有黑名单检测和 CC 挑战,立刻放行。只要当前生效规则组配置了任意白名单,请求未命中全部白名单时会被拦截,白名单在这种情况下表现为准入名单。 2. **黑名单强力阻断**: 如果在白名单判定中未被捕获,请求将进入黑名单漏斗。一旦请求源 IP 命中 IP 黑名单、命中引用的黑名单 IP 组、或是处于被禁止的国家/地区范围内,Lua 引擎立即将 `ngx.ctx.openflare_waf_blocked` 标记设为 `true`。 3. **输出响应**: - 命中黑名单后,Lua 提取匹配到规则组的 `block_status_code`(默认返回 418 / 403)和 `block_response_body`(拦截页面 HTML),通过 `ngx.say()` 输出响应体并执行 `ngx.exit(status)` 平滑退出请求,防止请求继续向后透传。 \ No newline at end of file + 命中黑名单后,Lua 提取匹配到规则组的 `block_status_code`(默认返回 418 / 403)和 `block_response_body`(拦截页面 HTML),通过 `ngx.say()` 输出响应体并执行 `ngx.exit(status)` 平滑退出请求,防止请求继续向后透传。 diff --git a/docs/guide/waf-usage.md b/docs/guide/waf-usage.md index bdf62400..85badba2 100644 --- a/docs/guide/waf-usage.md +++ b/docs/guide/waf-usage.md @@ -130,7 +130,8 @@ IP 组是进行大批量 IP 过滤的基石。OpenFlare 提供了极富弹性的 │ (否) ▼ 2. 匹配国家 / 省份地域白名单? ────────(是)─────► [ 放行 (ALLOW) ] - │ (否) + │ (否,且已配置任意白名单) ─► [ 拦截 (BLOCK) ] + │ (否,且未配置白名单) ▼ 3. 匹配 IP 黑名单 / 黑名单 IP 组? ──────(是)─────► [ 拦截 (BLOCK) ] ──► 返回自定义状态码与HTML拦截页 │ (否) @@ -152,7 +153,8 @@ IP 组是进行大批量 IP 过滤的基石。OpenFlare 提供了极富弹性的 ## 最佳实践与调优建议 -* **白名单前置与保护**:在部署高强度黑名单或地域屏蔽前,建议首先创建一个「受信任 IP 组」,放入你团队的办公室出口 IP、本地开发 IP 以及可能访问你的第三方回调源站 IP(如微信、支付宝支付回调地址),并在规则组的**白名单**中优先引入。这可以有效防止误杀。 +* **白名单准入语义**:一旦某个生效规则组配置了 IP 白名单、白名单 IP 组或地域白名单,请求必须命中其中至少一条白名单规则才会继续放行;未命中白名单的请求会被拦截。白名单命中后仍会优先绕过后续黑名单与 PoW 检查。 +* **白名单前置与保护**:在部署高强度黑名单或地域屏蔽前,建议首先创建一个「受信任 IP 组」,放入你团队的办公室出口 IP、本地开发 IP 以及可能访问你的第三方回调源站 IP(如微信、支付宝支付回调地址),并在规则组的**白名单**中优先引入。这可以有效防止误杀,但也会让未在白名单内的来源无法访问。 * **合理微调 PoW 难度**:人机 CC 挑战的哈希碰撞计算(`challenge_difficulty`)是一把双刃剑。 * 难度值 `3`:几乎瞬间完成计算,防 CC 强度低。 * 难度值 `4`:普通手机/低端浏览器在 100~300ms 内完成计算,防护性能良好。 diff --git a/openflare_agent/internal/nginx/manager_test.go b/openflare_agent/internal/nginx/manager_test.go index ed9e0493..f7d271cc 100644 --- a/openflare_agent/internal/nginx/manager_test.go +++ b/openflare_agent/internal/nginx/manager_test.go @@ -699,6 +699,18 @@ func TestManagedPowLuaFilesUseInternalChallengeFlow(t *testing.T) { } } +func TestManagedWAFLuaTreatsWhitelistAsAllowlist(t *testing.T) { + if !strings.Contains(openRestyWAFRuntimeLua, "local function first_allowlist_group(groups)") { + t.Fatal("expected waf runtime to detect allowlist rule groups") + } + if !strings.Contains(openRestyWAFRuntimeLua, "local allowlist_group = first_allowlist_group(groups)") { + t.Fatal("expected waf runtime to enter allowlist mode when whitelist rules exist") + } + if !strings.Contains(openRestyWAFRuntimeLua, "return exit_with_group(allowlist_group)") { + t.Fatal("expected waf runtime to block requests that miss configured whitelists") + } +} + func TestManagerRollbackRestoresCertFiles(t *testing.T) { tempDir := t.TempDir() routePath := filepath.Join(tempDir, "routes.conf") diff --git a/openflare_agent/internal/nginx/waf_assets.go b/openflare_agent/internal/nginx/waf_assets.go index ee679ad3..bfa024b6 100644 --- a/openflare_agent/internal/nginx/waf_assets.go +++ b/openflare_agent/internal/nginx/waf_assets.go @@ -91,6 +91,10 @@ local function list_contains(items, value) return false end +local function table_has_items(items) + return type(items) == "table" and #items > 0 +end + local function parse_ipv4(value) local a, b, c, d = string.match(value or "", "^(%d+)%.(%d+)%.(%d+)%.(%d+)$") if not a then @@ -215,6 +219,17 @@ local function exit_with_group(group) return ngx.exit(ngx.status) end +local function first_allowlist_group(groups) + for _, group in ipairs(groups) do + if table_has_items(group.ip_whitelist) + or table_has_items(group.ip_whitelist_group_ids) + or table_has_items(group.country_whitelist) then + return group + end + end + return nil +end + local config = load_config() if not config then if config_dict:add("_missing_config_logged", true, 60) then @@ -249,6 +264,11 @@ for _, group in ipairs(groups) do end end +local allowlist_group = first_allowlist_group(groups) +if allowlist_group then + return exit_with_group(allowlist_group) +end + for _, group in ipairs(groups) do if ip_matches(group.ip_blacklist, ip) or ip_matches_group_ids(group.ip_blacklist_group_ids, ip, ip_groups_config) then return exit_with_group(group)