Compare commits

..

5 Commits

Author SHA1 Message Date
Jason Cameron
33ea6c714f fix: replace checker.NewMapIterator with newMapIterator for HTTPHeaders and URLValues
Signed-off-by: Jason Cameron <jason.cameron@stanwith.me>
2026-02-18 12:47:15 -05:00
Jason Cameron
8e9b641280 fix: implement map iterators for HTTPHeaders and URLValues to resolve CEL internal errors
Signed-off-by: Jason Cameron <jason.cameron@stanwith.me>
2026-02-18 12:46:26 -05:00
Jason Cameron
d21c67f902 test: add unit tests for CELChecker map iteration
Signed-off-by: Jason Cameron <jason.cameron@stanwith.me>
2026-02-18 12:46:26 -05:00
Jason Cameron
19e82973af fix: enable CEL iterators
Signed-off-by: Jason Cameron <jason.cameron@stanwith.me>
2026-02-18 12:46:26 -05:00
Xe Iaso
35b5e78a0d chore: tag v1.25.0
Signed-off-by: Xe Iaso <me@xeiaso.net>
2026-02-18 15:56:28 +00:00
15 changed files with 142 additions and 159 deletions

View File

@@ -24,7 +24,6 @@ jobs:
- i18n
- log-file
- nginx
- haproxy-simple
- palemoon/amd64
#- palemoon/i386
- robots_txt

View File

@@ -1 +1 @@
1.24.0
1.25.0

View File

@@ -11,6 +11,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
<!-- This changes the project to: -->
- Fix CEL internal errors when iterating `headers`/`query` map wrappers by implementing map iterators for `HTTPHeaders` and `URLValues` ([#1465](https://github.com/TecharoHQ/anubis/pull/1465)).
## v1.25.0: Necron
Hey all,
I'm sure you've all been aware that things have been slowing down a little with Anubis development, and I want to apologize for that. A lot has been going on in my life lately (my blog will have a post out on Friday with more information), and as a result I haven't really had the energy to work on Anubis in publicly visible ways. There are things going on behind the scenes, but nothing is really shippable yet, sorry!
I've also been feeling some burnout in the wake of perennial waves of anger directed towards me. I'm handling it, I'll be fine, I've just had a lot going on in my life and it's been rough.
I've been missing the sense of wanderlust and discovery that comes with the artistic way I playfully develop software. I suspect that some of the stresses I've been through (setting up a complicated surgery in a country whose language you aren't fluent in is kind of an experience) have been sapping my energy. I'd gonna try to mess with things on my break, but realistically I'm probably just gonna be either watching Stargate SG-1 or doing unreasonable amounts of ocean fishing in Final Fantasy 14. Normally I'd love to keep the details about my medical state fairly private, but I'm more of a public figure now than I was this time last year so I don't really get the invisibility I'm used to for this.
I've also had a fair amount of negativity directed at me for simply being much more visible than the anonymous threat actors running the scrapers that are ruining everything, which though understandable has not helped.
Anyways, it all worked out and I'm about to be in the hospital for a week, so if things go really badly with this release please downgrade to the last version and/or upgrade to the main branch when the fix PR is inevitably merged. I hoped to have time to tame GPG and set up full release automation in the Anubis repo, but that didn't work out this time and that's okay.
If I can challenge you all to do something, go out there and try to actually create something new somehow. Combine ideas you've never mixed before. Be creative, be human, make something purely for yourself to scratch an itch that you've always had yet never gotten around to actually mending.
At the very least, try to be an example of how you want other people to act, even when you're in a situation where software written by someone else is configured to require a user agent to execute javascript to access a webpage.
Be well,
Xe
PS: if you're well-versed in FFXIV lore, the release title should give you an idea of the kind of stuff I've been going through mentally.
- Add iplist2rule tool that lets admins turn an IP address blocklist into an Anubis ruleset.
- Add Polish locale ([#1292](https://github.com/TecharoHQ/anubis/pull/1309))
- Fix honeypot and imprint links missing `BASE_PREFIX` when deployed behind a path prefix ([#1402](https://github.com/TecharoHQ/anubis/issues/1402))
@@ -18,8 +45,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Improve idle performance in memory storage
- Add HAProxy Configurations to Docs ([#1424](https://github.com/TecharoHQ/anubis/pull/1424))
<!-- This changes the project to: -->
## v1.24.0: Y'shtola Rhul
Anubis is back and better than ever! Lots of minor fixes with some big ones interspersed.

View File

@@ -0,0 +1,44 @@
package policy
import (
"net/http"
"testing"
"github.com/TecharoHQ/anubis/internal/dns"
"github.com/TecharoHQ/anubis/lib/config"
"github.com/TecharoHQ/anubis/lib/store/memory"
)
func newTestDNS(t *testing.T) *dns.Dns {
t.Helper()
ctx := t.Context()
memStore := memory.New(ctx)
cache := dns.NewDNSCache(300, 300, memStore)
return dns.New(ctx, cache)
}
func TestCELChecker_MapIterationWrappers(t *testing.T) {
cfg := &config.ExpressionOrList{
Expression: `headers.exists(k, k == "Accept") && query.exists(k, k == "format")`,
}
checker, err := NewCELChecker(cfg, newTestDNS(t))
if err != nil {
t.Fatalf("creating CEL checker failed: %v", err)
}
req, err := http.NewRequest(http.MethodGet, "https://example.com/?format=json", nil)
if err != nil {
t.Fatalf("making request failed: %v", err)
}
req.Header.Set("Accept", "application/json")
got, err := checker.Check(req)
if err != nil {
t.Fatalf("checking expression failed: %v", err)
}
if !got {
t.Fatal("expected expression to evaluate true")
}
}

View File

@@ -66,7 +66,9 @@ func (h HTTPHeaders) Get(key ref.Val) ref.Val {
return result
}
func (h HTTPHeaders) Iterator() traits.Iterator { panic("TODO(Xe): implement me") }
func (h HTTPHeaders) Iterator() traits.Iterator {
return newMapIterator(h.Header)
}
func (h HTTPHeaders) IsZeroValue() bool {
return len(h.Header) == 0

View File

@@ -0,0 +1,60 @@
package expressions
import (
"errors"
"maps"
"reflect"
"slices"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/common/types/traits"
)
var ErrNotImplemented = errors.New("expressions: not implemented")
type stringSliceIterator struct {
keys []string
idx int
}
func (s *stringSliceIterator) Value() any {
return s
}
func (s *stringSliceIterator) ConvertToNative(typeDesc reflect.Type) (any, error) {
return nil, ErrNotImplemented
}
func (s *stringSliceIterator) ConvertToType(typeValue ref.Type) ref.Val {
return types.NewErr("can't convert from %q to %q", types.IteratorType, typeValue)
}
func (s *stringSliceIterator) Equal(other ref.Val) ref.Val {
return types.NewErr("can't compare %q to %q", types.IteratorType, other.Type())
}
func (s *stringSliceIterator) Type() ref.Type {
return types.IteratorType
}
func (s *stringSliceIterator) HasNext() ref.Val {
return types.Bool(s.idx < len(s.keys))
}
func (s *stringSliceIterator) Next() ref.Val {
if s.HasNext() != types.True {
return nil
}
val := s.keys[s.idx]
s.idx++
return types.String(val)
}
func newMapIterator(m map[string][]string) traits.Iterator {
return &stringSliceIterator{
keys: slices.Collect(maps.Keys(m)),
idx: 0,
}
}

View File

@@ -1,7 +1,6 @@
package expressions
import (
"errors"
"net/url"
"reflect"
"strings"
@@ -11,8 +10,6 @@ import (
"github.com/google/cel-go/common/types/traits"
)
var ErrNotImplemented = errors.New("expressions: not implemented")
// URLValues is a type wrapper to expose url.Values into CEL programs.
type URLValues struct {
url.Values
@@ -69,7 +66,9 @@ func (u URLValues) Get(key ref.Val) ref.Val {
return result
}
func (u URLValues) Iterator() traits.Iterator { panic("TODO(Xe): implement me") }
func (u URLValues) Iterator() traits.Iterator {
return newMapIterator(u.Values)
}
func (u URLValues) IsZeroValue() bool {
return len(u.Values) == 0

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@techaro/anubis",
"version": "1.24.0",
"version": "1.25.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@techaro/anubis",
"version": "1.24.0",
"version": "1.25.0",
"license": "ISC",
"dependencies": {
"@aws-crypto/sha256-js": "^5.2.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@techaro/anubis",
"version": "1.24.0",
"version": "1.25.0",
"description": "",
"main": "index.js",
"scripts": {
@@ -66,4 +66,4 @@
"trailingComma": "all",
"printWidth": 80
}
}
}

View File

@@ -1,11 +0,0 @@
# /etc/anubis/default.env
BIND=/shared/anubis.sock
BIND_NETWORK=unix
SOCKET_MODE=0666
DIFFICULTY=4
METRICS_BIND=:9090
COOKIE_DYNAMIC_DOMAIN=true
# address and port of the actual application (httpdebug container)
TARGET=http://httpdebug:3000
POLICY_FNAME=/cfg/anubis.yaml

View File

@@ -1,11 +0,0 @@
bots:
- name: mozilla
user_agent_regex: Mozilla
action: CHALLENGE
challenge:
difficulty: 2
algorithm: fast
status_codes:
CHALLENGE: 401
DENY: 403

View File

@@ -1,27 +0,0 @@
# /etc/haproxy/haproxy.cfg
frontend FE-application
mode http
timeout client 5s
timeout connect 5s
timeout server 5s
bind :80
# ssl offloading on port 8443 using a certificate from /etc/haproxy/ssl/
bind :8443 ssl crt /etc/techaro/pki/haproxy-simple.test.pem alpn h2,http/1.1 ssl-min-ver TLSv1.2 no-tls-tickets
# set X-Real-IP header required for Anubis
http-request set-header X-Real-IP "%[src]"
# redirect HTTP to HTTPS
http-request redirect scheme https code 301 unless { ssl_fc }
# add HSTS header
http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
# route to Anubis backend by default
default_backend BE-anubis-application
backend BE-anubis-application
mode http
timeout connect 5s
timeout server 5s
server anubis /shared/anubis.sock

View File

@@ -1,27 +0,0 @@
services:
haproxy:
image: haproxytech/haproxy-alpine:3.0
ports:
- 80:80
- 8443:8443
volumes:
- ./conf/haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg
- ./pki:/etc/techaro/pki:ro
- anubis-socket:/shared
anubis:
image: ghcr.io/techarohq/anubis:main
env_file: ./anubis.env
user: root
volumes:
- anubis-socket:/shared
- ./conf/anubis:/cfg:ro
depends_on:
- httpdebug
httpdebug:
image: ghcr.io/xe/x/httpdebug
pull_policy: always
volumes:
anubis-socket:

View File

@@ -1,39 +0,0 @@
#!/usr/bin/env node
async function main() {
console.log("Starting HAProxy simple smoke test...");
console.log("trying to hit backend through haproxy");
let resp = await fetch(
"https://localhost:8443",
{
headers: {
"User-Agent": "Anubis testing",
}
}
);
if (resp.status !== 200) {
throw new Error(`Expected 200, got ${resp.status}`);
}
console.log("Got 200 as expected");
console.log("trying to get stopped by anubis");
resp = await fetch(
"https://localhost:8443",
{
headers: {
"User-Agent": "Mozilla/5.0",
}
}
);
if (resp.status !== 401) {
throw new Error(`Expected 401, got ${resp.status}`);
}
console.log("Got 401 as expected");
console.log("All runtime tests passed successfully!");
}
await main();

View File

@@ -1,31 +0,0 @@
#!/usr/bin/env bash
source ../lib/lib.sh
export KO_DOCKER_REPO=ko.local
set -euo pipefail
# Step 1: Config validation
mint_cert haproxy-simple.test
# Combine cert and key for HAProxy SSL directory format
cat pki/haproxy-simple.test/cert.pem pki/haproxy-simple.test/key.pem >pki/haproxy-simple.test/haproxy.pem
docker run --rm \
-v $PWD/conf/haproxy:/usr/local/etc/haproxy:ro \
-v $PWD/pki:/etc/techaro/pki:ro \
haproxytech/haproxy-alpine:3.0 \
haproxy -c -f /usr/local/etc/haproxy/haproxy.cfg
# Step 2: Runtime testing
echo "Starting services..."
docker compose up -d
sleep 5
echo "Services are healthy. Starting runtime tests..."
export NODE_TLS_REJECT_UNAUTHORIZED=0
node test.mjs
# Cleanup happens automatically via trap in lib.sh