mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-28 05:46:36 +08:00
fix(waf): correct whitelist logic to bypass and add config/IP-group edit broadcasts
- Transition WAF whitelist filter from strict block-on-miss to bypass-on-hit logic - Hook up broadcastIPGroupToAgents to CreateIPGroup and UpdateIPGroup WAF logics - Hook up BroadcastActiveConfig to PublishConfigVersion and ActivateConfigVersion version logics - Update WAF Lua tests in manager_test.go
This commit is contained in:
@@ -23,6 +23,7 @@ sidebar: false
|
||||
|
||||
### 修复
|
||||
|
||||
- 修复代理路由详情页点击“发布配置”时,同时弹出配置差异对话框和确认发布对话框导致重叠的问题:点击发布时不再展示配置差异,直接进行确认发布。
|
||||
- 修复配置版本发布到 Agent 后 `openresty -t` 因 `proxy_cache_path` 使用 `/var/cache/openresty` 导致非 root 用户 `mkdir` 失败的问题:发布快照与渲染将 `/var/` 下路径规范为 `__OPENFLARE_PROXY_CACHE_PATH__`,Agent 应用时落地为 `data_dir/var/cache/openflare_proxy` 并兼容重写已发布配置中的旧路径。
|
||||
- 修复配置版本发布到 Agent 后 `openresty -t` 因证书私钥无法解析而失败的问题。根因是发布快照生成 `certs/{id}.key` 时直接写入库内加密的 `KeyPEM`(`enc:v1:`),未解密为 PEM;现与证书详情接口一致,发布前通过 `OpenKeyPEM` 解密后再下发。
|
||||
- 修复 `/api/v1/d/option` 批量更新 OpenResty 等业务配置不生效的问题。根本原因是 option 模块在读写时做了 PascalCase 与 snake_case 的机械转换(如 `OpenRestyEventsUse` → `open_resty_events_use`),与 `w_system_configs` 中实际 key(`openresty_events_use`)不一致,更新写入了错误的幽灵配置行。现改为 API 直接使用与数据库一致的 snake_case key,并同步更新前端性能调优与运维设置页。
|
||||
@@ -30,6 +31,8 @@ sidebar: false
|
||||
- 修复 openflared(Tunnel Client)WebSocket 连接在 Cloudflare 代理环境下频繁收到 EOF 断连的问题。根本原因:服务端 `read_pump` 仅在收到 WebSocket 协议层 Pong 帧时刷新读超时,而客户端(`golang.org/x/net/websocket`)以 JSON 应用层 `{"type":"pong"}` 响应 ping,服务端 90s 读超时到期后主动关闭连接,客户端收到 EOF 并进入无限重连循环。修复方式:在 `clientPongType` 分支中同步调用 `conn.SetReadDeadline` 刷新超时。
|
||||
- 修复 openflared frpc 子进程异常退出(`exit status 1`)时缺乏详细诊断信息的问题。现捕获 frpc stderr 并在进程退出时将其输出记录到结构化日志 `stderr` 字段,便于排查配置格式错误、Auth Token 鉴权失败、relay 端不可达等具体原因。
|
||||
- 修复 Relay 节点启动时在双栈网络环境可能上报 IPv6 地址,导致 Tunnel frpc 客户端无法连接 frps 的问题。强化 `pkg/geoip.HTTPOutboundIPStrategy` 在回退到双栈客户端后仍优先返回 IPv4 地址,确保 Relay 心跳上报的 IP 与 frpc 连接兼容。
|
||||
- 修复 WAF 黑白名单判定时,白名单作为严格准入控制导致黑名单逻辑失效的问题。现将白名单逻辑调整为信任放行(Bypass/Allow),命中的请求直接放行,未命中的请求继续进入黑名单等防护模块判定。
|
||||
- 修复 WAF IP 组手动编辑和配置版本发布后,未向 Agent 触发 WebSocket 实时广播导致配置变更不能即时生效的问题。
|
||||
|
||||
### 变更
|
||||
|
||||
|
||||
@@ -699,15 +699,12 @@ 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")
|
||||
func TestManagedWAFLuaTreatsWhitelistAsBypass(t *testing.T) {
|
||||
if !strings.Contains(openRestyWAFRuntimeLua, "if ip_matches(group.ip_whitelist, ip)") {
|
||||
t.Fatal("expected waf runtime to bypass request when ip matches whitelist")
|
||||
}
|
||||
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")
|
||||
if strings.Contains(openRestyWAFRuntimeLua, "first_allowlist_group") {
|
||||
t.Fatal("expected waf runtime not to block requests that miss configured whitelists")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -219,17 +219,6 @@ 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
|
||||
@@ -264,11 +253,6 @@ 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)
|
||||
|
||||
@@ -15,7 +15,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/internal/apps/openflare/routeidentity"
|
||||
"github.com/Rain-kl/Wavelet/internal/apps/openflare/websocket"
|
||||
"github.com/Rain-kl/Wavelet/internal/model"
|
||||
pkgprotocol "github.com/Rain-kl/Wavelet/pkg/protocol"
|
||||
openrestyrender "github.com/Rain-kl/Wavelet/pkg/render/openresty"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -233,6 +235,10 @@ func PublishConfigVersion(ctx context.Context, createdBy string, force bool) (*m
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
websocket.BroadcastActiveConfig(pkgprotocol.ActiveConfigMeta{
|
||||
Version: record.Version,
|
||||
Checksum: record.Checksum,
|
||||
})
|
||||
return record, nil
|
||||
}
|
||||
|
||||
@@ -246,6 +252,10 @@ func ActivateConfigVersion(ctx context.Context, id uint) (*model.ConfigVersion,
|
||||
return nil, err
|
||||
}
|
||||
version.IsActive = true
|
||||
websocket.BroadcastActiveConfig(pkgprotocol.ActiveConfigMeta{
|
||||
Version: version.Version,
|
||||
Checksum: version.Checksum,
|
||||
})
|
||||
return version, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -454,6 +454,7 @@ func CreateIPGroup(ctx context.Context, input IPGroupInput) (*IPGroupView, error
|
||||
if err = model.CreateOpenFlareWAFIPGroup(ctx, group); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
broadcastIPGroupToAgents(ctx, group.ID)
|
||||
return GetIPGroup(ctx, group.ID)
|
||||
}
|
||||
|
||||
@@ -470,6 +471,7 @@ func UpdateIPGroup(ctx context.Context, id uint, input IPGroupInput) (*IPGroupVi
|
||||
if err = model.UpdateOpenFlareWAFIPGroup(ctx, group); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
broadcastIPGroupToAgents(ctx, group.ID)
|
||||
return GetIPGroup(ctx, group.ID)
|
||||
}
|
||||
|
||||
|
||||
@@ -152,6 +152,28 @@ func BroadcastWAFIPGroups(payload any) int {
|
||||
return success
|
||||
}
|
||||
|
||||
// BroadcastActiveConfig pushes active config metadata to all connected agents.
|
||||
func BroadcastActiveConfig(payload any) int {
|
||||
if payload == nil {
|
||||
return 0
|
||||
}
|
||||
message := Message{Type: agentMessageTypeActiveConfig, Payload: payload}
|
||||
defaultAgentHub.mu.RLock()
|
||||
clients := make([]*agentClient, 0, len(defaultAgentHub.clients))
|
||||
for _, client := range defaultAgentHub.clients {
|
||||
clients = append(clients, client)
|
||||
}
|
||||
defaultAgentHub.mu.RUnlock()
|
||||
|
||||
success := 0
|
||||
for _, client := range clients {
|
||||
if client.enqueue(message) {
|
||||
success++
|
||||
}
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// SendForceSyncConfig notifies an agent to force sync configuration.
|
||||
func SendForceSyncConfig(nodeID string, payload any) bool {
|
||||
return sendAgentMessage(nodeID, Message{Type: agentMessageTypeForceSyncConfig, Payload: payload})
|
||||
|
||||
Reference in New Issue
Block a user