diff --git a/openflare_agent/internal/nginx/manager_test.go b/openflare_agent/internal/nginx/manager_test.go index 7d20da6e..ed9e0493 100644 --- a/openflare_agent/internal/nginx/manager_test.go +++ b/openflare_agent/internal/nginx/manager_test.go @@ -549,7 +549,7 @@ func TestManagerEnsureLuaAssetsWritesReadableFiles(t *testing.T) { if err != nil { t.Fatalf("failed to read pow lua file: %v", err) } - if !strings.Contains(string(data), filepath.ToSlash(manager.RuntimeConfigDir)+"/pow_config.json") { + if !strings.Contains(string(data), filepath.ToSlash(manager.RuntimeConfigDir)+"/waf_config.json") { t.Fatalf("expected pow lua to read runtime config dir, got %s", string(data)) } } diff --git a/openflare_agent/internal/nginx/pow_assets.go b/openflare_agent/internal/nginx/pow_assets.go index c318b2b4..7c013bf8 100644 --- a/openflare_agent/internal/nginx/pow_assets.go +++ b/openflare_agent/internal/nginx/pow_assets.go @@ -39,9 +39,9 @@ end -- Lazy-load pow_config from file; reload when content changes local function load_pow_config() local config_paths = { - "__OPENFLARE_RUNTIME_CONFIG_DIR__/pow_config.json", - "/etc/nginx/openflare-lua/pow_config.json", - "/usr/local/openresty/nginx/conf/pow_config.json" + "__OPENFLARE_RUNTIME_CONFIG_DIR__/waf_config.json", + "/etc/nginx/openflare-lua/waf_config.json", + "/usr/local/openresty/nginx/conf/waf_config.json" } for _, config_path in ipairs(config_paths) do local f = io.open(config_path, "r") @@ -54,7 +54,7 @@ local function load_pow_config() return end - -- Clear old domain entries + -- Clear old domain/site entries local old_keys = pow_config_dict:get("_domain_keys") if old_keys then for domain in string.gmatch(old_keys, "[^\n]+") do @@ -64,15 +64,38 @@ local function load_pow_config() local domain_keys = {} if content and content ~= "" and content ~= "{}" then - local ok, entries = pcall(cjson.decode, content) - if ok and entries and type(entries) == "table" then - for _, entry in ipairs(entries) do - if entry.domains then - for _, domain in ipairs(entry.domains) do - pow_config_dict:set(domain, cjson.encode(entry), 0) - domain_keys[#domain_keys+1] = domain + local ok, decoded = pcall(cjson.decode, content) + if ok and decoded and decoded.rule_groups and decoded.site_rule_groups then + -- Build rule groups map (group ID -> PoWConfig) + local groups = {} + for _, group in ipairs(decoded.rule_groups) do + if group.pow_enabled then + groups[tostring(group.id)] = group.pow_config + end + end + -- Build site name to pow_config map + for site, group_ids in pairs(decoded.site_rule_groups) do + local pow_config = nil + -- Check custom group IDs first + for _, id in ipairs(group_ids) do + pow_config = groups[tostring(id)] + if pow_config then + break end end + -- If not found, check global group IDs + if not pow_config then + for _, group in ipairs(decoded.rule_groups) do + if group.is_global and group.pow_enabled then + pow_config = group.pow_config + break + end + end + end + if pow_config then + pow_config_dict:set(site, cjson.encode({enabled = true, config = pow_config}), 0) + domain_keys[#domain_keys+1] = site + end end end end @@ -91,7 +114,12 @@ if not host or host == "" then return end -local config_raw = pow_config_dict:get(host) +local site = ngx.var.openflare_waf_site or "" +if site == "" then + site = host +end + +local config_raw = pow_config_dict:get(site) if not config_raw then return end @@ -199,17 +227,22 @@ local args = ngx.req.get_uri_args() local host = args["host"] or ngx.var.host or "" local redir = args["redir"] or "" -local config_raw = pow_config_dict:get(host) +local site = ngx.var.openflare_waf_site or "" +if site == "" then + site = host +end + +local config_raw = pow_config_dict:get(site) if not config_raw then ngx.status = 403 - ngx.say("PoW not configured for this host") + ngx.say("PoW not configured for this site") return end local ok, route_config = pcall(cjson.decode, config_raw) if not ok or not route_config or not route_config.enabled then ngx.status = 403 - ngx.say("PoW not enabled for this host") + ngx.say("PoW not enabled for this site") return end diff --git a/openflare_agent/internal/sync/service_test.go b/openflare_agent/internal/sync/service_test.go index e10aaf4f..bf989e24 100644 --- a/openflare_agent/internal/sync/service_test.go +++ b/openflare_agent/internal/sync/service_test.go @@ -190,7 +190,7 @@ func TestSyncOnceSuccess(t *testing.T) { if client.reports[0].MainConfigChecksum == "" || client.reports[0].RouteConfigChecksum == "" { t.Fatal("expected main and route config checksums to be reported") } - if client.reports[0].SupportFileCount != 4 { + if client.reports[0].SupportFileCount != 3 { t.Fatalf("expected support file count to be reported, got %d", client.reports[0].SupportFileCount) } } @@ -326,7 +326,7 @@ func TestSyncOnceRollbackOnNginxFailure(t *testing.T) { if client.reports[0].MainConfigChecksum == "" || client.reports[0].RouteConfigChecksum == "" { t.Fatal("expected failed report to include main and route config checksums") } - if client.reports[0].SupportFileCount != 4 { + if client.reports[0].SupportFileCount != 3 { t.Fatalf("expected failed report to include support file count, got %d", client.reports[0].SupportFileCount) } } diff --git a/openflare_server/model/goose/goose_202606030003_drop_proxy_route_legacy_pow.go b/openflare_server/model/goose/goose_202606030003_drop_proxy_route_legacy_pow.go new file mode 100644 index 00000000..c8f1fdd8 --- /dev/null +++ b/openflare_server/model/goose/goose_202606030003_drop_proxy_route_legacy_pow.go @@ -0,0 +1,41 @@ +package goose + +import ( + "fmt" + + presslygoose "github.com/pressly/goose/v3" + "gorm.io/gorm" +) + +const versionDropProxyRouteLegacyPoW int64 = 202606030003 + +// migration202606030003 drops the legacy pow_enabled and pow_config columns +// from proxy_routes table, since PoW is now entirely managed under WAF rule groups. +func migration202606030003(backend string, ctx Context) *presslygoose.Migration { + return newGORMMigration( + versionDropProxyRouteLegacyPoW, + "202606030003_drop_proxy_route_legacy_pow.go", + backend, + ctx, + migrateDropProxyRouteLegacyPoW, + ) +} + +func migrateDropProxyRouteLegacyPoW(ctx Context, db *gorm.DB, backend string) error { + if err := ctx.ApplyCurrentSchema(db, backend); err != nil { + return err + } + // Drop pow_enabled column if exists + if db.Migrator().HasColumn("proxy_routes", "pow_enabled") { + if err := db.Exec("ALTER TABLE proxy_routes DROP COLUMN pow_enabled").Error; err != nil { + return fmt.Errorf("drop proxy_routes.pow_enabled: %w", err) + } + } + // Drop pow_config column if exists + if db.Migrator().HasColumn("proxy_routes", "pow_config") { + if err := db.Exec("ALTER TABLE proxy_routes DROP COLUMN pow_config").Error; err != nil { + return fmt.Errorf("drop proxy_routes.pow_config: %w", err) + } + } + return nil +} diff --git a/openflare_server/model/goose/migrations.go b/openflare_server/model/goose/migrations.go index af806009..afa6437a 100644 --- a/openflare_server/model/goose/migrations.go +++ b/openflare_server/model/goose/migrations.go @@ -43,6 +43,7 @@ func registeredMigrations(backend string, ctx Context) []*presslygoose.Migration migration202606020001(backend, ctx), migration202606030001(backend, ctx), migration202606030002(backend, ctx), + migration202606030003(backend, ctx), } } diff --git a/openflare_server/model/main_test.go b/openflare_server/model/main_test.go index 6a9536df..552a78ab 100644 --- a/openflare_server/model/main_test.go +++ b/openflare_server/model/main_test.go @@ -205,6 +205,12 @@ func TestUpgradeDatabaseSchemaV15ToV16AppliesCompressedReleaseSchema(t *testing. if err := applyCurrentSchema(db, "sqlite"); err != nil { t.Fatalf("apply current schema: %v", err) } + if err := db.Exec("ALTER TABLE proxy_routes ADD COLUMN pow_enabled BOOLEAN NOT NULL DEFAULT 0").Error; err != nil { + t.Fatalf("failed to add legacy pow_enabled: %v", err) + } + if err := db.Exec("ALTER TABLE proxy_routes ADD COLUMN pow_config TEXT NOT NULL DEFAULT '{}'").Error; err != nil { + t.Fatalf("failed to add legacy pow_config: %v", err) + } if err := ensureDefaultWAFRuleGroup(db); err != nil { t.Fatalf("ensure default waf rule group: %v", err) } @@ -329,6 +335,13 @@ func TestEnsureDatabaseSchemaUpToDateUpgradesLegacyDatabase(t *testing.T) { if err := autoMigrateAll(db); err != nil { t.Fatalf("auto migrate db: %v", err) } + // Add legacy PoW columns manually to proxy_routes table to simulate legacy schema v9-v17 state + if err := db.Exec("ALTER TABLE proxy_routes ADD COLUMN pow_enabled BOOLEAN NOT NULL DEFAULT 0").Error; err != nil { + t.Fatalf("failed to add legacy pow_enabled: %v", err) + } + if err := db.Exec("ALTER TABLE proxy_routes ADD COLUMN pow_config TEXT NOT NULL DEFAULT '{}'").Error; err != nil { + t.Fatalf("failed to add legacy pow_config: %v", err) + } if err := db.Create(&User{ Username: "legacy", Password: "secret", @@ -439,6 +452,13 @@ func TestEnsureDatabaseSchemaUpToDateAddsProxyRouteDomainCertificateFields(t *te if err := db.AutoMigrate(&legacyProxyRouteV7{}); err != nil { t.Fatalf("auto migrate legacy proxy_routes v7: %v", err) } + // Add legacy PoW columns manually to proxy_routes table to simulate legacy schema v9-v17 state + if err := db.Exec("ALTER TABLE proxy_routes ADD COLUMN pow_enabled BOOLEAN NOT NULL DEFAULT 0").Error; err != nil { + t.Fatalf("failed to add legacy pow_enabled: %v", err) + } + if err := db.Exec("ALTER TABLE proxy_routes ADD COLUMN pow_config TEXT NOT NULL DEFAULT '{}'").Error; err != nil { + t.Fatalf("failed to add legacy pow_config: %v", err) + } now := time.Now().UTC() certID := uint(9) @@ -527,6 +547,12 @@ func TestEnsureDatabaseSchemaUpToDateAddsNodeIPManualOverride(t *testing.T) { if err := applyCurrentSchema(db, "sqlite"); err != nil { t.Fatalf("apply current schema: %v", err) } + if err := db.Exec("ALTER TABLE proxy_routes ADD COLUMN pow_enabled BOOLEAN NOT NULL DEFAULT 0").Error; err != nil { + t.Fatalf("failed to add legacy pow_enabled: %v", err) + } + if err := db.Exec("ALTER TABLE proxy_routes ADD COLUMN pow_config TEXT NOT NULL DEFAULT '{}'").Error; err != nil { + t.Fatalf("failed to add legacy pow_config: %v", err) + } if err := ensureDefaultWAFRuleGroup(db); err != nil { t.Fatalf("ensure default waf rule group: %v", err) } @@ -570,6 +596,12 @@ func TestEnsureDatabaseSchemaUpToDateV16BackfillsNodeColumnsWhenNewColumnsAlread if err := applyCurrentSchema(db, "sqlite"); err != nil { t.Fatalf("apply current schema: %v", err) } + if err := db.Exec("ALTER TABLE proxy_routes ADD COLUMN pow_enabled BOOLEAN NOT NULL DEFAULT 0").Error; err != nil { + t.Fatalf("failed to add legacy pow_enabled: %v", err) + } + if err := db.Exec("ALTER TABLE proxy_routes ADD COLUMN pow_config TEXT NOT NULL DEFAULT '{}'").Error; err != nil { + t.Fatalf("failed to add legacy pow_config: %v", err) + } if err := ensureDefaultWAFRuleGroup(db); err != nil { t.Fatalf("ensure default waf rule group: %v", err) } diff --git a/openflare_server/model/migrations.go b/openflare_server/model/migrations.go index a5514b20..3d305a9f 100644 --- a/openflare_server/model/migrations.go +++ b/openflare_server/model/migrations.go @@ -1131,11 +1131,23 @@ func validateDatabaseSchemaV9(db *gorm.DB, backend string) error { if err := validateDatabaseSchemaV8(db, backend); err != nil { return err } - if !db.Migrator().HasColumn(&ProxyRoute{}, "pow_enabled") { - return fmt.Errorf("column proxy_routes.pow_enabled is missing") + hasAppliedDropPoW := false + if db.Migrator().HasTable("goose_db_version") { + var count int64 + _ = db.Table("goose_db_version"). + Where("version_id = ? AND is_applied = ?", 202606030003, true). + Count(&count).Error + if count > 0 { + hasAppliedDropPoW = true + } } - if !db.Migrator().HasColumn(&ProxyRoute{}, "pow_config") { - return fmt.Errorf("column proxy_routes.pow_config is missing") + if !hasAppliedDropPoW { + if !db.Migrator().HasColumn(&ProxyRoute{}, "pow_enabled") { + return fmt.Errorf("column proxy_routes.pow_enabled is missing") + } + if !db.Migrator().HasColumn(&ProxyRoute{}, "pow_config") { + return fmt.Errorf("column proxy_routes.pow_config is missing") + } } return nil } diff --git a/openflare_server/model/proxy_route.go b/openflare_server/model/proxy_route.go index fee4125f..bf06dfc4 100644 --- a/openflare_server/model/proxy_route.go +++ b/openflare_server/model/proxy_route.go @@ -24,8 +24,6 @@ type ProxyRoute struct { CachePolicy string `json:"cache_policy" gorm:"size:32;not null;default:''"` CacheRules string `json:"cache_rules" gorm:"type:text;not null;default:'[]'"` CustomHeaders string `json:"custom_headers" gorm:"type:text;not null;default:'[]'"` - PoWEnabled bool `json:"pow_enabled" gorm:"column:pow_enabled;not null;default:false"` - PoWConfig string `json:"pow_config" gorm:"column:pow_config;type:text;not null;default:'{}'"` BasicAuthEnabled bool `json:"basic_auth_enabled" gorm:"not null;default:false"` BasicAuthUsername string `json:"basic_auth_username" gorm:"size:255;not null;default:''"` BasicAuthPassword string `json:"basic_auth_password" gorm:"size:255;not null;default:''"` @@ -86,8 +84,6 @@ func (route *ProxyRoute) Update() error { "cache_policy": route.CachePolicy, "cache_rules": route.CacheRules, "custom_headers": route.CustomHeaders, - "pow_enabled": route.PoWEnabled, - "pow_config": route.PoWConfig, "basic_auth_enabled": route.BasicAuthEnabled, "basic_auth_username": route.BasicAuthUsername, "basic_auth_password": route.BasicAuthPassword, diff --git a/openflare_server/service/agent_test.go b/openflare_server/service/agent_test.go index 7a22fe89..f3b126e0 100644 --- a/openflare_server/service/agent_test.go +++ b/openflare_server/service/agent_test.go @@ -11,39 +11,6 @@ import ( "gorm.io/gorm" ) -func TestGetActiveConfigForAgentIncludesPoWConfig(t *testing.T) { - setupServiceTestDB(t) - - _, err := CreateProxyRoute(ProxyRouteInput{ - Domain: "pow-agent.example.com", - OriginURL: "https://origin.internal", - Enabled: true, - PoWEnabled: true, - PoWConfig: `{"difficulty":4,"algorithm":"fast","session_ttl":86400,"challenge_ttl":300,"whitelist":{"paths":["/.well-known/*","/favicon.ico","/robots.txt"],"user_agents":["Googlebot","bingbot","Baiduspider"]},"blacklist":{"ips":[],"ip_cidrs":[],"paths":[],"path_regexes":[],"user_agents":[]}}`, - }) - if err != nil { - t.Fatalf("CreateProxyRoute failed: %v", err) - } - - if _, err := PublishConfigVersion("root", false); err != nil { - t.Fatalf("PublishConfigVersion failed: %v", err) - } - - activeConfig, err := GetActiveConfigForAgent() - if err != nil { - t.Fatalf("GetActiveConfigForAgent failed: %v", err) - } - - for _, file := range activeConfig.SupportFiles { - if file.Path == "pow_config.json" || file.Path == "waf_config.json" { - t.Fatalf("agent config should not receive rendered runtime config file %s", file.Path) - } - } - if !strings.Contains(activeConfig.SourceConfigJSON, `"pow_enabled":true`) { - t.Fatal("expected agent config source json to include PoW source configuration") - } -} - func TestGetActiveConfigForAgentIncludesWAFConfig(t *testing.T) { setupServiceTestDB(t) @@ -144,39 +111,6 @@ func TestChangedWAFIPGroupsForAgentReturnsChecksumDelta(t *testing.T) { } } -func TestGetActiveConfigForAgentUsesTenMinutePoWSessionDefault(t *testing.T) { - setupServiceTestDB(t) - - _, err := CreateProxyRoute(ProxyRouteInput{ - Domain: "pow-default.example.com", - OriginURL: "https://origin.internal", - Enabled: true, - PoWEnabled: true, - PoWConfig: `{}`, - }) - if err != nil { - t.Fatalf("CreateProxyRoute failed: %v", err) - } - - if _, err := PublishConfigVersion("root", false); err != nil { - t.Fatalf("PublishConfigVersion failed: %v", err) - } - - activeConfig, err := GetActiveConfigForAgent() - if err != nil { - t.Fatalf("GetActiveConfigForAgent failed: %v", err) - } - - for _, file := range activeConfig.SupportFiles { - if file.Path == "pow_config.json" { - t.Fatal("agent config should not receive rendered pow_config.json") - } - } - if !strings.Contains(activeConfig.SourceConfigJSON, `"session_ttl":600`) { - t.Fatalf("expected default PoW session TTL to be in source json, got %s", activeConfig.SourceConfigJSON) - } -} - func TestRegisterNodeWithAccessToken(t *testing.T) { setupServiceTestDB(t) diff --git a/openflare_server/service/config_version.go b/openflare_server/service/config_version.go index 5abe38de..5fb5a615 100644 --- a/openflare_server/service/config_version.go +++ b/openflare_server/service/config_version.go @@ -83,8 +83,6 @@ type snapshotRoute struct { CachePolicy string `json:"cache_policy,omitempty"` CacheRules []string `json:"cache_rules,omitempty"` CustomHeaders []ProxyRouteCustomHeaderInput `json:"custom_headers,omitempty"` - PoWEnabled bool `json:"pow_enabled,omitempty"` - PoWConfig *ProxyRoutePoWConfig `json:"pow_config,omitempty"` BasicAuthEnabled bool `json:"basic_auth_enabled,omitempty"` BasicAuthUsername string `json:"basic_auth_username,omitempty"` BasicAuthPassword string `json:"basic_auth_password,omitempty"` @@ -551,13 +549,6 @@ func buildSnapshotRoutes(routes []*model.ProxyRoute) ([]snapshotRoute, error) { if err != nil { return nil, fmt.Errorf("路由 %s 缓存规则无效", route.Domain) } - powConfig, err := decodeStoredPoWConfig(route.PoWEnabled, route.PoWConfig) - if err != nil { - return nil, fmt.Errorf("路由 %s PoW 配置无效", route.Domain) - } - if !route.PoWEnabled { - powConfig = nil - } items = append(items, snapshotRoute{ ID: route.ID, SiteName: normalizeProxyRouteSiteNameInput(route, route.SiteName, domains[0]), @@ -579,8 +570,6 @@ func buildSnapshotRoutes(routes []*model.ProxyRoute) ([]snapshotRoute, error) { CachePolicy: route.CachePolicy, CacheRules: cacheRules, CustomHeaders: customHeaders, - PoWEnabled: route.PoWEnabled, - PoWConfig: powConfig, BasicAuthEnabled: route.BasicAuthEnabled, BasicAuthUsername: route.BasicAuthUsername, BasicAuthPassword: route.BasicAuthPassword, @@ -861,17 +850,6 @@ func normalizeSnapshotRoutes(routes []snapshotRoute) []snapshotRoute { if err == nil { routes[index].LimitRate = normalizedLimitRate } - if routes[index].PoWEnabled { - raw, err := json.Marshal(routes[index].PoWConfig) - if err == nil { - normalizedPoWConfig, err := normalizePoWConfig(true, string(raw)) - if err == nil { - routes[index].PoWConfig = &normalizedPoWConfig - } - } - } else { - routes[index].PoWConfig = nil - } if !routes[index].BasicAuthEnabled { routes[index].BasicAuthUsername = "" routes[index].BasicAuthPassword = "" @@ -918,7 +896,7 @@ func flattenSnapshotRoutesByDomain(routes []snapshotRoute) map[string]snapshotRo } func snapshotRouteConfigEqual(left snapshotRoute, right snapshotRoute) bool { - if left.SiteName != right.SiteName || left.Domain != right.Domain || left.OriginURL != right.OriginURL || left.OriginHost != right.OriginHost || left.EnableHTTPS != right.EnableHTTPS || left.RedirectHTTP != right.RedirectHTTP || left.LimitConnPerServer != right.LimitConnPerServer || left.LimitConnPerIP != right.LimitConnPerIP || left.LimitRate != right.LimitRate || left.CacheEnabled != right.CacheEnabled || left.CachePolicy != right.CachePolicy || left.PoWEnabled != right.PoWEnabled || left.BasicAuthEnabled != right.BasicAuthEnabled || left.BasicAuthUsername != right.BasicAuthUsername || left.BasicAuthPassword != right.BasicAuthPassword || left.UpstreamType != right.UpstreamType || !uintPtrEqual(left.TunnelNodeID, right.TunnelNodeID) || left.TunnelTargetAddr != right.TunnelTargetAddr || left.TunnelTargetProto != right.TunnelTargetProto || !uintPtrEqual(left.PagesProjectID, right.PagesProjectID) || !snapshotPagesDeploymentEqual(left.PagesDeployment, right.PagesDeployment) || !uintSliceEqual(left.CertIDs, right.CertIDs) || !uintSliceEqual(left.DomainCertIDs, right.DomainCertIDs) { + if left.SiteName != right.SiteName || left.Domain != right.Domain || left.OriginURL != right.OriginURL || left.OriginHost != right.OriginHost || left.EnableHTTPS != right.EnableHTTPS || left.RedirectHTTP != right.RedirectHTTP || left.LimitConnPerServer != right.LimitConnPerServer || left.LimitConnPerIP != right.LimitConnPerIP || left.LimitRate != right.LimitRate || left.CacheEnabled != right.CacheEnabled || left.CachePolicy != right.CachePolicy || left.BasicAuthEnabled != right.BasicAuthEnabled || left.BasicAuthUsername != right.BasicAuthUsername || left.BasicAuthPassword != right.BasicAuthPassword || left.UpstreamType != right.UpstreamType || !uintPtrEqual(left.TunnelNodeID, right.TunnelNodeID) || left.TunnelTargetAddr != right.TunnelTargetAddr || left.TunnelTargetProto != right.TunnelTargetProto || !uintPtrEqual(left.PagesProjectID, right.PagesProjectID) || !snapshotPagesDeploymentEqual(left.PagesDeployment, right.PagesDeployment) || !uintSliceEqual(left.CertIDs, right.CertIDs) || !uintSliceEqual(left.DomainCertIDs, right.DomainCertIDs) { return false } if len(left.Domains) != len(right.Domains) { @@ -953,9 +931,6 @@ func snapshotRouteConfigEqual(left snapshotRoute, right snapshotRoute) bool { return false } } - if !snapshotPoWConfigEqual(left.PoWConfig, right.PoWConfig) { - return false - } return true } @@ -986,26 +961,6 @@ func snapshotWAFConfigEqual(left snapshotWAFDocument, right snapshotWAFDocument) return string(leftJSON) == string(rightJSON) } -func snapshotPoWConfigEqual(left *ProxyRoutePoWConfig, right *ProxyRoutePoWConfig) bool { - if left == nil || right == nil { - return left == nil && right == nil - } - return left.Difficulty == right.Difficulty && - left.Algorithm == right.Algorithm && - left.SessionTTL == right.SessionTTL && - left.ChallengeTTL == right.ChallengeTTL && - stringSliceEqual(left.Whitelist.IPs, right.Whitelist.IPs) && - stringSliceEqual(left.Whitelist.IPCidrs, right.Whitelist.IPCidrs) && - stringSliceEqual(left.Whitelist.Paths, right.Whitelist.Paths) && - stringSliceEqual(left.Whitelist.PathRegexes, right.Whitelist.PathRegexes) && - stringSliceEqual(left.Whitelist.UserAgents, right.Whitelist.UserAgents) && - stringSliceEqual(left.Blacklist.IPs, right.Blacklist.IPs) && - stringSliceEqual(left.Blacklist.IPCidrs, right.Blacklist.IPCidrs) && - stringSliceEqual(left.Blacklist.Paths, right.Blacklist.Paths) && - stringSliceEqual(left.Blacklist.PathRegexes, right.Blacklist.PathRegexes) && - stringSliceEqual(left.Blacklist.UserAgents, right.Blacklist.UserAgents) -} - func stringSliceEqual(left []string, right []string) bool { if len(left) != len(right) { return false diff --git a/openflare_server/service/https_phase1_test.go b/openflare_server/service/https_phase1_test.go index 3903bb26..bd36b164 100644 --- a/openflare_server/service/https_phase1_test.go +++ b/openflare_server/service/https_phase1_test.go @@ -982,31 +982,31 @@ func TestPublishConfigVersionDetectsPoWChanges(t *testing.T) { if err != nil { t.Fatalf("initial PublishConfigVersion failed: %v", err) } - if !strings.Contains(firstRelease.Version.SupportFilesJSON, `"path":"pow_config.json"`) { - t.Fatal("expected publish to include pow_config.json support file") + if !strings.Contains(firstRelease.Version.SupportFilesJSON, `"path":"waf_config.json"`) { + t.Fatal("expected publish to include waf_config.json support file") } - _, err = UpdateProxyRoute(route.ID, ProxyRouteInput{ - Domain: route.Domain, - OriginURL: route.OriginURL, - Enabled: true, - PoWEnabled: true, - PoWConfig: `{"difficulty":5,"algorithm":"slow","session_ttl":7200,"challenge_ttl":180,"whitelist":{"ips":["127.0.0.1"],"ip_cidrs":[],"paths":[],"path_regexes":[],"user_agents":[]},"blacklist":{"ips":[],"ip_cidrs":[],"paths":["/login"],"path_regexes":[],"user_agents":[]}}`, - RedirectHTTP: false, + group, err := CreateWAFRuleGroup(WAFRuleGroupInput{ + Name: "pow group", + Enabled: true, + BlockStatusCode: 418, + PoWEnabled: true, + PoWConfig: json.RawMessage(`{"difficulty":5,"algorithm":"slow","session_ttl":7200,"challenge_ttl":180,"whitelist":{"ips":["127.0.0.1"],"ip_cidrs":[],"paths":[],"path_regexes":[],"user_agents":[]},"blacklist":{"ips":[],"ip_cidrs":[],"paths":["/login"],"path_regexes":[],"user_agents":[]}}`), }) if err != nil { - t.Fatalf("UpdateProxyRoute failed: %v", err) + t.Fatalf("CreateWAFRuleGroup failed: %v", err) + } + + if _, err = ReplaceWAFSiteRuleGroups(route.ID, []uint{group.ID}); err != nil { + t.Fatalf("ReplaceWAFSiteRuleGroups failed: %v", err) } diff, err := DiffConfigVersion() if err != nil { t.Fatalf("DiffConfigVersion failed: %v", err) } - if len(diff.ModifiedDomains) != 1 || diff.ModifiedDomains[0] != "pow.example.com" { - t.Fatalf("expected PoW change to mark domain as modified, got %#v", diff.ModifiedDomains) - } - if len(diff.ModifiedSites) != 1 || diff.ModifiedSites[0] != "pow.example.com" { - t.Fatalf("expected PoW change to mark site as modified, got %#v", diff.ModifiedSites) + if !diff.WAFConfigChanged { + t.Fatal("expected PoW change (via WAF Rule Group) to trigger WAF config change") } secondRelease, err := PublishConfigVersion("root", false) @@ -1053,18 +1053,18 @@ func TestPublishConfigVersionDetectsPoWChanges(t *testing.T) { if err := json.Unmarshal([]byte(secondRelease.Version.SupportFilesJSON), &supportFiles); err != nil { t.Fatalf("failed to decode support files: %v", err) } - foundPowSupportFile := false + foundWafSupportFile := false for _, file := range supportFiles { - if file.Path != "pow_config.json" { + if file.Path != "waf_config.json" { continue } - foundPowSupportFile = true + foundWafSupportFile = true if !strings.Contains(file.Content, `"difficulty":5`) { - t.Fatalf("expected pow support file to persist config, got %s", file.Content) + t.Fatalf("expected waf support file to persist pow config, got %s", file.Content) } } - if !foundPowSupportFile { - t.Fatal("expected publish to include pow_config.json support file") + if !foundWafSupportFile { + t.Fatal("expected publish to include waf_config.json support file") } } @@ -1081,15 +1081,13 @@ func TestPublishConfigVersionRendersBasicAuthWithPoW(t *testing.T) { t.Fatalf("CreateTLSCertificate failed: %v", err) } - _, err = CreateProxyRoute(ProxyRouteInput{ + route, err := CreateProxyRoute(ProxyRouteInput{ Domain: "xbot.example.com", OriginURL: "http://c1:36185", Enabled: true, EnableHTTPS: true, CertID: &certificate.ID, RedirectHTTP: true, - PoWEnabled: true, - PoWConfig: `{"difficulty":4,"algorithm":"fast","session_ttl":600,"challenge_ttl":300,"whitelist":{"ips":[],"ip_cidrs":[],"paths":[],"path_regexes":[],"user_agents":[]},"blacklist":{"ips":[],"ip_cidrs":[],"paths":[],"path_regexes":[],"user_agents":[]}}`, BasicAuthEnabled: true, BasicAuthUsername: "admin", BasicAuthPassword: "123", @@ -1098,6 +1096,21 @@ func TestPublishConfigVersionRendersBasicAuthWithPoW(t *testing.T) { t.Fatalf("CreateProxyRoute failed: %v", err) } + group, err := CreateWAFRuleGroup(WAFRuleGroupInput{ + Name: "pow group", + Enabled: true, + BlockStatusCode: 418, + PoWEnabled: true, + PoWConfig: json.RawMessage(`{"difficulty":4,"algorithm":"fast","session_ttl":600,"challenge_ttl":300,"whitelist":{"ips":[],"ip_cidrs":[],"paths":[],"path_regexes":[],"user_agents":[]},"blacklist":{"ips":[],"ip_cidrs":[],"paths":[],"path_regexes":[],"user_agents":[]}}`), + }) + if err != nil { + t.Fatalf("CreateWAFRuleGroup failed: %v", err) + } + + if _, err = ReplaceWAFSiteRuleGroups(route.ID, []uint{group.ID}); err != nil { + t.Fatalf("ReplaceWAFSiteRuleGroups failed: %v", err) + } + result, err := PublishConfigVersion("root", false) if err != nil { t.Fatalf("PublishConfigVersion failed: %v", err) diff --git a/openflare_server/service/proxy_route.go b/openflare_server/service/proxy_route.go index 53e13156..bda17435 100644 --- a/openflare_server/service/proxy_route.go +++ b/openflare_server/service/proxy_route.go @@ -55,8 +55,6 @@ type ProxyRouteInput struct { CachePolicy string `json:"cache_policy"` CacheRules []string `json:"cache_rules"` CustomHeaders []ProxyRouteCustomHeaderInput `json:"custom_headers"` - PoWEnabled bool `json:"pow_enabled"` - PoWConfig string `json:"pow_config"` BasicAuthEnabled bool `json:"basic_auth_enabled"` BasicAuthUsername string `json:"basic_auth_username"` BasicAuthPassword string `json:"basic_auth_password"` @@ -96,8 +94,6 @@ type ProxyRouteView struct { CacheRuleList []string `json:"cache_rule_list"` CustomHeaders string `json:"custom_headers"` CustomHeaderList []ProxyRouteCustomHeaderInput `json:"custom_header_list"` - PoWEnabled bool `json:"pow_enabled"` - PoWConfig *ProxyRoutePoWConfig `json:"pow_config"` BasicAuthEnabled bool `json:"basic_auth_enabled"` BasicAuthUsername string `json:"basic_auth_username"` BasicAuthPassword string `json:"basic_auth_password"` @@ -239,15 +235,6 @@ func buildProxyRoute(route *model.ProxyRoute, input ProxyRouteInput) (*model.Pro return nil, err } - powConfig, err := normalizePoWConfig(input.PoWEnabled, input.PoWConfig) - if err != nil { - return nil, err - } - powConfigJSON, err := json.Marshal(powConfig) - if err != nil { - return nil, err - } - if !input.EnableHTTPS { input.RedirectHTTP = false input.CertID = nil @@ -330,8 +317,6 @@ func buildProxyRoute(route *model.ProxyRoute, input ProxyRouteInput) (*model.Pro route.CachePolicy = normalizeCachePolicy(input.CacheEnabled, cachePolicy) route.CacheRules = string(cacheRulesJSON) route.CustomHeaders = string(customHeadersJSON) - route.PoWEnabled = input.PoWEnabled - route.PoWConfig = string(powConfigJSON) route.BasicAuthEnabled = input.BasicAuthEnabled route.BasicAuthUsername = input.BasicAuthUsername route.BasicAuthPassword = input.BasicAuthPassword @@ -395,10 +380,6 @@ func buildProxyRouteView(route *model.ProxyRoute) (*ProxyRouteView, error) { if err != nil { return nil, err } - powConfig, err := decodeStoredPoWConfig(route.PoWEnabled, route.PoWConfig) - if err != nil { - return nil, err - } certIDs, err := decodeStoredCertIDs(route.CertIDs, route.CertID) if err != nil { return nil, err @@ -439,8 +420,6 @@ func buildProxyRouteView(route *model.ProxyRoute) (*ProxyRouteView, error) { CacheRuleList: cacheRules, CustomHeaders: route.CustomHeaders, CustomHeaderList: customHeaders, - PoWEnabled: route.PoWEnabled, - PoWConfig: powConfig, BasicAuthEnabled: route.BasicAuthEnabled, BasicAuthUsername: route.BasicAuthUsername, BasicAuthPassword: route.BasicAuthPassword, diff --git a/openflare_server/utils/render/openresty/render.go b/openflare_server/utils/render/openresty/render.go index 315955b5..3e2c1d24 100644 --- a/openflare_server/utils/render/openresty/render.go +++ b/openflare_server/utils/render/openresty/render.go @@ -34,12 +34,7 @@ func Render(doc Document, certificateFiles []SupportFile) (*Result, error) { if err != nil { return nil, err } - powConfig, err := RenderPoWConfig(doc) - if err != nil { - return nil, err - } files := append([]SupportFile(nil), certificateFiles...) - files = append(files, SupportFile{Path: "pow_config.json", Content: powConfig}) files = append(files, SupportFile{Path: "waf_config.json", Content: wafConfig}) files = DedupeSupportFiles(files) return &Result{ @@ -88,9 +83,6 @@ func RenderRouteConfig(doc Document, certificateFiles []SupportFile) (string, er cacheConfig := routeCacheConfig{Enabled: route.CacheEnabled, Policy: route.CachePolicy, Rules: route.CacheRules} limitConfig := routeLimitConfig{LimitConnPerServer: route.LimitConnPerServer, LimitConnPerIP: route.LimitConnPerIP, LimitRate: route.LimitRate} powEnabled, _ := getPoWConfigForRoute(route.ID, doc.WAF) - if route.PoWEnabled { - powEnabled = true - } if normalizeRouteUpstreamType(route.UpstreamType) == "pages" { if route.PagesDeployment == nil { return "", fmt.Errorf("route %s pages deployment is missing", route.Domain) @@ -214,12 +206,6 @@ func RenderPoWConfig(doc Document) (string, error) { entries := make([]domainEntry, 0) for _, route := range doc.Routes { powEnabled, powConfig := getPoWConfigForRoute(route.ID, doc.WAF) - if route.PoWEnabled { - powEnabled = true - if route.PoWConfig != nil { - powConfig = route.PoWConfig - } - } if !powEnabled { continue } @@ -234,19 +220,21 @@ func RenderPoWConfig(doc Document) (string, error) { func RenderWAFConfig(snapshot WAFDocument) (string, error) { type wafRuntimeRuleGroup struct { - ID uint `json:"id"` - Name string `json:"name"` - IsGlobal bool `json:"is_global"` - BlockStatusCode int `json:"block_status_code"` - BlockResponseBody string `json:"block_response_body"` - IPWhitelist []string `json:"ip_whitelist"` - IPBlacklist []string `json:"ip_blacklist"` - IPWhitelistGroups []uint `json:"ip_whitelist_group_ids,omitempty"` - IPBlacklistGroups []uint `json:"ip_blacklist_group_ids,omitempty"` - CountryWhitelist []string `json:"country_whitelist"` - CountryBlacklist []string `json:"country_blacklist"` - RegionWhitelist []string `json:"region_whitelist"` - RegionBlacklist []string `json:"region_blacklist"` + ID uint `json:"id"` + Name string `json:"name"` + IsGlobal bool `json:"is_global"` + BlockStatusCode int `json:"block_status_code"` + BlockResponseBody string `json:"block_response_body"` + IPWhitelist []string `json:"ip_whitelist"` + IPBlacklist []string `json:"ip_blacklist"` + IPWhitelistGroups []uint `json:"ip_whitelist_group_ids,omitempty"` + IPBlacklistGroups []uint `json:"ip_blacklist_group_ids,omitempty"` + CountryWhitelist []string `json:"country_whitelist"` + CountryBlacklist []string `json:"country_blacklist"` + RegionWhitelist []string `json:"region_whitelist"` + RegionBlacklist []string `json:"region_blacklist"` + PoWEnabled bool `json:"pow_enabled"` + PoWConfig *PoWConfig `json:"pow_config,omitempty"` } type wafRuntimeConfig struct { DefaultBlockStatusCode int `json:"default_block_status_code"` @@ -268,6 +256,10 @@ func RenderWAFConfig(snapshot WAFDocument) (string, error) { globalGroupIDs = append(globalGroupIDs, group.ID) } enabledGroupIDs[group.ID] = struct{}{} + powConfig := group.PoWConfig + if !group.PoWEnabled { + powConfig = nil + } groups = append(groups, wafRuntimeRuleGroup{ ID: group.ID, Name: group.Name, @@ -282,6 +274,8 @@ func RenderWAFConfig(snapshot WAFDocument) (string, error) { CountryBlacklist: group.CountryBlacklist, RegionWhitelist: group.RegionWhitelist, RegionBlacklist: group.RegionBlacklist, + PoWEnabled: group.PoWEnabled, + PoWConfig: powConfig, }) } sort.Slice(groups, func(i, j int) bool {