feat: implement challenge registry (#607)

* feat: implement challenge method registry

This paves the way for implementing a no-js check method (#95) by making
the challenge providers more generic.

Signed-off-by: Xe Iaso <me@xeiaso.net>

* fix(lib/challenge): rename proof-of-work package to proofofwork

Signed-off-by: Xe Iaso <me@xeiaso.net>

* fix(lib): make validated challenges a CounterVec

Signed-off-by: Xe Iaso <me@xeiaso.net>

* fix(lib): annotate jwts with challenge method

Signed-off-by: Xe Iaso <me@xeiaso.net>

* test(lib/challenge/proofofwork): implement tests

Signed-off-by: Xe Iaso <me@xeiaso.net>

* test(lib): add smoke tests for known good and known bad config files

Signed-off-by: Xe Iaso <me@xeiaso.net>

* docs: update CHANGELOG

Signed-off-by: Xe Iaso <me@xeiaso.net>

* fix(lib): use challenge.Impl#Issue when issuing challenges

Signed-off-by: Xe Iaso <me@xeiaso.net>

---------

Signed-off-by: Xe Iaso <me@xeiaso.net>
This commit is contained in:
Xe Iaso 2025-06-03 22:01:58 -04:00 committed by GitHub
parent ba4412c907
commit f2db43ad4b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 480 additions and 132 deletions

View file

@ -42,13 +42,7 @@ const (
RuleBenchmark Rule = "DEBUG_BENCHMARK"
)
type Algorithm string
const (
AlgorithmUnknown Algorithm = ""
AlgorithmFast Algorithm = "fast"
AlgorithmSlow Algorithm = "slow"
)
const DefaultAlgorithm = "fast"
type BotConfig struct {
UserAgentRegex *string `json:"user_agent_regex"`
@ -170,15 +164,14 @@ func (b BotConfig) Valid() error {
}
type ChallengeRules struct {
Algorithm Algorithm `json:"algorithm"`
Difficulty int `json:"difficulty"`
ReportAs int `json:"report_as"`
Algorithm string `json:"algorithm"`
Difficulty int `json:"difficulty"`
ReportAs int `json:"report_as"`
}
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)")
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 {
@ -192,13 +185,6 @@ func (cr ChallengeRules) Valid() error {
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...))
}

View file

@ -130,20 +130,6 @@ func TestBotValid(t *testing.T) {
},
err: ErrChallengeDifficultyTooHigh,
},
{
name: "challenge wrong algorithm",
bot: BotConfig{
Name: "mozilla-ua",
Action: RuleChallenge,
PathRegex: p("Mozilla"),
Challenge: &ChallengeRules{
Difficulty: 420,
ReportAs: 4,
Algorithm: "high quality rips",
},
},
err: ErrChallengeRuleHasWrongAlgorithm,
},
{
name: "invalid cidr range",
bot: BotConfig{
@ -361,7 +347,7 @@ func TestBotConfigZero(t *testing.T) {
b.Challenge = &ChallengeRules{
Difficulty: 4,
ReportAs: 4,
Algorithm: AlgorithmFast,
Algorithm: DefaultAlgorithm,
}
if b.Zero() {
t.Error("BotConfig with challenge rules is zero value")

View file

@ -5,10 +5,9 @@ import (
"fmt"
"io"
"github.com/TecharoHQ/anubis/lib/policy/config"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/TecharoHQ/anubis/lib/policy/config"
)
var (
@ -16,6 +15,8 @@ var (
Name: "anubis_policy_results",
Help: "The results of each policy rule",
}, []string{"rule", "action"})
ErrChallengeRuleHasWrongAlgorithm = errors.New("config.Bot.ChallengeRules: algorithm is invalid")
)
type ParsedConfig struct {
@ -107,12 +108,12 @@ func ParseConfig(fin io.Reader, fname string, defaultDifficulty int) (*ParsedCon
parsedBot.Challenge = &config.ChallengeRules{
Difficulty: defaultDifficulty,
ReportAs: defaultDifficulty,
Algorithm: config.AlgorithmFast,
Algorithm: "fast",
}
} else {
parsedBot.Challenge = b.Challenge
if parsedBot.Challenge.Algorithm == config.AlgorithmUnknown {
parsedBot.Challenge.Algorithm = config.AlgorithmFast
if parsedBot.Challenge.Algorithm == "" {
parsedBot.Challenge.Algorithm = config.DefaultAlgorithm
}
}