chore: move checker package to top level

Signed-off-by: Xe Iaso <me@xeiaso.net>
This commit is contained in:
Xe Iaso
2025-07-01 23:58:42 +00:00
parent dc0dde3053
commit 208ceca723
16 changed files with 114 additions and 101 deletions

View File

@@ -4,7 +4,7 @@ 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"
)

View File

@@ -9,7 +9,7 @@ import (
"strings"
"github.com/TecharoHQ/anubis/internal"
"github.com/TecharoHQ/anubis/lib/policy/checker"
"github.com/TecharoHQ/anubis/lib/checker"
"github.com/gaissmai/bart"
)

View File

@@ -1,41 +0,0 @@
// Package checker defines the Checker interface and a helper utility to avoid import cycles.
package checker
import (
"fmt"
"net/http"
"strings"
"github.com/TecharoHQ/anubis/internal"
)
type Impl interface {
Check(*http.Request) (matches bool, err error)
Hash() string
}
type List []Impl
func (l List) Check(r *http.Request) (bool, error) {
for _, c := range l {
ok, err := c.Check(r)
if err != nil {
return ok, err
}
if ok {
return ok, nil
}
}
return false, nil
}
func (l List) Hash() string {
var sb strings.Builder
for _, c := range l {
fmt.Fprintln(&sb, c.Hash())
}
return internal.FastHash(sb.String())
}

View File

@@ -1,42 +0,0 @@
package checker
import (
"encoding/json"
"sort"
"sync"
)
type Factory interface {
ValidateConfig(json.RawMessage) error
Create(json.RawMessage) (Impl, error)
}
var (
registry map[string]Factory = map[string]Factory{}
regLock sync.RWMutex
)
func Register(name string, factory Factory) {
regLock.Lock()
defer regLock.Unlock()
registry[name] = factory
}
func Get(name string) (Factory, bool) {
regLock.RLock()
defer regLock.RUnlock()
result, ok := registry[name]
return result, ok
}
func Methods() []string {
regLock.RLock()
defer regLock.RUnlock()
var result []string
for method := range registry {
result = append(result, method)
}
sort.Strings(result)
return result
}

View File

@@ -1,104 +0,0 @@
package remoteaddress
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/netip"
"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/gaissmai/bart"
)
var (
ErrNoRemoteAddresses = errors.New("remoteaddress: no remote addresses defined")
)
func init() {}
type Factory struct{}
func (Factory) ValidateConfig(inp json.RawMessage) error {
var fc fileConfig
if err := json.Unmarshal([]byte(inp), &fc); err != nil {
return fmt.Errorf("%w: %w", config.ErrUnparseableConfig, err)
}
if err := fc.Valid(); err != nil {
return err
}
return nil
}
func (Factory) Create(inp json.RawMessage) (checker.Impl, 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)
}
table := new(bart.Lite)
for _, cidr := range c.RemoteAddr {
table.Insert(cidr)
}
return &RemoteAddrChecker{
prefixTable: table,
hash: internal.FastHash(string(inp)),
}, nil
}
type fileConfig struct {
RemoteAddr []string `json:"remote_addresses,omitempty" yaml:"remote_addresses,omitempty"`
}
func (fc fileConfig) Valid() error {
var errs []error
if len(fc.RemoteAddr) == 0 {
errs = append(errs, ErrNoRemoteAddresses)
}
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))
}
}
if len(errs) != 0 {
return fmt.Errorf("%w: %w", policy.ErrMisconfiguration, errors.Join(errs...))
}
return nil
}
type RemoteAddrChecker struct {
prefixTable *bart.Lite
hash string
}
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)
}
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 rac.prefixTable.Contains(addr), nil
}
func (rac *RemoteAddrChecker) Hash() string {
return rac.hash
}

View File

@@ -1,216 +0,0 @@
package remoteaddress
import (
_ "embed"
"encoding/json"
"errors"
"net/http"
"testing"
"github.com/TecharoHQ/anubis/lib/policy/checker"
"github.com/TecharoHQ/anubis/lib/policy/config"
)
func TestFactoryIsCheckerFactory(t *testing.T) {
if _, ok := (any(Factory{})).(checker.Factory); !ok {
t.Fatal("Factory is not an instance of checker.Factory")
}
}
func TestFactoryValidateConfig(t *testing.T) {
f := Factory{}
for _, tt := range []struct {
name string
data []byte
err error
}{
{
name: "basic valid",
data: []byte(`{
"remote_addresses": [
"1.1.1.1/32"
]
}`),
},
{
name: "not json",
data: []byte(`]`),
err: config.ErrUnparseableConfig,
},
{
name: "no cidr",
data: []byte(`{
"remote_addresses": []
}`),
err: ErrNoRemoteAddresses,
},
{
name: "bad cidr",
data: []byte(`{
"remote_addresses": [
"according to all laws of aviation"
]
}`),
err: config.ErrInvalidCIDR,
},
} {
t.Run(tt.name, func(t *testing.T) {
data := json.RawMessage(tt.data)
if err := f.ValidateConfig(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")
}
})
}
}
func TestFactoryCreate(t *testing.T) {
f := Factory{}
for _, tt := range []struct {
name string
data []byte
err error
ip string
match bool
}{
{
name: "basic valid",
data: []byte(`{
"remote_addresses": [
"1.1.1.1/32"
]
}`),
ip: "1.1.1.1",
match: true,
},
{
name: "bad cidr",
data: []byte(`{
"remote_addresses": [
"according to all laws of aviation"
]
}`),
err: config.ErrUnparseableConfig,
},
} {
t.Run(tt.name, func(t *testing.T) {
data := json.RawMessage(tt.data)
impl, err := f.Create(data)
if !errors.Is(err, tt.err) {
t.Logf("want: %v", tt.err)
t.Logf("got: %v", err)
t.Fatal("creation didn't do what was expected")
}
if tt.err != nil {
return
}
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)
}
match, err := impl.Check(r)
if tt.match != match {
t.Errorf("match: %v, wanted: %v", match, tt.match)
}
if err != nil && tt.err != nil && !errors.Is(err, tt.err) {
t.Errorf("err: %v, wanted: %v", err, tt.err)
}
if impl.Hash() == "" {
t.Error("hash method returns empty string")
}
})
}
}
// 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)
// }
// })
// }
// }

View File

@@ -1,5 +0,0 @@
{
"remote_addresses": [
"according to all laws of aviation"
]
}

View File

@@ -1,3 +0,0 @@
{
"remote_addresses": []
}

View File

@@ -1,5 +0,0 @@
{
"remote_addresses": [
"1.1.1.1/32"
]
}

View File

@@ -9,7 +9,7 @@ import (
"sync/atomic"
"github.com/TecharoHQ/anubis/internal/thoth"
"github.com/TecharoHQ/anubis/lib/policy/checker"
"github.com/TecharoHQ/anubis/lib/checker"
"github.com/TecharoHQ/anubis/lib/policy/config"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"