
* Refactor anubis to split business logic into a lib, and cmd to just be direct usage. * Post-rebase fixes. * Update changelog, remove unnecessary one. * lib: refactor this This is mostly based on my personal preferences for how Go code should be laid out. I'm not sold on the package name "lib" (I'd call it anubis but that would stutter), but people are probably gonna import it as libanubis so it's likely fine. Packages have been "flattened" to centralize implementation with area of concern. This goes against the Java-esque style that many people like, but I think this helps make things simple. Most notably: the dnsbl client (which is a hack) is an internal package until it's made more generic. Then it can be made external. I also fixed the logic such that `go generate` works and rebased on main. * internal/test: run tests iff npx exists and DONT_USE_NETWORK is not set Signed-off-by: Xe Iaso <me@xeiaso.net> * internal/test: install deps Signed-off-by: Xe Iaso <me@xeiaso.net> * .github/workflows: verbose go tests? Signed-off-by: Xe Iaso <me@xeiaso.net> * internal/test: sleep 2 Signed-off-by: Xe Iaso <me@xeiaso.net> * internal/test: nix this test so CI works Signed-off-by: Xe Iaso <me@xeiaso.net> * internal/test: warmup per browser? Signed-off-by: Xe Iaso <me@xeiaso.net> * internal/test: disable for now :( Signed-off-by: Xe Iaso <me@xeiaso.net> * lib/anubis: do not apply bot rules if address check fails Closes #83 --------- Signed-off-by: Xe Iaso <me@xeiaso.net> Co-authored-by: Xe Iaso <me@xeiaso.net>
162 lines
4.3 KiB
Go
162 lines
4.3 KiB
Go
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
|
|
}
|