cmd/anubis: configurable difficulty per-bot rule (#53)
Closes #30 Introduces the "challenge" field in bot rule definitions: ```json { "name": "generic-bot-catchall", "user_agent_regex": "(?i:bot|crawler)", "action": "CHALLENGE", "challenge": { "difficulty": 16, "report_as": 4, "algorithm": "slow" } } ``` This makes Anubis return a challenge page for every user agent with "bot" or "crawler" in it (case-insensitively) with difficulty 16 using the old "slow" algorithm but reporting in the client as difficulty 4. This is useful when you want to make certain clients in particular suffer. Additional validation and testing logic has been added to make sure that users do not define "impossible" challenge settings. If no algorithm is specified, Anubis defaults to the "fast" algorithm. Signed-off-by: Xe Iaso <me@xeiaso.net>
This commit is contained in:
parent
90049001e9
commit
d3e509517c
17 changed files with 311 additions and 46 deletions
|
|
@ -9,17 +9,26 @@ import (
|
|||
type Rule string
|
||||
|
||||
const (
|
||||
RuleUnknown = ""
|
||||
RuleAllow = "ALLOW"
|
||||
RuleDeny = "DENY"
|
||||
RuleChallenge = "CHALLENGE"
|
||||
RuleUnknown Rule = ""
|
||||
RuleAllow Rule = "ALLOW"
|
||||
RuleDeny Rule = "DENY"
|
||||
RuleChallenge Rule = "CHALLENGE"
|
||||
)
|
||||
|
||||
type Algorithm string
|
||||
|
||||
const (
|
||||
AlgorithmUnknown Algorithm = ""
|
||||
AlgorithmFast Algorithm = "fast"
|
||||
AlgorithmSlow Algorithm = "slow"
|
||||
)
|
||||
|
||||
type Bot struct {
|
||||
Name string `json:"name"`
|
||||
UserAgentRegex *string `json:"user_agent_regex"`
|
||||
PathRegex *string `json:"path_regex"`
|
||||
Action Rule `json:"action"`
|
||||
Name string `json:"name"`
|
||||
UserAgentRegex *string `json:"user_agent_regex"`
|
||||
PathRegex *string `json:"path_regex"`
|
||||
Action Rule `json:"action"`
|
||||
Challenge *ChallengeRules `json:"challenge,omitempty"`
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
@ -66,6 +75,12 @@ func (b Bot) Valid() error {
|
|||
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...))
|
||||
}
|
||||
|
|
@ -73,6 +88,43 @@ func (b Bot) Valid() error {
|
|||
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 []Bot `json:"bots"`
|
||||
DNSBL bool `json:"dnsbl"`
|
||||
|
|
|
|||
|
|
@ -87,6 +87,48 @@ func TestBotValid(t *testing.T) {
|
|||
},
|
||||
err: ErrInvalidPathRegex,
|
||||
},
|
||||
{
|
||||
name: "challenge difficulty too low",
|
||||
bot: Bot{
|
||||
Name: "mozilla-ua",
|
||||
Action: RuleChallenge,
|
||||
PathRegex: p("Mozilla"),
|
||||
Challenge: &ChallengeRules{
|
||||
Difficulty: 0,
|
||||
ReportAs: 4,
|
||||
Algorithm: "fast",
|
||||
},
|
||||
},
|
||||
err: ErrChallengeDifficultyTooLow,
|
||||
},
|
||||
{
|
||||
name: "challenge difficulty too high",
|
||||
bot: Bot{
|
||||
Name: "mozilla-ua",
|
||||
Action: RuleChallenge,
|
||||
PathRegex: p("Mozilla"),
|
||||
Challenge: &ChallengeRules{
|
||||
Difficulty: 420,
|
||||
ReportAs: 4,
|
||||
Algorithm: "fast",
|
||||
},
|
||||
},
|
||||
err: ErrChallengeDifficultyTooHigh,
|
||||
},
|
||||
{
|
||||
name: "challenge wrong algorithm",
|
||||
bot: Bot{
|
||||
Name: "mozilla-ua",
|
||||
Action: RuleChallenge,
|
||||
PathRegex: p("Mozilla"),
|
||||
Challenge: &ChallengeRules{
|
||||
Difficulty: 420,
|
||||
ReportAs: 4,
|
||||
Algorithm: "high quality rips",
|
||||
},
|
||||
},
|
||||
err: ErrChallengeRuleHasWrongAlgorithm,
|
||||
},
|
||||
}
|
||||
|
||||
for _, cs := range tests {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue