Allow filtering by remote addresses (#52)
* Added the possibility to define rules for remote addresses * Added change in changelog * Added check for X-Real-Ip and X-Forwarded-For when checking for remote address filtering * cmd/anubis: refine IP filtering logic * Optimize the configuration so that the IP trie is created once at application start instead of dynamically being created every request. * Document the changes in the changelog and docs site. * Allow pure IP range filtering. * Allow user agent based IP range filtering. * Allow path based IP range filtering. * Create --debug-x-real-ip-default flag for testing Anubis locally without a HTTP load balancer. --------- Co-authored-by: Xe Iaso <me@xeiaso.net>
This commit is contained in:
parent
e7b9b17b92
commit
d6d879133e
11 changed files with 554 additions and 27 deletions
|
|
@ -5,13 +5,16 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"regexp"
|
||||
|
||||
"github.com/TecharoHQ/anubis/cmd/anubis/internal/config"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"github.com/yl2chen/cidranger"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -32,8 +35,9 @@ type Bot struct {
|
|||
Name string
|
||||
UserAgent *regexp.Regexp
|
||||
Path *regexp.Regexp
|
||||
Action config.Rule
|
||||
Action config.Rule `json:"action"`
|
||||
Challenge *config.ChallengeRules
|
||||
Ranger cidranger.Ranger
|
||||
}
|
||||
|
||||
func (b Bot) Hash() (string, error) {
|
||||
|
|
@ -77,6 +81,19 @@ func parseConfig(fin io.Reader, fname string, defaultDifficulty int) (*ParsedCon
|
|||
Action: b.Action,
|
||||
}
|
||||
|
||||
if b.RemoteAddr != nil && len(b.RemoteAddr) > 0 {
|
||||
parsedBot.Ranger = cidranger.NewPCTrieRanger()
|
||||
|
||||
for _, cidr := range b.RemoteAddr {
|
||||
_, rng, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[unexpected] range %s not parsing: %w", cidr, err)
|
||||
}
|
||||
|
||||
parsedBot.Ranger.Insert(cidranger.NewBasicRangerEntry(*rng))
|
||||
}
|
||||
}
|
||||
|
||||
if b.UserAgentRegex != nil {
|
||||
userAgent, err := regexp.Compile(*b.UserAgentRegex)
|
||||
if err != nil {
|
||||
|
|
@ -140,18 +157,47 @@ func cr(name string, rule config.Rule) CheckResult {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *Server) checkRemoteAddress(b Bot, addr net.IP) bool {
|
||||
if b.Ranger == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
ok, err := b.Ranger.Contains(addr)
|
||||
if err != nil {
|
||||
log.Panicf("[unexpected] something very funky is going on, %q does not have a calculable network number: %v", addr.String(), err)
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// Check evaluates the list of rules, and returns the result
|
||||
func (s *Server) check(r *http.Request) (CheckResult, *Bot) {
|
||||
func (s *Server) check(r *http.Request) (CheckResult, *Bot, error) {
|
||||
host := r.Header.Get("X-Real-Ip")
|
||||
if host == "" {
|
||||
return zilch[CheckResult](), nil, fmt.Errorf("[misconfiguration] X-Real-Ip header is not set")
|
||||
}
|
||||
|
||||
addr := net.ParseIP(host)
|
||||
if addr == nil {
|
||||
return zilch[CheckResult](), nil, fmt.Errorf("[misconfiguration] %q is not an IP address", host)
|
||||
}
|
||||
|
||||
for _, b := range s.policy.Bots {
|
||||
if b.UserAgent != nil {
|
||||
if b.UserAgent.MatchString(r.UserAgent()) {
|
||||
return cr("bot/"+b.Name, b.Action), &b
|
||||
if uaMatch := b.UserAgent.MatchString(r.UserAgent()); uaMatch || (uaMatch && s.checkRemoteAddress(b, addr)) {
|
||||
return cr("bot/"+b.Name, b.Action), &b, nil
|
||||
}
|
||||
}
|
||||
|
||||
if b.Path != nil {
|
||||
if b.Path.MatchString(r.URL.Path) {
|
||||
return cr("bot/"+b.Name, b.Action), &b
|
||||
if pathMatch := b.Path.MatchString(r.URL.Path); pathMatch || (pathMatch && s.checkRemoteAddress(b, addr)) {
|
||||
return cr("bot/"+b.Name, b.Action), &b, nil
|
||||
}
|
||||
}
|
||||
|
||||
if b.Ranger != nil {
|
||||
if s.checkRemoteAddress(b, addr) {
|
||||
return cr("bot/"+b.Name, b.Action), &b, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -162,5 +208,5 @@ func (s *Server) check(r *http.Request) (CheckResult, *Bot) {
|
|||
ReportAs: defaultDifficulty,
|
||||
Algorithm: config.AlgorithmFast,
|
||||
},
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue