Compare commits

...

3 Commits

Author SHA1 Message Date
Xe Iaso
e0c4fcc398 Merge branch 'main' into Xe/valkey-2 2025-06-11 17:08:40 -04:00
Xe Iaso
9d05ea6430 chore: spelling
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-06-09 17:19:42 -04:00
Xe Iaso
21ee6e3406 feat(lib): valkey backed store
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-06-09 17:19:21 -04:00
8 changed files with 137 additions and 0 deletions

View File

@@ -199,6 +199,7 @@ qwant
qwantbot
rac
rcvar
rdb
redir
redirectscheme
relayd
@@ -252,6 +253,7 @@ unmarshal
uuidgen
uvx
UXP
valkey
Varis
Velen
vendored

View File

@@ -67,6 +67,7 @@ var (
ogCacheConsiderHost = flag.Bool("og-cache-consider-host", false, "enable or disable the use of the host in the Open Graph tag cache")
extractResources = flag.String("extract-resources", "", "if set, extract the static resources to the specified folder")
webmasterEmail = flag.String("webmaster-email", "", "if set, displays webmaster's email on the reject page for appeals")
valkeyURL = flag.String("valkey-url", "", "Valkey URL for Anubis' state layer")
versionFlag = flag.Bool("version", false, "print Anubis version")
xffStripPrivate = flag.Bool("xff-strip-private", true, "if set, strip private addresses from X-Forwarded-For")
)

2
go.mod
View File

@@ -9,6 +9,7 @@ require (
github.com/google/cel-go v0.25.0
github.com/playwright-community/playwright-go v0.5200.0
github.com/prometheus/client_golang v1.22.0
github.com/redis/go-redis/v9 v9.9.0
github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a
github.com/yl2chen/cidranger v1.0.2
golang.org/x/net v0.41.0
@@ -41,6 +42,7 @@ require (
github.com/cloudflare/circl v1.6.1 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/deckarep/golang-set/v2 v2.8.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dlclark/regexp2 v1.11.4 // indirect
github.com/dop251/goja v0.0.0-20250309171923-bcd7cc6bf64c // indirect
github.com/emirpasic/gods v1.18.1 // indirect

8
go.sum
View File

@@ -46,6 +46,10 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/caarlos0/testfs v0.4.4 h1:3PHvzHi5Lt+g332CiShwS8ogTgS3HjrmzZxCm6JCDr8=
github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk=
github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM=
@@ -75,6 +79,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deckarep/golang-set/v2 v2.8.0 h1:swm0rlPCmdWn9mESxKOjWk8hXSqoxOp+ZlfuyaAdFlQ=
github.com/deckarep/golang-set/v2 v2.8.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dop251/goja v0.0.0-20250309171923-bcd7cc6bf64c h1:mxWGS0YyquJ/ikZOjSrRjjFIbUqIP9ojyYQ+QZTU3Rg=
@@ -228,6 +234,8 @@ github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/redis/go-redis/v9 v9.9.0 h1:URbPQ4xVQSQhZ27WMQVmZSo3uT3pL+4IdHVcYq2nVfM=
github.com/redis/go-redis/v9 v9.9.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=

10
internal/store/store.go Normal file
View File

@@ -0,0 +1,10 @@
package store
import "context"
type Impl interface {
GetInt(ctx context.Context, segments []string) (int, error)
MultiGetInt(ctx context.Context, segments [][]string) ([]int, error)
Increment(ctx context.Context, segments []string) error
}

View File

@@ -0,0 +1,91 @@
package valkey
import (
"context"
"errors"
"fmt"
"strconv"
"strings"
"github.com/TecharoHQ/anubis/internal/store"
valkey "github.com/redis/go-redis/v9"
)
var (
_ store.Impl = &Store{}
)
type Store struct {
rdb *valkey.Client
}
func New(rdb *valkey.Client) *Store {
return &Store{rdb: rdb}
}
func (s *Store) Increment(ctx context.Context, segments []string) error {
key := fmt.Sprintf("anubis:%s", strings.Join(segments, ":"))
if err := s.rdb.Incr(ctx, key).Err(); err != nil {
return err
}
return nil
}
func (s *Store) GetInt(ctx context.Context, segments []string) (int, error) {
key := fmt.Sprintf("anubis:%s", strings.Join(segments, ":"))
numStr, err := s.rdb.Get(ctx, key).Result()
if err != nil {
return 0, err
}
num, err := strconv.Atoi(numStr)
if err != nil {
return 0, err
}
return num, nil
}
func (s *Store) MultiGetInt(ctx context.Context, segments [][]string) ([]int, error) {
var keys []string
for _, segment := range segments {
key := fmt.Sprintf("anubis:%s", strings.Join(segment, ":"))
keys = append(keys, key)
}
values, err := s.rdb.MGet(ctx, keys...).Result()
if err != nil {
return nil, err
}
var errs []error
result := make([]int, len(values))
for i, val := range values {
if val == nil {
result[i] = 0
errs = append(errs, fmt.Errorf("can't get key %s: value is null", keys[i]))
continue
}
switch v := val.(type) {
case string:
num, err := strconv.Atoi(v)
if err != nil {
errs = append(errs, fmt.Errorf("can't parse key %s: %w", keys[i], err))
continue
}
result[i] = num
default:
errs = append(errs, fmt.Errorf("can't parse key %s: wanted type string but got type %T", keys[i], val))
}
}
if len(errs) != 0 {
return nil, fmt.Errorf("can't read from valkey: %w", errors.Join(errs...))
}
return result, nil
}

View File

@@ -24,6 +24,7 @@ import (
"github.com/TecharoHQ/anubis/internal"
"github.com/TecharoHQ/anubis/internal/dnsbl"
"github.com/TecharoHQ/anubis/internal/ogtags"
"github.com/TecharoHQ/anubis/internal/store"
"github.com/TecharoHQ/anubis/lib/challenge"
"github.com/TecharoHQ/anubis/lib/policy"
"github.com/TecharoHQ/anubis/lib/policy/config"
@@ -70,6 +71,7 @@ type Server struct {
priv ed25519.PrivateKey
pub ed25519.PublicKey
opts Options
store store.Impl
}
func (s *Server) challengeFor(r *http.Request, difficulty int) string {

View File

@@ -1,6 +1,7 @@
package lib
import (
"context"
"crypto/ed25519"
"crypto/rand"
"errors"
@@ -18,10 +19,12 @@ import (
"github.com/TecharoHQ/anubis/internal"
"github.com/TecharoHQ/anubis/internal/dnsbl"
"github.com/TecharoHQ/anubis/internal/ogtags"
"github.com/TecharoHQ/anubis/internal/store/valkey"
"github.com/TecharoHQ/anubis/lib/challenge"
"github.com/TecharoHQ/anubis/lib/policy"
"github.com/TecharoHQ/anubis/web"
"github.com/TecharoHQ/anubis/xess"
"github.com/redis/go-redis/v9"
)
type Options struct {
@@ -40,6 +43,7 @@ type Options struct {
OGPassthrough bool
CookiePartitioned bool
ServeRobotsTXT bool
ValkeyURL string
}
func LoadPoliciesOrDefault(fname string, defaultDifficulty int) (*policy.ParsedConfig, error) {
@@ -114,6 +118,23 @@ func New(opts Options) (*Server, error) {
cookieName: cookieName,
}
if opts.ValkeyURL != "" {
vkOpts, err := redis.ParseURL(opts.ValkeyURL)
if err != nil {
return nil, fmt.Errorf("can't parse valkey URL: %q: %w", opts.ValkeyURL, err)
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
cli := redis.NewClient(vkOpts)
if _, err := cli.Ping(ctx).Result(); err != nil {
return nil, fmt.Errorf("can't ping valkey: %w", err)
}
result.store = valkey.New(cli)
}
mux := http.NewServeMux()
xess.Mount(mux)