Skip to content

Commit 1ee0a79

Browse files
committed
feat(lib): implement request weight
Replaces #608 This is a big one and will be what makes Anubis a generic web application firewall. This introduces the WEIGH option, allowing administrators to have facets of request metadata add or remove "weight", or the level of suspicion. This really makes Anubis weigh the soul of requests. Signed-off-by: Xe Iaso <[email protected]>
1 parent 5a7499e commit 1ee0a79

File tree

11 files changed

+85
-16
lines changed

11 files changed

+85
-16
lines changed

docs/docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
## [Unreleased]
1313

14+
- Requests can have their weight be adjusted, if a request weighs zero then it is allowed through
1415
- Refactor challenge presentation logic to use a challenge registry
1516
- Allow challenge implementations to register HTTP routes
1617

lib/anubis.go

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -406,10 +406,11 @@ func (s *Server) TestError(w http.ResponseWriter, r *http.Request) {
406406
s.respondWithError(w, r, err)
407407
}
408408

409-
func cr(name string, rule config.Rule) policy.CheckResult {
409+
func cr(name string, rule config.Rule, weight int) policy.CheckResult {
410410
return policy.CheckResult{
411-
Name: name,
412-
Rule: rule,
411+
Name: name,
412+
Rule: rule,
413+
Weight: weight,
413414
}
414415
}
415416

@@ -425,18 +426,37 @@ func (s *Server) check(r *http.Request) (policy.CheckResult, *policy.Bot, error)
425426
return decaymap.Zilch[policy.CheckResult](), nil, fmt.Errorf("[misconfiguration] %q is not an IP address", host)
426427
}
427428

429+
weight := 0
430+
428431
for _, b := range s.policy.Bots {
429432
match, err := b.Rules.Check(r)
430433
if err != nil {
431434
return decaymap.Zilch[policy.CheckResult](), nil, fmt.Errorf("can't run check %s: %w", b.Name, err)
432435
}
433436

434437
if match {
435-
return cr("bot/"+b.Name, b.Action), &b, nil
438+
switch b.Action {
439+
case config.RuleDeny, config.RuleAllow, config.RuleBenchmark:
440+
return cr("bot/"+b.Name, b.Action, weight), &b, nil
441+
case config.RuleChallenge:
442+
weight += 5
443+
case config.RuleWeigh:
444+
weight += b.Weight.Adjust
445+
}
446+
}
447+
448+
if weight < 0 {
449+
return cr("weight/okay", config.RuleAllow, weight), &policy.Bot{
450+
Challenge: &config.ChallengeRules{
451+
Difficulty: s.policy.DefaultDifficulty,
452+
ReportAs: s.policy.DefaultDifficulty,
453+
Algorithm: config.DefaultAlgorithm,
454+
},
455+
}, nil
436456
}
437457
}
438458

439-
return cr("default/allow", config.RuleAllow), &policy.Bot{
459+
return cr("default/allow", config.RuleAllow, weight), &policy.Bot{
440460
Challenge: &config.ChallengeRules{
441461
Difficulty: s.policy.DefaultDifficulty,
442462
ReportAs: s.policy.DefaultDifficulty,

lib/policy/bot.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ type Bot struct {
1212
Challenge *config.ChallengeRules
1313
Name string
1414
Action config.Rule
15+
Weight *config.Weight
1516
}
1617

1718
func (b Bot) Hash() string {

lib/policy/checkresult.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@ import (
77
)
88

99
type CheckResult struct {
10-
Name string
11-
Rule config.Rule
10+
Name string
11+
Rule config.Rule
12+
Weight int
1213
}
1314

1415
func (cr CheckResult) LogValue() slog.Value {
1516
return slog.GroupValue(
1617
slog.String("name", cr.Name),
17-
slog.String("rule", string(cr.Rule)))
18+
slog.String("rule", string(cr.Rule)),
19+
slog.Int("weight", cr.Weight),
20+
)
1821
}

lib/policy/config/config.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,20 +39,22 @@ const (
3939
RuleAllow Rule = "ALLOW"
4040
RuleDeny Rule = "DENY"
4141
RuleChallenge Rule = "CHALLENGE"
42+
RuleWeigh Rule = "WEIGH"
4243
RuleBenchmark Rule = "DEBUG_BENCHMARK"
4344
)
4445

4546
const DefaultAlgorithm = "fast"
4647

4748
type BotConfig struct {
48-
UserAgentRegex *string `json:"user_agent_regex"`
49-
PathRegex *string `json:"path_regex"`
50-
HeadersRegex map[string]string `json:"headers_regex"`
51-
Expression *ExpressionOrList `json:"expression"`
49+
UserAgentRegex *string `json:"user_agent_regex,omitempty"`
50+
PathRegex *string `json:"path_regex,omitempty"`
51+
HeadersRegex map[string]string `json:"headers_regex,omitempty"`
52+
Expression *ExpressionOrList `json:"expression,omitempty"`
5253
Challenge *ChallengeRules `json:"challenge,omitempty"`
54+
Weight *Weight `json:"weight,omitempty"`
5355
Name string `json:"name"`
5456
Action Rule `json:"action"`
55-
RemoteAddr []string `json:"remote_addresses"`
57+
RemoteAddr []string `json:"remote_addresses,omitempty"`
5658
}
5759

5860
func (b BotConfig) Zero() bool {
@@ -144,7 +146,7 @@ func (b BotConfig) Valid() error {
144146
}
145147

146148
switch b.Action {
147-
case RuleAllow, RuleBenchmark, RuleChallenge, RuleDeny:
149+
case RuleAllow, RuleBenchmark, RuleChallenge, RuleDeny, RuleWeigh:
148150
// okay
149151
default:
150152
errs = append(errs, fmt.Errorf("%w: %q", ErrUnknownAction, b.Action))
@@ -156,6 +158,10 @@ func (b BotConfig) Valid() error {
156158
}
157159
}
158160

161+
if b.Action == RuleWeigh && b.Weight == nil {
162+
b.Weight = &Weight{Adjust: 5}
163+
}
164+
159165
if len(errs) != 0 {
160166
return fmt.Errorf("config: bot entry for %q is not valid:\n%w", b.Name, errors.Join(errs...))
161167
}

lib/policy/config/config_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,25 @@ func TestBotValid(t *testing.T) {
168168
},
169169
err: nil,
170170
},
171+
{
172+
name: "weight rule without weight",
173+
bot: BotConfig{
174+
Name: "weight-adjust-if-mozilla",
175+
Action: RuleWeigh,
176+
UserAgentRegex: p("Mozilla"),
177+
},
178+
},
179+
{
180+
name: "weight rule with weight adjust",
181+
bot: BotConfig{
182+
Name: "weight-adjust-if-mozilla",
183+
Action: RuleWeigh,
184+
UserAgentRegex: p("Mozilla"),
185+
Weight: &Weight{
186+
Adjust: 5,
187+
},
188+
},
189+
},
171190
}
172191

173192
for _, cs := range tests {

lib/policy/config/expressionorlist.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ var (
1414

1515
type ExpressionOrList struct {
1616
Expression string `json:"-"`
17-
All []string `json:"all"`
18-
Any []string `json:"any"`
17+
All []string `json:"all,omitempty"`
18+
Any []string `json:"any,omitempty"`
1919
}
2020

2121
func (eol ExpressionOrList) Equal(rhs *ExpressionOrList) bool {
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
bots:
2+
- name: simple-weight-adjust
3+
action: WEIGH
4+
user_agent_regex: Mozilla
5+
weight:
6+
adjust: 5
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
bots:
2+
- name: weight
3+
action: WEIGH
4+
user_agent_regex: Mozilla

lib/policy/config/weight.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package config
2+
3+
type Weight struct {
4+
Adjust int `json:"adjust"`
5+
}

0 commit comments

Comments
 (0)