diff options
| author | Xe Iaso <me@xeiaso.net> | 2025-03-17 01:25:18 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-03-17 01:25:18 -0400 |
| commit | 2859f037cdf0cfd7314768152a2a5ad0ec8cd638 (patch) | |
| tree | 7031b50ed6509b9ce22ff8de7628d2771da33237 | |
| parent | c3f5f1f5463bb8c48dc341e31957a54527f299e9 (diff) | |
| download | x-2859f037cdf0cfd7314768152a2a5ad0ec8cd638.tar.xz x-2859f037cdf0cfd7314768152a2a5ad0ec8cd638.zip | |
cmd/anubis: add rule hashes for admin-configured denials (#696)
* cmd/anubis: add rule hashes for admin-configured denials
Closes #695
Signed-off-by: Xe Iaso <me@xeiaso.net>
* cmd/anubis: remove theoretical nil pointer deference panic
This won't actually happen in real life, but the code paths might change so we should be somewhat defensive.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---------
Signed-off-by: Xe Iaso <me@xeiaso.net>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
| -rw-r--r-- | cmd/anubis/index_templ.go | 2 | ||||
| -rw-r--r-- | cmd/anubis/main.go | 31 | ||||
| -rw-r--r-- | cmd/anubis/policy.go | 21 | ||||
| -rw-r--r-- | cmd/anubis/static/js/main.mjs.gz | bin | 985 -> 985 bytes | |||
| -rw-r--r-- | cmd/anubis/testdata/everything_blocked.json | 10 |
5 files changed, 57 insertions, 7 deletions
diff --git a/cmd/anubis/index_templ.go b/cmd/anubis/index_templ.go index 50d65ef..4b400d1 100644 --- a/cmd/anubis/index_templ.go +++ b/cmd/anubis/index_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.819 +// templ: version: v0.3.833 package main //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/cmd/anubis/main.go b/cmd/anubis/main.go index 69be826..de0a592 100644 --- a/cmd/anubis/main.go +++ b/cmd/anubis/main.go @@ -114,6 +114,21 @@ func main() { log.Fatal(err) } + fmt.Println("Rule error IDs:") + for _, rule := range s.policy.Bots { + if rule.Action != config.RuleDeny { + continue + } + + hash, err := rule.Hash() + if err != nil { + log.Fatalf("can't calculate checksum of rule %s: %v", rule.Name, err) + } + + fmt.Printf("* %s: %s\n", rule.Name, hash) + } + fmt.Println() + mux := http.NewServeMux() xess.Mount(mux) @@ -229,7 +244,7 @@ type Server struct { } func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request) { - cr := s.check(r) + cr, rule := s.check(r) r.Header.Add("X-Anubis-Rule", cr.Name) r.Header.Add("X-Anubis-Action", string(cr.Rule)) lg := slog.With( @@ -272,7 +287,19 @@ func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request) { case config.RuleDeny: clearCookie(w) lg.Info("explicit deny") - templ.Handler(base("Oh noes!", errorPage("Access Denied")), templ.WithStatus(http.StatusOK)).ServeHTTP(w, r) + if rule == nil { + lg.Error("rule is nil, cannot calculate checksum") + templ.Handler(base("Oh noes!", errorPage("Other internal server error (contact the admin)")), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r) + return + } + hash, err := rule.Hash() + if err != nil { + lg.Error("can't calculate checksum of rule", "err", err) + templ.Handler(base("Oh noes!", errorPage("Other internal server error (contact the admin)")), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r) + return + } + lg.Debug("rule hash", "hash", hash) + templ.Handler(base("Oh noes!", errorPage(fmt.Sprintf("Access Denied: error code %s", hash))), templ.WithStatus(http.StatusOK)).ServeHTTP(w, r) return case config.RuleChallenge: lg.Debug("challenge requested") diff --git a/cmd/anubis/policy.go b/cmd/anubis/policy.go index 6e2241e..421bfff 100644 --- a/cmd/anubis/policy.go +++ b/cmd/anubis/policy.go @@ -35,6 +35,19 @@ type Bot struct { Action config.Rule `json:"action"` } +func (b Bot) Hash() (string, error) { + var pathRex string + if b.Path != nil { + pathRex = b.Path.String() + } + var userAgentRex string + if b.UserAgent != nil { + userAgentRex = b.UserAgent.String() + } + + return sha256sum(fmt.Sprintf("%s::%s::%s", b.Name, pathRex, userAgentRex)) +} + func parseConfig(fin io.Reader, fname string) (*ParsedConfig, error) { var c config.Config if err := json.NewDecoder(fin).Decode(&c); err != nil { @@ -110,20 +123,20 @@ func cr(name string, rule config.Rule) CheckResult { } // Check evaluates the list of rules, and returns the result -func (s *Server) check(r *http.Request) CheckResult { +func (s *Server) check(r *http.Request) (CheckResult, *Bot) { for _, b := range s.policy.Bots { if b.UserAgent != nil { if b.UserAgent.MatchString(r.UserAgent()) { - return cr("bot/"+b.Name, b.Action) + return cr("bot/"+b.Name, b.Action), &b } } if b.Path != nil { if b.Path.MatchString(r.URL.Path) { - return cr("bot/"+b.Name, b.Action) + return cr("bot/"+b.Name, b.Action), &b } } } - return cr("default/allow", config.RuleAllow) + return cr("default/allow", config.RuleAllow), nil } diff --git a/cmd/anubis/static/js/main.mjs.gz b/cmd/anubis/static/js/main.mjs.gz Binary files differindex 868c622..c104f77 100644 --- a/cmd/anubis/static/js/main.mjs.gz +++ b/cmd/anubis/static/js/main.mjs.gz diff --git a/cmd/anubis/testdata/everything_blocked.json b/cmd/anubis/testdata/everything_blocked.json new file mode 100644 index 0000000..e1763e4 --- /dev/null +++ b/cmd/anubis/testdata/everything_blocked.json @@ -0,0 +1,10 @@ +{ + "bots": [ + { + "name": "everything", + "user_agent_regex": ".*", + "action": "DENY" + } + ], + "dnsbl": false +}
\ No newline at end of file |
