diff options
Diffstat (limited to 'lib/policy/config/config.go')
| -rw-r--r-- | lib/policy/config/config.go | 162 |
1 files changed, 162 insertions, 0 deletions
diff --git a/lib/policy/config/config.go b/lib/policy/config/config.go new file mode 100644 index 0000000..67eddbf --- /dev/null +++ b/lib/policy/config/config.go @@ -0,0 +1,162 @@ +package config + +import ( + "errors" + "fmt" + "net" + "regexp" +) + +var ( + ErrNoBotRulesDefined = errors.New("config: must define at least one (1) bot rule") + ErrBotMustHaveName = errors.New("config.Bot: must set name") + ErrBotMustHaveUserAgentOrPath = errors.New("config.Bot: must set either user_agent_regex, path_regex, or remote_addresses") + ErrBotMustHaveUserAgentOrPathNotBoth = errors.New("config.Bot: must set either user_agent_regex, path_regex, and not both") + ErrUnknownAction = errors.New("config.Bot: unknown action") + ErrInvalidUserAgentRegex = errors.New("config.Bot: invalid user agent regex") + ErrInvalidPathRegex = errors.New("config.Bot: invalid path regex") + ErrInvalidCIDR = errors.New("config.Bot: invalid CIDR") +) + +type Rule string + +const ( + RuleUnknown Rule = "" + RuleAllow Rule = "ALLOW" + RuleDeny Rule = "DENY" + RuleChallenge Rule = "CHALLENGE" +) + +type Algorithm string + +const ( + AlgorithmUnknown Algorithm = "" + AlgorithmFast Algorithm = "fast" + AlgorithmSlow Algorithm = "slow" +) + +type BotConfig struct { + Name string `json:"name"` + UserAgentRegex *string `json:"user_agent_regex"` + PathRegex *string `json:"path_regex"` + Action Rule `json:"action"` + RemoteAddr []string `json:"remote_addresses"` + Challenge *ChallengeRules `json:"challenge,omitempty"` +} + +func (b BotConfig) Valid() error { + var errs []error + + if b.Name == "" { + errs = append(errs, ErrBotMustHaveName) + } + + if b.UserAgentRegex == nil && b.PathRegex == nil && (b.RemoteAddr == nil || len(b.RemoteAddr) == 0) { + errs = append(errs, ErrBotMustHaveUserAgentOrPath) + } + + if b.UserAgentRegex != nil && b.PathRegex != nil { + errs = append(errs, ErrBotMustHaveUserAgentOrPathNotBoth) + } + + if b.UserAgentRegex != nil { + if _, err := regexp.Compile(*b.UserAgentRegex); err != nil { + errs = append(errs, ErrInvalidUserAgentRegex, err) + } + } + + if b.PathRegex != nil { + if _, err := regexp.Compile(*b.PathRegex); err != nil { + errs = append(errs, ErrInvalidPathRegex, err) + } + } + + if b.RemoteAddr != nil && len(b.RemoteAddr) > 0 { + for _, cidr := range b.RemoteAddr { + if _, _, err := net.ParseCIDR(cidr); err != nil { + errs = append(errs, ErrInvalidCIDR, err) + } + } + } + + switch b.Action { + case RuleAllow, RuleChallenge, RuleDeny: + // okay + default: + errs = append(errs, fmt.Errorf("%w: %q", ErrUnknownAction, b.Action)) + } + + if b.Action == RuleChallenge && b.Challenge != nil { + if err := b.Challenge.Valid(); err != nil { + errs = append(errs, err) + } + } + + if len(errs) != 0 { + return fmt.Errorf("config: bot entry for %q is not valid:\n%w", b.Name, errors.Join(errs...)) + } + + return nil +} + +type ChallengeRules struct { + Difficulty int `json:"difficulty"` + ReportAs int `json:"report_as"` + Algorithm Algorithm `json:"algorithm"` +} + +var ( + ErrChallengeRuleHasWrongAlgorithm = errors.New("config.Bot.ChallengeRules: algorithm is invalid") + ErrChallengeDifficultyTooLow = errors.New("config.Bot.ChallengeRules: difficulty is too low (must be >= 1)") + ErrChallengeDifficultyTooHigh = errors.New("config.Bot.ChallengeRules: difficulty is too high (must be <= 64)") +) + +func (cr ChallengeRules) Valid() error { + var errs []error + + if cr.Difficulty < 1 { + errs = append(errs, fmt.Errorf("%w, got: %d", ErrChallengeDifficultyTooLow, cr.Difficulty)) + } + + if cr.Difficulty > 64 { + errs = append(errs, fmt.Errorf("%w, got: %d", ErrChallengeDifficultyTooHigh, cr.Difficulty)) + } + + switch cr.Algorithm { + case AlgorithmFast, AlgorithmSlow, AlgorithmUnknown: + // do nothing, it's all good + default: + errs = append(errs, fmt.Errorf("%w: %q", ErrChallengeRuleHasWrongAlgorithm, cr.Algorithm)) + } + + if len(errs) != 0 { + return fmt.Errorf("config: challenge rules entry is not valid:\n%w", errors.Join(errs...)) + } + + return nil +} + +type Config struct { + Bots []BotConfig `json:"bots"` + DNSBL bool `json:"dnsbl"` +} + +func (c Config) Valid() error { + var errs []error + + if len(c.Bots) == 0 { + errs = append(errs, ErrNoBotRulesDefined) + } + + for _, b := range c.Bots { + if err := b.Valid(); err != nil { + errs = append(errs, err) + } + } + + if len(errs) != 0 { + return fmt.Errorf("config is not valid:\n%w", errors.Join(errs...)) + } + + return nil +} |
