From 178c60cf72225c075e230cfa5e7d373683ead229 Mon Sep 17 00:00:00 2001 From: Xe Iaso Date: Fri, 25 Jul 2025 18:45:40 +0000 Subject: [PATCH] refactor: raise checker to be a subpackage of lib Signed-off-by: Xe Iaso --- lib/anubis.go | 5 +- lib/{policy => }/checker/checker.go | 10 ++- lib/{policy => }/checker/registry.go | 5 +- .../checker/remoteaddress/remoteaddress.go | 39 ++++++--- .../remoteaddress/remoteaddress_test.go | 84 +------------------ .../testdata/invalid_bad_cidr.json | 0 .../testdata/invalid_no_cidr.json | 0 .../testdata/invalid_not_json.json | 0 .../testdata/valid_addresses.json | 0 lib/policy/bot.go | 4 +- lib/policy/checker.go | 14 ++-- lib/policy/checker_test.go | 8 +- lib/policy/policy.go | 2 +- lib/thoth/asnchecker.go | 4 +- lib/thoth/asnchecker_test.go | 4 +- lib/thoth/geoipchecker.go | 4 +- lib/thoth/geoipchecker_test.go | 4 +- 17 files changed, 68 insertions(+), 119 deletions(-) rename lib/{policy => }/checker/checker.go (75%) rename lib/{policy => }/checker/registry.go (84%) rename lib/{policy => }/checker/remoteaddress/remoteaddress.go (60%) rename lib/{policy => }/checker/remoteaddress/remoteaddress_test.go (55%) rename lib/{policy => }/checker/remoteaddress/testdata/invalid_bad_cidr.json (100%) rename lib/{policy => }/checker/remoteaddress/testdata/invalid_no_cidr.json (100%) rename lib/{policy => }/checker/remoteaddress/testdata/invalid_not_json.json (100%) rename lib/{policy => }/checker/remoteaddress/testdata/valid_addresses.json (100%) diff --git a/lib/anubis.go b/lib/anubis.go index 123b517e..e375a212 100644 --- a/lib/anubis.go +++ b/lib/anubis.go @@ -28,12 +28,15 @@ import ( "github.com/TecharoHQ/anubis/internal/dnsbl" "github.com/TecharoHQ/anubis/internal/ogtags" "github.com/TecharoHQ/anubis/lib/challenge" + "github.com/TecharoHQ/anubis/lib/checker" "github.com/TecharoHQ/anubis/lib/localization" "github.com/TecharoHQ/anubis/lib/policy" - "github.com/TecharoHQ/anubis/lib/policy/checker" "github.com/TecharoHQ/anubis/lib/policy/config" "github.com/TecharoHQ/anubis/lib/store" + // checker implementations + _ "github.com/TecharoHQ/anubis/lib/checker/all" + // challenge implementations _ "github.com/TecharoHQ/anubis/lib/challenge/metarefresh" _ "github.com/TecharoHQ/anubis/lib/challenge/proofofwork" diff --git a/lib/policy/checker/checker.go b/lib/checker/checker.go similarity index 75% rename from lib/policy/checker/checker.go rename to lib/checker/checker.go index dcb1c0c0..21922f8f 100644 --- a/lib/policy/checker/checker.go +++ b/lib/checker/checker.go @@ -2,6 +2,7 @@ package checker import ( + "errors" "fmt" "net/http" "strings" @@ -9,12 +10,17 @@ import ( "github.com/TecharoHQ/anubis/internal" ) -type Impl interface { +var ( + ErrUnparseableConfig = errors.New("checker: config is unparseable") + ErrInvalidConfig = errors.New("checker: config is invalid") +) + +type Interface interface { Check(*http.Request) (matches bool, err error) Hash() string } -type List []Impl +type List []Interface func (l List) Check(r *http.Request) (bool, error) { for _, c := range l { diff --git a/lib/policy/checker/registry.go b/lib/checker/registry.go similarity index 84% rename from lib/policy/checker/registry.go rename to lib/checker/registry.go index d618c06d..384a99d0 100644 --- a/lib/policy/checker/registry.go +++ b/lib/checker/registry.go @@ -1,14 +1,15 @@ package checker import ( + "context" "encoding/json" "sort" "sync" ) type Factory interface { - ValidateConfig(json.RawMessage) error - Create(json.RawMessage) (Impl, error) + Build(context.Context, json.RawMessage) (Interface, error) + Valid(context.Context, json.RawMessage) error } var ( diff --git a/lib/policy/checker/remoteaddress/remoteaddress.go b/lib/checker/remoteaddress/remoteaddress.go similarity index 60% rename from lib/policy/checker/remoteaddress/remoteaddress.go rename to lib/checker/remoteaddress/remoteaddress.go index b05d68dd..836af27c 100644 --- a/lib/policy/checker/remoteaddress/remoteaddress.go +++ b/lib/checker/remoteaddress/remoteaddress.go @@ -1,31 +1,34 @@ package remoteaddress import ( + "context" "encoding/json" "errors" "fmt" "net/http" "net/netip" + "github.com/TecharoHQ/anubis" "github.com/TecharoHQ/anubis/internal" - "github.com/TecharoHQ/anubis/lib/policy" - "github.com/TecharoHQ/anubis/lib/policy/checker" - "github.com/TecharoHQ/anubis/lib/policy/config" + "github.com/TecharoHQ/anubis/lib/checker" "github.com/gaissmai/bart" ) var ( ErrNoRemoteAddresses = errors.New("remoteaddress: no remote addresses defined") + ErrInvalidCIDR = errors.New("remoteaddress: invalid CIDR") ) -func init() {} +func init() { + checker.Register("remote_address", Factory{}) +} type Factory struct{} -func (Factory) ValidateConfig(inp json.RawMessage) error { +func (Factory) Valid(_ context.Context, inp json.RawMessage) error { var fc fileConfig if err := json.Unmarshal([]byte(inp), &fc); err != nil { - return fmt.Errorf("%w: %w", config.ErrUnparseableConfig, err) + return fmt.Errorf("%w: %w", checker.ErrUnparseableConfig, err) } if err := fc.Valid(); err != nil { @@ -35,13 +38,13 @@ func (Factory) ValidateConfig(inp json.RawMessage) error { return nil } -func (Factory) Create(inp json.RawMessage) (checker.Impl, error) { +func (Factory) Build(_ context.Context, inp json.RawMessage) (checker.Interface, error) { c := struct { RemoteAddr []netip.Prefix `json:"remote_addresses,omitempty" yaml:"remote_addresses,omitempty"` }{} if err := json.Unmarshal([]byte(inp), &c); err != nil { - return nil, fmt.Errorf("%w: %w", config.ErrUnparseableConfig, err) + return nil, fmt.Errorf("%w: %w", checker.ErrUnparseableConfig, err) } table := new(bart.Lite) @@ -69,17 +72,29 @@ func (fc fileConfig) Valid() error { for _, cidr := range fc.RemoteAddr { if _, err := netip.ParsePrefix(cidr); err != nil { - errs = append(errs, fmt.Errorf("%w: cidr %q is invalid: %w", config.ErrInvalidCIDR, cidr, err)) + errs = append(errs, fmt.Errorf("%w: cidr %q is invalid: %w", ErrInvalidCIDR, cidr, err)) } } if len(errs) != 0 { - return fmt.Errorf("%w: %w", policy.ErrMisconfiguration, errors.Join(errs...)) + return fmt.Errorf("%w: %w", checker.ErrInvalidConfig, errors.Join(errs...)) } return nil } +func New(cidrs []string) (checker.Interface, error) { + fc := fileConfig{ + RemoteAddr: cidrs, + } + data, err := json.Marshal(fc) + if err != nil { + return nil, err + } + + return Factory{}.Build(context.Background(), json.RawMessage(data)) +} + type RemoteAddrChecker struct { prefixTable *bart.Lite hash string @@ -88,12 +103,12 @@ type RemoteAddrChecker struct { func (rac *RemoteAddrChecker) Check(r *http.Request) (bool, error) { host := r.Header.Get("X-Real-Ip") if host == "" { - return false, fmt.Errorf("%w: header X-Real-Ip is not set", policy.ErrMisconfiguration) + return false, fmt.Errorf("%w: header X-Real-Ip is not set", anubis.ErrMisconfiguration) } addr, err := netip.ParseAddr(host) if err != nil { - return false, fmt.Errorf("%w: %s is not an IP address: %w", policy.ErrMisconfiguration, host, err) + return false, fmt.Errorf("%w: %s is not an IP address: %w", anubis.ErrMisconfiguration, host, err) } return rac.prefixTable.Contains(addr), nil diff --git a/lib/policy/checker/remoteaddress/remoteaddress_test.go b/lib/checker/remoteaddress/remoteaddress_test.go similarity index 55% rename from lib/policy/checker/remoteaddress/remoteaddress_test.go rename to lib/checker/remoteaddress/remoteaddress_test.go index f3bb9a3e..7fdc0f37 100644 --- a/lib/policy/checker/remoteaddress/remoteaddress_test.go +++ b/lib/checker/remoteaddress/remoteaddress_test.go @@ -7,7 +7,7 @@ import ( "net/http" "testing" - "github.com/TecharoHQ/anubis/lib/policy/checker" + "github.com/TecharoHQ/anubis/lib/checker" "github.com/TecharoHQ/anubis/lib/policy/config" ) @@ -58,7 +58,7 @@ func TestFactoryValidateConfig(t *testing.T) { t.Run(tt.name, func(t *testing.T) { data := json.RawMessage(tt.data) - if err := f.ValidateConfig(data); !errors.Is(err, tt.err) { + if err := f.Valid(t.Context(), data); !errors.Is(err, tt.err) { t.Logf("want: %v", tt.err) t.Logf("got: %v", err) t.Fatal("validation didn't do what was expected") @@ -100,7 +100,7 @@ func TestFactoryCreate(t *testing.T) { t.Run(tt.name, func(t *testing.T) { data := json.RawMessage(tt.data) - impl, err := f.Create(data) + impl, err := f.Build(t.Context(), data) if !errors.Is(err, tt.err) { t.Logf("want: %v", tt.err) t.Logf("got: %v", err) @@ -136,81 +136,3 @@ func TestFactoryCreate(t *testing.T) { }) } } - -// func TestRemoteAddrChecker(t *testing.T) { -// for _, tt := range []struct { -// err error -// name string -// ip string -// cidrs []string -// ok bool -// }{ -// { -// name: "match_ipv4", -// cidrs: []string{"0.0.0.0/0"}, -// ip: "1.1.1.1", -// ok: true, -// err: nil, -// }, -// { -// name: "match_ipv6", -// cidrs: []string{"::/0"}, -// ip: "cafe:babe::", -// ok: true, -// err: nil, -// }, -// { -// name: "not_match_ipv4", -// cidrs: []string{"1.1.1.1/32"}, -// ip: "1.1.1.2", -// ok: false, -// err: nil, -// }, -// { -// name: "not_match_ipv6", -// cidrs: []string{"cafe:babe::/128"}, -// ip: "cafe:babe:4::/128", -// ok: false, -// err: nil, -// }, -// { -// name: "no_ip_set", -// cidrs: []string{"::/0"}, -// ok: false, -// err: policy.ErrMisconfiguration, -// }, -// { -// name: "invalid_ip", -// cidrs: []string{"::/0"}, -// ip: "According to all natural laws of aviation", -// ok: false, -// err: policy.ErrMisconfiguration, -// }, -// } { -// t.Run(tt.name, func(t *testing.T) { -// rac, err := NewRemoteAddrChecker(tt.cidrs) -// if err != nil && !errors.Is(err, tt.err) { -// t.Fatalf("creating RemoteAddrChecker failed: %v", err) -// } - -// r, err := http.NewRequest(http.MethodGet, "/", nil) -// if err != nil { -// t.Fatalf("can't make request: %v", err) -// } - -// if tt.ip != "" { -// r.Header.Add("X-Real-Ip", tt.ip) -// } - -// ok, err := rac.Check(r) - -// if tt.ok != ok { -// t.Errorf("ok: %v, wanted: %v", ok, tt.ok) -// } - -// if err != nil && tt.err != nil && !errors.Is(err, tt.err) { -// t.Errorf("err: %v, wanted: %v", err, tt.err) -// } -// }) -// } -// } diff --git a/lib/policy/checker/remoteaddress/testdata/invalid_bad_cidr.json b/lib/checker/remoteaddress/testdata/invalid_bad_cidr.json similarity index 100% rename from lib/policy/checker/remoteaddress/testdata/invalid_bad_cidr.json rename to lib/checker/remoteaddress/testdata/invalid_bad_cidr.json diff --git a/lib/policy/checker/remoteaddress/testdata/invalid_no_cidr.json b/lib/checker/remoteaddress/testdata/invalid_no_cidr.json similarity index 100% rename from lib/policy/checker/remoteaddress/testdata/invalid_no_cidr.json rename to lib/checker/remoteaddress/testdata/invalid_no_cidr.json diff --git a/lib/policy/checker/remoteaddress/testdata/invalid_not_json.json b/lib/checker/remoteaddress/testdata/invalid_not_json.json similarity index 100% rename from lib/policy/checker/remoteaddress/testdata/invalid_not_json.json rename to lib/checker/remoteaddress/testdata/invalid_not_json.json diff --git a/lib/policy/checker/remoteaddress/testdata/valid_addresses.json b/lib/checker/remoteaddress/testdata/valid_addresses.json similarity index 100% rename from lib/policy/checker/remoteaddress/testdata/valid_addresses.json rename to lib/checker/remoteaddress/testdata/valid_addresses.json diff --git a/lib/policy/bot.go b/lib/policy/bot.go index 479bccc3..12661abe 100644 --- a/lib/policy/bot.go +++ b/lib/policy/bot.go @@ -4,12 +4,12 @@ import ( "fmt" "github.com/TecharoHQ/anubis/internal" - "github.com/TecharoHQ/anubis/lib/policy/checker" + "github.com/TecharoHQ/anubis/lib/checker" "github.com/TecharoHQ/anubis/lib/policy/config" ) type Bot struct { - Rules checker.Impl + Rules checker.Interface Challenge *config.ChallengeRules Weight *config.Weight Name string diff --git a/lib/policy/checker.go b/lib/policy/checker.go index 8c6119f1..5422872f 100644 --- a/lib/policy/checker.go +++ b/lib/policy/checker.go @@ -10,7 +10,7 @@ import ( "github.com/TecharoHQ/anubis" "github.com/TecharoHQ/anubis/internal" - "github.com/TecharoHQ/anubis/lib/policy/checker" + "github.com/TecharoHQ/anubis/lib/checker" "github.com/gaissmai/bart" ) @@ -19,7 +19,7 @@ type RemoteAddrChecker struct { hash string } -func NewRemoteAddrChecker(cidrs []string) (checker.Impl, error) { +func NewRemoteAddrChecker(cidrs []string) (checker.Interface, error) { table := new(bart.Lite) for _, cidr := range cidrs { @@ -61,11 +61,11 @@ type HeaderMatchesChecker struct { hash string } -func NewUserAgentChecker(rexStr string) (checker.Impl, error) { +func NewUserAgentChecker(rexStr string) (checker.Interface, error) { return NewHeaderMatchesChecker("User-Agent", rexStr) } -func NewHeaderMatchesChecker(header, rexStr string) (checker.Impl, error) { +func NewHeaderMatchesChecker(header, rexStr string) (checker.Interface, error) { rex, err := regexp.Compile(strings.TrimSpace(rexStr)) if err != nil { return nil, fmt.Errorf("%w: regex %s failed parse: %w", anubis.ErrMisconfiguration, rexStr, err) @@ -90,7 +90,7 @@ type PathChecker struct { hash string } -func NewPathChecker(rexStr string) (checker.Impl, error) { +func NewPathChecker(rexStr string) (checker.Interface, error) { rex, err := regexp.Compile(strings.TrimSpace(rexStr)) if err != nil { return nil, fmt.Errorf("%w: regex %s failed parse: %w", anubis.ErrMisconfiguration, rexStr, err) @@ -110,7 +110,7 @@ func (pc *PathChecker) Hash() string { return pc.hash } -func NewHeaderExistsChecker(key string) checker.Impl { +func NewHeaderExistsChecker(key string) checker.Interface { return headerExistsChecker{strings.TrimSpace(key)} } @@ -130,7 +130,7 @@ func (hec headerExistsChecker) Hash() string { return internal.FastHash(hec.header) } -func NewHeadersChecker(headermap map[string]string) (checker.Impl, error) { +func NewHeadersChecker(headermap map[string]string) (checker.Interface, error) { var result checker.List var errs []error diff --git a/lib/policy/checker_test.go b/lib/policy/checker_test.go index 6109babe..2414ccf5 100644 --- a/lib/policy/checker_test.go +++ b/lib/policy/checker_test.go @@ -4,6 +4,8 @@ import ( "errors" "net/http" "testing" + + "github.com/TecharoHQ/anubis" ) func TestRemoteAddrChecker(t *testing.T) { @@ -46,14 +48,14 @@ func TestRemoteAddrChecker(t *testing.T) { name: "no_ip_set", cidrs: []string{"::/0"}, ok: false, - err: ErrMisconfiguration, + err: anubis.ErrMisconfiguration, }, { name: "invalid_ip", cidrs: []string{"::/0"}, ip: "According to all natural laws of aviation", ok: false, - err: ErrMisconfiguration, + err: anubis.ErrMisconfiguration, }, } { t.Run(tt.name, func(t *testing.T) { @@ -124,7 +126,7 @@ func TestHeaderMatchesChecker(t *testing.T) { { name: "invalid_regex", rexStr: "a(b", - err: ErrMisconfiguration, + err: anubis.ErrMisconfiguration, }, } { t.Run(tt.name, func(t *testing.T) { diff --git a/lib/policy/policy.go b/lib/policy/policy.go index 3dc3157e..0dfd2724 100644 --- a/lib/policy/policy.go +++ b/lib/policy/policy.go @@ -8,7 +8,7 @@ import ( "log/slog" "sync/atomic" - "github.com/TecharoHQ/anubis/lib/policy/checker" + "github.com/TecharoHQ/anubis/lib/checker" "github.com/TecharoHQ/anubis/lib/policy/config" "github.com/TecharoHQ/anubis/lib/store" "github.com/TecharoHQ/anubis/lib/thoth" diff --git a/lib/thoth/asnchecker.go b/lib/thoth/asnchecker.go index 548765c2..b000c0b7 100644 --- a/lib/thoth/asnchecker.go +++ b/lib/thoth/asnchecker.go @@ -10,11 +10,11 @@ import ( "time" "github.com/TecharoHQ/anubis/internal" - "github.com/TecharoHQ/anubis/lib/policy/checker" + "github.com/TecharoHQ/anubis/lib/checker" iptoasnv1 "github.com/TecharoHQ/thoth-proto/gen/techaro/thoth/iptoasn/v1" ) -func (c *Client) ASNCheckerFor(asns []uint32) checker.Impl { +func (c *Client) ASNCheckerFor(asns []uint32) checker.Interface { asnMap := map[uint32]struct{}{} var sb strings.Builder fmt.Fprintln(&sb, "ASNChecker") diff --git a/lib/thoth/asnchecker_test.go b/lib/thoth/asnchecker_test.go index 787cdb42..fc189ce7 100644 --- a/lib/thoth/asnchecker_test.go +++ b/lib/thoth/asnchecker_test.go @@ -5,12 +5,12 @@ import ( "net/http/httptest" "testing" - "github.com/TecharoHQ/anubis/lib/policy/checker" + "github.com/TecharoHQ/anubis/lib/checker" "github.com/TecharoHQ/anubis/lib/thoth" iptoasnv1 "github.com/TecharoHQ/thoth-proto/gen/techaro/thoth/iptoasn/v1" ) -var _ checker.Impl = &thoth.ASNChecker{} +var _ checker.Interface = &thoth.ASNChecker{} func TestASNChecker(t *testing.T) { cli := loadSecrets(t) diff --git a/lib/thoth/geoipchecker.go b/lib/thoth/geoipchecker.go index ef6dcb88..ca299217 100644 --- a/lib/thoth/geoipchecker.go +++ b/lib/thoth/geoipchecker.go @@ -9,11 +9,11 @@ import ( "strings" "time" - "github.com/TecharoHQ/anubis/lib/policy/checker" + "github.com/TecharoHQ/anubis/lib/checker" iptoasnv1 "github.com/TecharoHQ/thoth-proto/gen/techaro/thoth/iptoasn/v1" ) -func (c *Client) GeoIPCheckerFor(countries []string) checker.Impl { +func (c *Client) GeoIPCheckerFor(countries []string) checker.Interface { countryMap := map[string]struct{}{} var sb strings.Builder fmt.Fprintln(&sb, "GeoIPChecker") diff --git a/lib/thoth/geoipchecker_test.go b/lib/thoth/geoipchecker_test.go index 9826282f..a88806fe 100644 --- a/lib/thoth/geoipchecker_test.go +++ b/lib/thoth/geoipchecker_test.go @@ -5,11 +5,11 @@ import ( "net/http/httptest" "testing" - "github.com/TecharoHQ/anubis/lib/policy/checker" + "github.com/TecharoHQ/anubis/lib/checker" "github.com/TecharoHQ/anubis/lib/thoth" ) -var _ checker.Impl = &thoth.GeoIPChecker{} +var _ checker.Interface = &thoth.GeoIPChecker{} func TestGeoIPChecker(t *testing.T) { cli := loadSecrets(t)