This commit is contained in:
ryan
2026-06-19 15:13:24 +08:00
parent 0b34792709
commit 32861c5db9
376 changed files with 3648 additions and 19957 deletions
+1 -5
View File
@@ -1,9 +1,5 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
// Package waf defines shared error messages for WAF management.
package waf
const (
errWAFRuleGroupNotFound = "WAF 规则组不存在"
errWAFIPGroupNotFound = "IP 组不存在"
)
+9 -5
View File
@@ -95,7 +95,7 @@ func syncOpenFlareWAFIPGroup(ctx context.Context, group *model.OpenFlareWAFIPGro
}
func syncIPGroupSubscription(ctx context.Context, group *model.OpenFlareWAFIPGroup, now time.Time) (*IPGroupSyncResult, error) {
content, err := downloadIPGroupSubscription(group.SubscriptionURL)
content, err := downloadIPGroupSubscription(ctx, group.SubscriptionURL)
if err != nil {
recordIPGroupSyncFailure(ctx, group, now, err)
return nil, err
@@ -266,7 +266,7 @@ func evaluateParsedIPGroupAutoConfig(ctx context.Context, config ipGroupAutoConf
if item.StatusCode >= 400 && item.StatusCode < 500 {
acc.clientErrorCount++
}
if item.StatusCode >= 500 {
if item.StatusCode >= http.StatusInternalServerError {
acc.serverErrorCount++
}
if hostIsIPLiteral(item.Host) {
@@ -341,16 +341,20 @@ func hostIsIPLiteral(value string) bool {
return ok
}
func downloadIPGroupSubscription(rawURL string) ([]byte, error) {
func downloadIPGroupSubscription(ctx context.Context, rawURL string) ([]byte, error) {
if err := validateSubscriptionURL(rawURL); err != nil {
return nil, err
}
client := http.Client{Timeout: 15 * time.Second}
resp, err := client.Get(rawURL)
req, err := http.NewRequestWithContext(ctx, "GET", rawURL, nil)
if err != nil {
return nil, fmt.Errorf("下载订阅失败: %w", err)
}
defer resp.Body.Close()
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("下载订阅失败: %w", err)
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("订阅返回状态码 %d", resp.StatusCode)
}
+26 -73
View File
@@ -8,10 +8,8 @@ import (
"encoding/json"
"errors"
"fmt"
"net"
"net/netip"
"net/url"
"regexp"
"sort"
"strings"
"time"
@@ -38,6 +36,9 @@ const (
defaultWAFIPGroupAutoLookbackMinutes = 60
minWAFIPGroupSyncIntervalMinutes = 5
maxWAFIPGroupSyncIntervalMinutes = 43200
minPoWSessionTTLSeconds = 60
minPoWChallengeTTLSeconds = 30
)
// RuleGroupInput is the create/update payload for WAF rule groups.
@@ -986,82 +987,34 @@ func defaultPoWConfig() PoWConfig {
}
func normalizePoWConfig(enabled bool, raw string) (PoWConfig, error) {
if !enabled {
return defaultPoWConfig(), nil
cfg, err := parsePoWConfigRaw(enabled, raw)
if err != nil {
return cfg, err
}
cfg := defaultPoWConfig()
text := strings.TrimSpace(raw)
if text != "" && text != "{}" {
if err := json.Unmarshal([]byte(text), &cfg); err != nil {
return cfg, errors.New("pow_config 格式无效")
}
if err := validatePoWCoreSettings(cfg); err != nil {
return cfg, err
}
if cfg.Difficulty < 1 || cfg.Difficulty > 16 {
return cfg, errors.New("pow_config.difficulty 必须在 1-16 之间")
if err := validatePoWCIDRs(cfg.Whitelist.IPCidrs, "白名单"); err != nil {
return cfg, err
}
if !powAlgorithmValues[cfg.Algorithm] {
return cfg, errors.New("pow_config.algorithm 必须为 fast 或 slow")
if err := validatePoWCIDRs(cfg.Blacklist.IPCidrs, "黑名单"); err != nil {
return cfg, err
}
if cfg.SessionTTL < 60 {
return cfg, errors.New("pow_config.session_ttl 不能小于 60 秒")
if err := validatePoWPathRegexes(cfg.Whitelist.PathRegexes, "白名单"); err != nil {
return cfg, err
}
if cfg.ChallengeTTL < 30 {
return cfg, errors.New("pow_config.challenge_ttl 不能小于 30 秒")
if err := validatePoWPathRegexes(cfg.Blacklist.PathRegexes, "黑名单"); err != nil {
return cfg, err
}
for _, cidr := range cfg.Whitelist.IPCidrs {
if _, _, err := net.ParseCIDR(cidr); err != nil {
return cfg, fmt.Errorf("pow_config 白名单 IP CIDR 格式无效: %s", cidr)
}
if err := validatePoWIPs(cfg.Whitelist.IPs, "白名单"); err != nil {
return cfg, err
}
for _, cidr := range cfg.Blacklist.IPCidrs {
if _, _, err := net.ParseCIDR(cidr); err != nil {
return cfg, fmt.Errorf("pow_config 黑名单 IP CIDR 格式无效: %s", cidr)
}
if err := validatePoWIPs(cfg.Blacklist.IPs, "黑名单"); err != nil {
return cfg, err
}
for _, re := range cfg.Whitelist.PathRegexes {
if _, err := regexp.Compile(re); err != nil {
return cfg, fmt.Errorf("pow_config 白名单路径正则格式无效: %s", re)
}
if err := validatePoWListMutualExclusion(cfg); err != nil {
return cfg, err
}
for _, re := range cfg.Blacklist.PathRegexes {
if _, err := regexp.Compile(re); err != nil {
return cfg, fmt.Errorf("pow_config 黑名单路径正则格式无效: %s", re)
}
}
for _, ip := range cfg.Whitelist.IPs {
if net.ParseIP(ip) == nil {
return cfg, fmt.Errorf("pow_config 白名单 IP 格式无效: %s", ip)
}
}
for _, ip := range cfg.Blacklist.IPs {
if net.ParseIP(ip) == nil {
return cfg, fmt.Errorf("pow_config 黑名单 IP 格式无效: %s", ip)
}
}
type dimension struct {
name string
wl []string
bl []string
}
dimensions := []dimension{
{"IP", cfg.Whitelist.IPs, cfg.Blacklist.IPs},
{"IP CIDR", cfg.Whitelist.IPCidrs, cfg.Blacklist.IPCidrs},
{"路径", cfg.Whitelist.Paths, cfg.Blacklist.Paths},
{"路径正则", cfg.Whitelist.PathRegexes, cfg.Blacklist.PathRegexes},
{"User-Agent", cfg.Whitelist.UserAgents, cfg.Blacklist.UserAgents},
}
for _, dim := range dimensions {
if len(dim.wl) > 0 && len(dim.bl) > 0 {
return cfg, fmt.Errorf("pow_config %s 不能同时配置白名单和黑名单", dim.name)
}
}
return cfg, nil
}
@@ -1111,11 +1064,11 @@ func parseIPGroupAutoConfig(raw json.RawMessage) (ipGroupAutoConfig, error) {
if config.LookbackMinutes <= 0 {
config.LookbackMinutes = defaultWAFIPGroupAutoLookbackMinutes
}
if config.LookbackMinutes < 5 {
config.LookbackMinutes = 5
if config.LookbackMinutes < minWAFIPGroupSyncIntervalMinutes {
config.LookbackMinutes = minWAFIPGroupSyncIntervalMinutes
}
if config.LookbackMinutes > 43200 {
config.LookbackMinutes = 43200
if config.LookbackMinutes > maxWAFIPGroupSyncIntervalMinutes {
config.LookbackMinutes = maxWAFIPGroupSyncIntervalMinutes
}
if config.TTL == 0 {
config.TTL = -1
@@ -0,0 +1,92 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package waf
import (
"encoding/json"
"errors"
"fmt"
"net"
"regexp"
"strings"
)
func parsePoWConfigRaw(enabled bool, raw string) (PoWConfig, error) {
if !enabled {
return defaultPoWConfig(), nil
}
cfg := defaultPoWConfig()
text := strings.TrimSpace(raw)
if text == "" || text == "{}" {
return cfg, nil
}
if err := json.Unmarshal([]byte(text), &cfg); err != nil {
return cfg, errors.New("pow_config 格式无效")
}
return cfg, nil
}
func validatePoWCoreSettings(cfg PoWConfig) error {
if cfg.Difficulty < 1 || cfg.Difficulty > 16 {
return errors.New("pow_config.difficulty 必须在 1-16 之间")
}
if !powAlgorithmValues[cfg.Algorithm] {
return errors.New("pow_config.algorithm 必须为 fast 或 slow")
}
if cfg.SessionTTL < minPoWSessionTTLSeconds {
return errors.New("pow_config.session_ttl 不能小于 60 秒")
}
if cfg.ChallengeTTL < minPoWChallengeTTLSeconds {
return errors.New("pow_config.challenge_ttl 不能小于 30 秒")
}
return nil
}
func validatePoWCIDRs(cidrs []string, listName string) error {
for _, cidr := range cidrs {
if _, _, err := net.ParseCIDR(cidr); err != nil {
return fmt.Errorf("pow_config %s IP CIDR 格式无效: %s", listName, cidr)
}
}
return nil
}
func validatePoWPathRegexes(regexes []string, listName string) error {
for _, re := range regexes {
if _, err := regexp.Compile(re); err != nil {
return fmt.Errorf("pow_config %s路径正则格式无效: %s", listName, re)
}
}
return nil
}
func validatePoWIPs(ips []string, listName string) error {
for _, ip := range ips {
if net.ParseIP(ip) == nil {
return fmt.Errorf("pow_config %s IP 格式无效: %s", listName, ip)
}
}
return nil
}
func validatePoWListMutualExclusion(cfg PoWConfig) error {
type dimension struct {
name string
wl []string
bl []string
}
dimensions := []dimension{
{"IP", cfg.Whitelist.IPs, cfg.Blacklist.IPs},
{"IP CIDR", cfg.Whitelist.IPCidrs, cfg.Blacklist.IPCidrs},
{"路径", cfg.Whitelist.Paths, cfg.Blacklist.Paths},
{"路径正则", cfg.Whitelist.PathRegexes, cfg.Blacklist.PathRegexes},
{"User-Agent", cfg.Whitelist.UserAgents, cfg.Blacklist.UserAgents},
}
for _, dim := range dimensions {
if len(dim.wl) > 0 && len(dim.bl) > 0 {
return fmt.Errorf("pow_config %s 不能同时配置白名单和黑名单", dim.name)
}
}
return nil
}