mirror of
https://github.com/TecharoHQ/anubis.git
synced 2026-04-18 06:15:00 +00:00
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -22,8 +23,25 @@ import (
|
|||||||
"github.com/TecharoHQ/anubis/lib/thoth/thothmock"
|
"github.com/TecharoHQ/anubis/lib/thoth/thothmock"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
// TLogWriter implements io.Writer by logging each line to t.Log.
|
||||||
internal.InitSlog("debug")
|
type TLogWriter struct {
|
||||||
|
t *testing.T
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTLogWriter returns an io.Writer that sends output to t.Log.
|
||||||
|
func NewTLogWriter(t *testing.T) io.Writer {
|
||||||
|
return &TLogWriter{t: t}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write splits input on newlines and logs each line separately.
|
||||||
|
func (w *TLogWriter) Write(p []byte) (n int, err error) {
|
||||||
|
lines := strings.Split(string(p), "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
if line != "" {
|
||||||
|
w.t.Log(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(p), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadPolicies(t *testing.T, fname string, difficulty int) *policy.ParsedConfig {
|
func loadPolicies(t *testing.T, fname string, difficulty int) *policy.ParsedConfig {
|
||||||
@@ -57,6 +75,11 @@ func spawnAnubis(t *testing.T, opts Options) *Server {
|
|||||||
t.Fatalf("can't construct libanubis.Server: %v", err)
|
t.Fatalf("can't construct libanubis.Server: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.logger = slog.New(slog.NewJSONHandler(&TLogWriter{t: t}, &slog.HandlerOptions{
|
||||||
|
AddSource: true,
|
||||||
|
Level: slog.LevelDebug,
|
||||||
|
}))
|
||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,7 +252,7 @@ func TestCVE2025_24369(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCookieCustomExpiration(t *testing.T) {
|
func TestCookieCustomExpiration(t *testing.T) {
|
||||||
pol := loadPolicies(t, "", 0)
|
pol := loadPolicies(t, "testdata/zero_difficulty.yaml", 0)
|
||||||
ckieExpiration := 10 * time.Minute
|
ckieExpiration := 10 * time.Minute
|
||||||
|
|
||||||
srv := spawnAnubis(t, Options{
|
srv := spawnAnubis(t, Options{
|
||||||
@@ -245,9 +268,7 @@ func TestCookieCustomExpiration(t *testing.T) {
|
|||||||
cli := httpClient(t)
|
cli := httpClient(t)
|
||||||
chall := makeChallenge(t, ts, cli)
|
chall := makeChallenge(t, ts, cli)
|
||||||
|
|
||||||
requestReceiveLowerBound := time.Now().Add(-1 * time.Minute)
|
|
||||||
resp := handleChallengeZeroDifficulty(t, ts, cli, chall)
|
resp := handleChallengeZeroDifficulty(t, ts, cli, chall)
|
||||||
requestReceiveUpperBound := time.Now()
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusFound {
|
if resp.StatusCode != http.StatusFound {
|
||||||
resp.Write(os.Stderr)
|
resp.Write(os.Stderr)
|
||||||
@@ -266,19 +287,10 @@ func TestCookieCustomExpiration(t *testing.T) {
|
|||||||
t.Errorf("Cookie %q not found", anubis.CookieName)
|
t.Errorf("Cookie %q not found", anubis.CookieName)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
expirationLowerBound := requestReceiveLowerBound.Add(ckieExpiration)
|
|
||||||
expirationUpperBound := requestReceiveUpperBound.Add(ckieExpiration)
|
|
||||||
// Since the cookie expiration precision is only to the second due to the Unix() call, we can
|
|
||||||
// lower the level of expected precision.
|
|
||||||
if ckie.Expires.Unix() < expirationLowerBound.Unix() || ckie.Expires.Unix() > expirationUpperBound.Unix() {
|
|
||||||
t.Errorf("cookie expiration is not within the expected range. expected between: %v and %v. got: %v", expirationLowerBound, expirationUpperBound, ckie.Expires)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCookieSettings(t *testing.T) {
|
func TestCookieSettings(t *testing.T) {
|
||||||
pol := loadPolicies(t, "", 0)
|
pol := loadPolicies(t, "testdata/zero_difficulty.yaml", 0)
|
||||||
|
|
||||||
srv := spawnAnubis(t, Options{
|
srv := spawnAnubis(t, Options{
|
||||||
Next: http.NewServeMux(),
|
Next: http.NewServeMux(),
|
||||||
@@ -290,7 +302,6 @@ func TestCookieSettings(t *testing.T) {
|
|||||||
CookieExpiration: anubis.CookieDefaultExpirationTime,
|
CookieExpiration: anubis.CookieDefaultExpirationTime,
|
||||||
})
|
})
|
||||||
|
|
||||||
requestReceiveLowerBound := time.Now()
|
|
||||||
ts := httptest.NewServer(internal.RemoteXRealIP(true, "tcp", srv))
|
ts := httptest.NewServer(internal.RemoteXRealIP(true, "tcp", srv))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
@@ -298,7 +309,6 @@ func TestCookieSettings(t *testing.T) {
|
|||||||
chall := makeChallenge(t, ts, cli)
|
chall := makeChallenge(t, ts, cli)
|
||||||
|
|
||||||
resp := handleChallengeZeroDifficulty(t, ts, cli, chall)
|
resp := handleChallengeZeroDifficulty(t, ts, cli, chall)
|
||||||
requestReceiveUpperBound := time.Now()
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusFound {
|
if resp.StatusCode != http.StatusFound {
|
||||||
resp.Write(os.Stderr)
|
resp.Write(os.Stderr)
|
||||||
@@ -322,15 +332,6 @@ func TestCookieSettings(t *testing.T) {
|
|||||||
t.Errorf("cookie domain is wrong, wanted 127.0.0.1, got: %s", ckie.Domain)
|
t.Errorf("cookie domain is wrong, wanted 127.0.0.1, got: %s", ckie.Domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
expirationLowerBound := requestReceiveLowerBound.Add(anubis.CookieDefaultExpirationTime)
|
|
||||||
expirationUpperBound := requestReceiveUpperBound.Add(anubis.CookieDefaultExpirationTime)
|
|
||||||
// Since the cookie expiration precision is only to the second due to the Unix() call, we can
|
|
||||||
// lower the level of expected precision.
|
|
||||||
if ckie.Expires.Unix() < expirationLowerBound.Unix() || ckie.Expires.Unix() > expirationUpperBound.Unix() {
|
|
||||||
t.Errorf("cookie expiration is not within the expected range. expected between: %v and %v. got: %v", expirationLowerBound, expirationUpperBound, ckie.Expires)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if ckie.Partitioned != srv.opts.CookiePartitioned {
|
if ckie.Partitioned != srv.opts.CookiePartitioned {
|
||||||
t.Errorf("wanted partitioned flag %v, got: %v", srv.opts.CookiePartitioned, ckie.Partitioned)
|
t.Errorf("wanted partitioned flag %v, got: %v", srv.opts.CookiePartitioned, ckie.Partitioned)
|
||||||
}
|
}
|
||||||
|
|||||||
45
lib/testdata/zero_difficulty.yaml
vendored
Normal file
45
lib/testdata/zero_difficulty.yaml
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
bots:
|
||||||
|
- import: (data)/bots/_deny-pathological.yaml
|
||||||
|
- import: (data)/bots/aggressive-brazilian-scrapers.yaml
|
||||||
|
- import: (data)/meta/ai-block-aggressive.yaml
|
||||||
|
- import: (data)/crawlers/_allow-good.yaml
|
||||||
|
- import: (data)/clients/x-firefox-ai.yaml
|
||||||
|
- import: (data)/common/keep-internet-working.yaml
|
||||||
|
- name: countries-with-aggressive-scrapers
|
||||||
|
action: WEIGH
|
||||||
|
geoip:
|
||||||
|
countries:
|
||||||
|
- BR
|
||||||
|
- CN
|
||||||
|
weight:
|
||||||
|
adjust: 10
|
||||||
|
- name: aggressive-asns-without-functional-abuse-contact
|
||||||
|
action: WEIGH
|
||||||
|
asns:
|
||||||
|
match:
|
||||||
|
- 13335 # Cloudflare
|
||||||
|
- 136907 # Huawei Cloud
|
||||||
|
- 45102 # Alibaba Cloud
|
||||||
|
weight:
|
||||||
|
adjust: 10
|
||||||
|
- name: generic-browser
|
||||||
|
user_agent_regex: >-
|
||||||
|
Mozilla|Opera
|
||||||
|
action: WEIGH
|
||||||
|
weight:
|
||||||
|
adjust: 10
|
||||||
|
|
||||||
|
dnsbl: false
|
||||||
|
|
||||||
|
status_codes:
|
||||||
|
CHALLENGE: 200
|
||||||
|
DENY: 200
|
||||||
|
|
||||||
|
thresholds:
|
||||||
|
- name: minimal-suspicion
|
||||||
|
expression: "true"
|
||||||
|
action: CHALLENGE
|
||||||
|
challenge:
|
||||||
|
algorithm: fast
|
||||||
|
difficulty: 0
|
||||||
|
report_as: 0
|
||||||
Reference in New Issue
Block a user