fix: 修复 WAF 规则组保存/绑定网站时报 of_waf_rule_group_bindings_pkey 冲突

This commit is contained in:
ryan
2026-06-20 21:52:33 +08:00
parent 99f6f3231a
commit 117d473c27
11 changed files with 194 additions and 29 deletions
+35 -11
View File
@@ -149,10 +149,7 @@ func RenderWAFConfig(snapshot WAFDocument) (string, error) {
globalGroupIDs = append(globalGroupIDs, group.ID)
}
enabledGroupIDs[group.ID] = struct{}{}
powConfig := group.PoWConfig
if !group.PoWEnabled {
powConfig = nil
}
powConfig := ensurePoWConfig(group.PoWEnabled, group.PoWConfig)
groups = append(groups, wafRuntimeRuleGroup{
ID: group.ID,
Name: group.Name,
@@ -773,27 +770,54 @@ func validateCertificateCoverage(certPEM string, domains []string) error {
}
func getPoWConfigForRoute(routeID uint, snapshot WAFDocument) (bool, *PoWConfig) {
enabledGroups := make(map[uint]WAFRuleGroup, len(snapshot.RuleGroups))
globalGroupIDs := make([]uint, 0)
for _, group := range snapshot.RuleGroups {
if !group.Enabled {
continue
}
enabledGroups[group.ID] = group
if group.IsGlobal {
globalGroupIDs = append(globalGroupIDs, group.ID)
}
}
sort.Slice(globalGroupIDs, func(i, j int) bool { return globalGroupIDs[i] < globalGroupIDs[j] })
var boundGroupIDs []uint
for _, binding := range snapshot.Bindings {
if binding.RouteID != routeID {
continue
}
for _, groupID := range binding.RuleGroupIDs {
for _, group := range snapshot.RuleGroups {
if group.ID == groupID && group.PoWEnabled {
return true, group.PoWConfig
}
if _, ok := enabledGroups[groupID]; ok {
boundGroupIDs = append(boundGroupIDs, groupID)
}
}
break
}
for _, group := range snapshot.RuleGroups {
if group.IsGlobal && group.PoWEnabled {
return true, group.PoWConfig
activeGroupIDs := uniqueUintIDs(append(append([]uint{}, globalGroupIDs...), boundGroupIDs...))
for _, groupID := range activeGroupIDs {
group := enabledGroups[groupID]
if group.PoWEnabled {
config := ensurePoWConfig(true, group.PoWConfig)
return true, config
}
}
return false, nil
}
func ensurePoWConfig(enabled bool, config *PoWConfig) *PoWConfig {
if !enabled {
return nil
}
if config != nil {
return config
}
defaultConfig := DefaultPoWConfig()
return &defaultConfig
}
func uniqueUintIDs(values []uint) []uint {
seen := make(map[uint]struct{}, len(values))
result := make([]uint, 0, len(values))
+69
View File
@@ -60,6 +60,75 @@ func TestRenderWAFConfigIncludesAllRouteSiteNames(t *testing.T) {
}
}
func TestRenderWAFConfigUsesDefaultPoWConfigWhenEnabledWithoutPayload(t *testing.T) {
doc := WAFDocument{
RuleGroups: []WAFRuleGroup{
{
ID: 1,
Name: "global",
Enabled: true,
IsGlobal: true,
PoWEnabled: true,
},
},
Bindings: []WAFBinding{
{RouteID: 1, SiteName: "example.com", RuleGroupIDs: []uint{}},
},
}
wafConfig, err := RenderWAFConfig(doc)
if err != nil {
t.Fatalf("RenderWAFConfig() error = %v", err)
}
var decoded struct {
RuleGroups []struct {
PoWEnabled bool `json:"pow_enabled"`
PoWConfig *PoWConfig `json:"pow_config"`
} `json:"rule_groups"`
}
if err := json.Unmarshal([]byte(wafConfig), &decoded); err != nil {
t.Fatalf("json.Unmarshal() error = %v", err)
}
if len(decoded.RuleGroups) != 1 {
t.Fatalf("expected 1 rule group, got %d", len(decoded.RuleGroups))
}
if !decoded.RuleGroups[0].PoWEnabled {
t.Fatal("expected pow_enabled=true")
}
if decoded.RuleGroups[0].PoWConfig == nil {
t.Fatal("expected default pow_config to be emitted")
}
if decoded.RuleGroups[0].PoWConfig.Difficulty != 4 {
t.Fatalf("expected default difficulty 4, got %d", decoded.RuleGroups[0].PoWConfig.Difficulty)
}
}
func TestGetPoWConfigForRouteUsesGlobalGroupWithoutExplicitBinding(t *testing.T) {
snapshot := WAFDocument{
RuleGroups: []WAFRuleGroup{
{
ID: 1,
Name: "global",
Enabled: true,
IsGlobal: true,
PoWEnabled: true,
},
},
Bindings: []WAFBinding{
{RouteID: 42, SiteName: "example.com", RuleGroupIDs: []uint{}},
},
}
enabled, config := getPoWConfigForRoute(42, snapshot)
if !enabled {
t.Fatal("expected pow to be enabled via global rule group")
}
if config == nil || config.Difficulty != 4 {
t.Fatalf("expected default pow config, got %#v", config)
}
}
func TestRenderPagesAPIProxyLocationBlock(t *testing.T) {
tests := []struct {
name string
+13
View File
@@ -100,6 +100,19 @@ type PoWConfig struct {
Blacklist PoWListConfig `json:"blacklist"`
}
// DefaultPoWConfig returns the canonical PoW defaults used when pow_enabled is
// true but no explicit pow_config payload is available.
func DefaultPoWConfig() PoWConfig {
return PoWConfig{
Difficulty: 4,
Algorithm: "fast",
SessionTTL: 600,
ChallengeTTL: 300,
Whitelist: PoWListConfig{IPs: []string{}, IPCidrs: []string{}, Paths: []string{}, PathRegexes: []string{}, UserAgents: []string{}},
Blacklist: PoWListConfig{IPs: []string{}, IPCidrs: []string{}, Paths: []string{}, PathRegexes: []string{}, UserAgents: []string{}},
}
}
// Route describes a single proxy or pages site entry in the OpenFlare config
// document, including upstream, TLS, caching, rate-limiting and WAF settings.
type Route struct {