This reverts commit e7cbd349f3
.
This commit is contained in:
parent
e7cbd349f3
commit
ecc6b47f90
6 changed files with 53 additions and 144 deletions
|
@ -33,9 +33,6 @@ import (
|
||||||
var (
|
var (
|
||||||
bind = flag.String("bind", ":8923", "network address to bind HTTP to")
|
bind = flag.String("bind", ":8923", "network address to bind HTTP to")
|
||||||
bindNetwork = flag.String("bind-network", "tcp", "network family to bind HTTP to, e.g. unix, tcp")
|
bindNetwork = flag.String("bind-network", "tcp", "network family to bind HTTP to, e.g. unix, tcp")
|
||||||
cookieDomain = flag.String("cookie-domain", "", "if set, the top-level domain that the Anubis cookie will be valid for")
|
|
||||||
cookieName = flag.String("cookie-name", anubis.CookieName, "the name of the cookie that Anubis stores challenge pass records in")
|
|
||||||
cookiePartitioned = flag.Bool("cookie-partitioned", false, "if true, sets the partitioned flag on Anubis cookies, enabling CHIPS support")
|
|
||||||
challengeDifficulty = flag.Int("difficulty", anubis.DefaultDifficulty, "difficulty of the challenge")
|
challengeDifficulty = flag.Int("difficulty", anubis.DefaultDifficulty, "difficulty of the challenge")
|
||||||
ed25519PrivateKeyHex = flag.String("ed25519-private-key-hex", "", "private key used to sign JWTs, if not set a random one will be assigned")
|
ed25519PrivateKeyHex = flag.String("ed25519-private-key-hex", "", "private key used to sign JWTs, if not set a random one will be assigned")
|
||||||
metricsBind = flag.String("metrics-bind", ":9090", "network address to bind metrics to")
|
metricsBind = flag.String("metrics-bind", ":9090", "network address to bind metrics to")
|
||||||
|
|
|
@ -18,7 +18,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- Fixed bot check to only apply if address range matches
|
- Fixed bot check to only apply if address range matches
|
||||||
- Fix default difficulty setting that was broken in a refactor
|
- Fix default difficulty setting that was broken in a refactor
|
||||||
- Linting fixes
|
- Linting fixes
|
||||||
- Extra cookie options can be set such as the name, domain, and partitioned flag [#73](https://github.com/TecharoHQ/anubis/issues/73)
|
|
||||||
- Make dark mode diff lines readable in the documentation
|
- Make dark mode diff lines readable in the documentation
|
||||||
|
|
||||||
## v1.14.2
|
## v1.14.2
|
||||||
|
|
|
@ -41,21 +41,18 @@ Anubis has very minimal system requirements. I suspect that 128Mi of ram may be
|
||||||
|
|
||||||
Anubis uses these environment variables for configuration:
|
Anubis uses these environment variables for configuration:
|
||||||
|
|
||||||
| Environment Variable | Default value | Explanation |
|
| Environment Variable | Default value | Explanation |
|
||||||
| :------------------------ | :--------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| :------------------------ | :---------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `BIND` | `:8923` | The network address that Anubis listens on. For `unix`, set this to a path: `/run/anubis/instance.sock` |
|
| `BIND` | `:8923` | The network address that Anubis listens on. For `unix`, set this to a path: `/run/anubis/instance.sock` |
|
||||||
| `BIND_NETWORK` | `tcp` | The address family that Anubis listens on. Accepts `tcp`, `unix` and anything Go's [`net.Listen`](https://pkg.go.dev/net#Listen) supports. |
|
| `BIND_NETWORK` | `tcp` | The address family that Anubis listens on. Accepts `tcp`, `unix` and anything Go's [`net.Listen`](https://pkg.go.dev/net#Listen) supports. |
|
||||||
| `COOKIE_DOMAIN` | unset | The domain the Anubis challenge pass cookie should be set to. This should be set to the domain you bought from your registrar (EG: `techaro.lol` if your webapp is running on `anubis.techaro.lol`). See [here](https://stackoverflow.com/a/1063760) for more information. |
|
| `DIFFICULTY` | `5` | The difficulty of the challenge, or the number of leading zeroes that must be in successful responses. |
|
||||||
| `COOKIE_NAME` | `within.website-x-cmd-anubis-auth` | The cookie that Anubis uses to determine if users have passed a challenge. This should not be changed without a good reason. |
|
| `ED25519_PRIVATE_KEY_HEX` | | The hex-encoded ed25519 private key used to sign Anubis responses. If this is not set, Anubis will generate one for you. This should be exactly 64 characters long. See below for details. |
|
||||||
| `COOKIE_PARTITIONED` | `false` | If set to `true`, enables the [partitioned (CHIPS) flag](https://developers.google.com/privacy-sandbox/cookies/chips), meaning that Anubis inside an iframe has a different set of cookies than the domain hosting the iframe. |
|
| `METRICS_BIND` | `:9090` | The network address that Anubis serves Prometheus metrics on. See `BIND` for more information. |
|
||||||
| `DIFFICULTY` | `5` | The difficulty of the challenge, or the number of leading zeroes that must be in successful responses. |
|
| `METRICS_BIND_NETWORK` | `tcp` | The address family that the Anubis metrics server listens on. See `BIND_NETWORK` for more information. |
|
||||||
| `ED25519_PRIVATE_KEY_HEX` | unset | The hex-encoded ed25519 private key used to sign Anubis responses. If this is not set, Anubis will generate one for you. This should be exactly 64 characters long. See below for details. |
|
| `SOCKET_MODE` | `0770` | _Only used when at least one of the `*_BIND_NETWORK` variables are set to `unix`._ The socket mode (permissions) for Unix domain sockets. |
|
||||||
| `METRICS_BIND` | `:9090` | The network address that Anubis serves Prometheus metrics on. See `BIND` for more information. |
|
| `POLICY_FNAME` | unset | The file containing [bot policy configuration](./policies.md). See the bot policy documentation for more details. If unset, the default bot policy configuration is used. |
|
||||||
| `METRICS_BIND_NETWORK` | `tcp` | The address family that the Anubis metrics server listens on. See `BIND_NETWORK` for more information. |
|
| `SERVE_ROBOTS_TXT` | `false` | If set `true`, Anubis will serve a default `robots.txt` file that disallows all known AI scrapers by name and then additionally disallows every scraper. This is useful if facts and circumstances make it difficult to change the underlying service to serve such a `robots.txt` file. |
|
||||||
| `SOCKET_MODE` | `0770` | _Only used when at least one of the `*_BIND_NETWORK` variables are set to `unix`._ The socket mode (permissions) for Unix domain sockets. |
|
| `TARGET` | `http://localhost:3923` | The URL of the service that Anubis should forward valid requests to. Supports Unix domain sockets, set this to a URI like so: `unix:///path/to/socket.sock`. |
|
||||||
| `POLICY_FNAME` | unset | The file containing [bot policy configuration](./policies.md). See the bot policy documentation for more details. If unset, the default bot policy configuration is used. |
|
|
||||||
| `SERVE_ROBOTS_TXT` | `false` | If set `true`, Anubis will serve a default `robots.txt` file that disallows all known AI scrapers by name and then additionally disallows every scraper. This is useful if facts and circumstances make it difficult to change the underlying service to serve such a `robots.txt` file. |
|
|
||||||
| `TARGET` | `http://localhost:3923` | The URL of the service that Anubis should forward valid requests to. Supports Unix domain sockets, set this to a URI like so: `unix:///path/to/socket.sock`. |
|
|
||||||
|
|
||||||
### Key generation
|
### Key generation
|
||||||
|
|
||||||
|
|
|
@ -67,10 +67,6 @@ type Options struct {
|
||||||
Policy *policy.ParsedConfig
|
Policy *policy.ParsedConfig
|
||||||
ServeRobotsTXT bool
|
ServeRobotsTXT bool
|
||||||
PrivateKey ed25519.PrivateKey
|
PrivateKey ed25519.PrivateKey
|
||||||
|
|
||||||
CookieDomain string
|
|
||||||
CookieName string
|
|
||||||
CookiePartitioned bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadPoliciesOrDefault(fname string, defaultDifficulty int) (*policy.ParsedConfig, error) {
|
func LoadPoliciesOrDefault(fname string, defaultDifficulty int) (*policy.ParsedConfig, error) {
|
||||||
|
@ -112,7 +108,6 @@ func New(opts Options) (*Server, error) {
|
||||||
priv: opts.PrivateKey,
|
priv: opts.PrivateKey,
|
||||||
pub: opts.PrivateKey.Public().(ed25519.PublicKey),
|
pub: opts.PrivateKey.Public().(ed25519.PublicKey),
|
||||||
policy: opts.Policy,
|
policy: opts.Policy,
|
||||||
opts: opts,
|
|
||||||
DNSBLCache: decaymap.New[string, dnsbl.DroneBLResponse](),
|
DNSBLCache: decaymap.New[string, dnsbl.DroneBLResponse](),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,7 +145,6 @@ type Server struct {
|
||||||
priv ed25519.PrivateKey
|
priv ed25519.PrivateKey
|
||||||
pub ed25519.PublicKey
|
pub ed25519.PublicKey
|
||||||
policy *policy.ParsedConfig
|
policy *policy.ParsedConfig
|
||||||
opts Options
|
|
||||||
DNSBLCache *decaymap.Impl[string, dnsbl.DroneBLResponse]
|
DNSBLCache *decaymap.Impl[string, dnsbl.DroneBLResponse]
|
||||||
ChallengeDifficulty int
|
ChallengeDifficulty int
|
||||||
}
|
}
|
||||||
|
@ -223,7 +217,7 @@ func (s *Server) MaybeReverseProxy(w http.ResponseWriter, r *http.Request) {
|
||||||
s.next.ServeHTTP(w, r)
|
s.next.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
case config.RuleDeny:
|
case config.RuleDeny:
|
||||||
s.ClearCookie(w)
|
ClearCookie(w)
|
||||||
lg.Info("explicit deny")
|
lg.Info("explicit deny")
|
||||||
if rule == nil {
|
if rule == nil {
|
||||||
lg.Error("rule is nil, cannot calculate checksum")
|
lg.Error("rule is nil, cannot calculate checksum")
|
||||||
|
@ -242,29 +236,29 @@ func (s *Server) MaybeReverseProxy(w http.ResponseWriter, r *http.Request) {
|
||||||
case config.RuleChallenge:
|
case config.RuleChallenge:
|
||||||
lg.Debug("challenge requested")
|
lg.Debug("challenge requested")
|
||||||
default:
|
default:
|
||||||
s.ClearCookie(w)
|
ClearCookie(w)
|
||||||
templ.Handler(web.Base("Oh noes!", web.ErrorPage("Other internal server error (contact the admin)")), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
|
templ.Handler(web.Base("Oh noes!", web.ErrorPage("Other internal server error (contact the admin)")), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ckie, err := r.Cookie(s.opts.CookieName)
|
ckie, err := r.Cookie(anubis.CookieName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lg.Debug("cookie not found", "path", r.URL.Path)
|
lg.Debug("cookie not found", "path", r.URL.Path)
|
||||||
s.ClearCookie(w)
|
ClearCookie(w)
|
||||||
s.RenderIndex(w, r)
|
s.RenderIndex(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ckie.Valid(); err != nil {
|
if err := ckie.Valid(); err != nil {
|
||||||
lg.Debug("cookie is invalid", "err", err)
|
lg.Debug("cookie is invalid", "err", err)
|
||||||
s.ClearCookie(w)
|
ClearCookie(w)
|
||||||
s.RenderIndex(w, r)
|
s.RenderIndex(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if time.Now().After(ckie.Expires) && !ckie.Expires.IsZero() {
|
if time.Now().After(ckie.Expires) && !ckie.Expires.IsZero() {
|
||||||
lg.Debug("cookie expired", "path", r.URL.Path)
|
lg.Debug("cookie expired", "path", r.URL.Path)
|
||||||
s.ClearCookie(w)
|
ClearCookie(w)
|
||||||
s.RenderIndex(w, r)
|
s.RenderIndex(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -275,7 +269,7 @@ func (s *Server) MaybeReverseProxy(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
if err != nil || !token.Valid {
|
if err != nil || !token.Valid {
|
||||||
lg.Debug("invalid token", "path", r.URL.Path, "err", err)
|
lg.Debug("invalid token", "path", r.URL.Path, "err", err)
|
||||||
s.ClearCookie(w)
|
ClearCookie(w)
|
||||||
s.RenderIndex(w, r)
|
s.RenderIndex(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -290,7 +284,7 @@ func (s *Server) MaybeReverseProxy(w http.ResponseWriter, r *http.Request) {
|
||||||
claims, ok := token.Claims.(jwt.MapClaims)
|
claims, ok := token.Claims.(jwt.MapClaims)
|
||||||
if !ok {
|
if !ok {
|
||||||
lg.Debug("invalid token claims type", "path", r.URL.Path)
|
lg.Debug("invalid token claims type", "path", r.URL.Path)
|
||||||
s.ClearCookie(w)
|
ClearCookie(w)
|
||||||
s.RenderIndex(w, r)
|
s.RenderIndex(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -298,7 +292,7 @@ func (s *Server) MaybeReverseProxy(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
if claims["challenge"] != challenge {
|
if claims["challenge"] != challenge {
|
||||||
lg.Debug("invalid challenge", "path", r.URL.Path)
|
lg.Debug("invalid challenge", "path", r.URL.Path)
|
||||||
s.ClearCookie(w)
|
ClearCookie(w)
|
||||||
s.RenderIndex(w, r)
|
s.RenderIndex(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -315,7 +309,7 @@ func (s *Server) MaybeReverseProxy(w http.ResponseWriter, r *http.Request) {
|
||||||
if subtle.ConstantTimeCompare([]byte(claims["response"].(string)), []byte(calculated)) != 1 {
|
if subtle.ConstantTimeCompare([]byte(claims["response"].(string)), []byte(calculated)) != 1 {
|
||||||
lg.Debug("invalid response", "path", r.URL.Path)
|
lg.Debug("invalid response", "path", r.URL.Path)
|
||||||
failedValidations.Inc()
|
failedValidations.Inc()
|
||||||
s.ClearCookie(w)
|
ClearCookie(w)
|
||||||
s.RenderIndex(w, r)
|
s.RenderIndex(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -378,7 +372,7 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
nonceStr := r.FormValue("nonce")
|
nonceStr := r.FormValue("nonce")
|
||||||
if nonceStr == "" {
|
if nonceStr == "" {
|
||||||
s.ClearCookie(w)
|
ClearCookie(w)
|
||||||
lg.Debug("no nonce")
|
lg.Debug("no nonce")
|
||||||
templ.Handler(web.Base("Oh noes!", web.ErrorPage("missing nonce")), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
|
templ.Handler(web.Base("Oh noes!", web.ErrorPage("missing nonce")), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
|
@ -386,7 +380,7 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
elapsedTimeStr := r.FormValue("elapsedTime")
|
elapsedTimeStr := r.FormValue("elapsedTime")
|
||||||
if elapsedTimeStr == "" {
|
if elapsedTimeStr == "" {
|
||||||
s.ClearCookie(w)
|
ClearCookie(w)
|
||||||
lg.Debug("no elapsedTime")
|
lg.Debug("no elapsedTime")
|
||||||
templ.Handler(web.Base("Oh noes!", web.ErrorPage("missing elapsedTime")), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
|
templ.Handler(web.Base("Oh noes!", web.ErrorPage("missing elapsedTime")), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
|
@ -394,7 +388,7 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
elapsedTime, err := strconv.ParseFloat(elapsedTimeStr, 64)
|
elapsedTime, err := strconv.ParseFloat(elapsedTimeStr, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.ClearCookie(w)
|
ClearCookie(w)
|
||||||
lg.Debug("elapsedTime doesn't parse", "err", err)
|
lg.Debug("elapsedTime doesn't parse", "err", err)
|
||||||
templ.Handler(web.Base("Oh noes!", web.ErrorPage("invalid elapsedTime")), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
|
templ.Handler(web.Base("Oh noes!", web.ErrorPage("invalid elapsedTime")), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
|
@ -410,7 +404,7 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
nonce, err := strconv.Atoi(nonceStr)
|
nonce, err := strconv.Atoi(nonceStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.ClearCookie(w)
|
ClearCookie(w)
|
||||||
lg.Debug("nonce doesn't parse", "err", err)
|
lg.Debug("nonce doesn't parse", "err", err)
|
||||||
templ.Handler(web.Base("Oh noes!", web.ErrorPage("invalid nonce")), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
|
templ.Handler(web.Base("Oh noes!", web.ErrorPage("invalid nonce")), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
|
@ -420,7 +414,7 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
|
||||||
calculated := internal.SHA256sum(calcString)
|
calculated := internal.SHA256sum(calcString)
|
||||||
|
|
||||||
if subtle.ConstantTimeCompare([]byte(response), []byte(calculated)) != 1 {
|
if subtle.ConstantTimeCompare([]byte(response), []byte(calculated)) != 1 {
|
||||||
s.ClearCookie(w)
|
ClearCookie(w)
|
||||||
lg.Debug("hash does not match", "got", response, "want", calculated)
|
lg.Debug("hash does not match", "got", response, "want", calculated)
|
||||||
templ.Handler(web.Base("Oh noes!", web.ErrorPage("invalid response")), templ.WithStatus(http.StatusForbidden)).ServeHTTP(w, r)
|
templ.Handler(web.Base("Oh noes!", web.ErrorPage("invalid response")), templ.WithStatus(http.StatusForbidden)).ServeHTTP(w, r)
|
||||||
failedValidations.Inc()
|
failedValidations.Inc()
|
||||||
|
@ -429,7 +423,7 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// compare the leading zeroes
|
// compare the leading zeroes
|
||||||
if !strings.HasPrefix(response, strings.Repeat("0", s.ChallengeDifficulty)) {
|
if !strings.HasPrefix(response, strings.Repeat("0", s.ChallengeDifficulty)) {
|
||||||
s.ClearCookie(w)
|
ClearCookie(w)
|
||||||
lg.Debug("difficulty check failed", "response", response, "difficulty", s.ChallengeDifficulty)
|
lg.Debug("difficulty check failed", "response", response, "difficulty", s.ChallengeDifficulty)
|
||||||
templ.Handler(web.Base("Oh noes!", web.ErrorPage("invalid response")), templ.WithStatus(http.StatusForbidden)).ServeHTTP(w, r)
|
templ.Handler(web.Base("Oh noes!", web.ErrorPage("invalid response")), templ.WithStatus(http.StatusForbidden)).ServeHTTP(w, r)
|
||||||
failedValidations.Inc()
|
failedValidations.Inc()
|
||||||
|
@ -448,18 +442,17 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
|
||||||
tokenString, err := token.SignedString(s.priv)
|
tokenString, err := token.SignedString(s.priv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lg.Error("failed to sign JWT", "err", err)
|
lg.Error("failed to sign JWT", "err", err)
|
||||||
s.ClearCookie(w)
|
ClearCookie(w)
|
||||||
templ.Handler(web.Base("Oh noes!", web.ErrorPage("failed to sign JWT")), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
|
templ.Handler(web.Base("Oh noes!", web.ErrorPage("failed to sign JWT")), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
http.SetCookie(w, &http.Cookie{
|
http.SetCookie(w, &http.Cookie{
|
||||||
Name: s.opts.CookieName,
|
Name: anubis.CookieName,
|
||||||
Value: tokenString,
|
Value: tokenString,
|
||||||
Expires: time.Now().Add(24 * 7 * time.Hour),
|
Expires: time.Now().Add(24 * 7 * time.Hour),
|
||||||
Domain: s.opts.CookieDomain,
|
SameSite: http.SameSiteLaxMode,
|
||||||
Partitioned: s.opts.CookiePartitioned,
|
Path: "/",
|
||||||
SameSite: http.SameSiteLaxMode,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
challengesValidated.Inc()
|
challengesValidated.Inc()
|
||||||
|
|
|
@ -1,18 +1,15 @@
|
||||||
package lib
|
package lib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/TecharoHQ/anubis"
|
"github.com/TecharoHQ/anubis"
|
||||||
"github.com/TecharoHQ/anubis/internal"
|
|
||||||
"github.com/TecharoHQ/anubis/lib/policy"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func loadPolicies(t *testing.T, fname string) *policy.ParsedConfig {
|
func spawnAnubis(t *testing.T, h http.Handler) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
policy, err := LoadPoliciesOrDefault("", anubis.DefaultDifficulty)
|
policy, err := LoadPoliciesOrDefault("", anubis.DefaultDifficulty)
|
||||||
|
@ -20,97 +17,23 @@ func loadPolicies(t *testing.T, fname string) *policy.ParsedConfig {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return policy
|
s, err := New(Options{
|
||||||
}
|
Next: h,
|
||||||
|
Policy: policy,
|
||||||
func spawnAnubis(t *testing.T, opts Options) *Server {
|
ServeRobotsTXT: true,
|
||||||
t.Helper()
|
})
|
||||||
|
|
||||||
s, err := New(opts)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("can't construct libanubis.Server: %v", err)
|
t.Fatalf("can't construct libanubis.Server: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return s
|
ts := httptest.NewServer(s)
|
||||||
}
|
t.Log(ts.URL)
|
||||||
|
|
||||||
func TestCookieSettings(t *testing.T) {
|
t.Cleanup(func() {
|
||||||
pol := loadPolicies(t, "")
|
ts.Close()
|
||||||
pol.DefaultDifficulty = 0
|
|
||||||
|
|
||||||
srv := spawnAnubis(t, Options{
|
|
||||||
Next: http.NewServeMux(),
|
|
||||||
Policy: pol,
|
|
||||||
|
|
||||||
CookieDomain: "local.cetacean.club",
|
|
||||||
CookiePartitioned: true,
|
|
||||||
CookieName: t.Name(),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
ts := httptest.NewServer(internal.DefaultXRealIP("127.0.0.1", srv))
|
return ts.URL
|
||||||
defer ts.Close()
|
|
||||||
|
|
||||||
cli := &http.Client{
|
|
||||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
|
||||||
return http.ErrUseLastResponse
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := cli.Post(ts.URL+"/.within.website/x/cmd/anubis/api/make-challenge", "", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("can't request challenge: %v", err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var chall = struct {
|
|
||||||
Challenge string `json:"challenge"`
|
|
||||||
}{}
|
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&chall); err != nil {
|
|
||||||
t.Fatalf("can't read challenge response body: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
nonce := 0
|
|
||||||
elapsedTime := 420
|
|
||||||
redir := "/"
|
|
||||||
calcString := fmt.Sprintf("%s%d", chall.Challenge, nonce)
|
|
||||||
calculated := internal.SHA256sum(calcString)
|
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodGet, ts.URL+"/.within.website/x/cmd/anubis/api/pass-challenge", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("can't make request: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
q := req.URL.Query()
|
|
||||||
q.Set("response", calculated)
|
|
||||||
q.Set("nonce", fmt.Sprint(nonce))
|
|
||||||
q.Set("redir", redir)
|
|
||||||
q.Set("elapsedTime", fmt.Sprint(elapsedTime))
|
|
||||||
req.URL.RawQuery = q.Encode()
|
|
||||||
|
|
||||||
resp, err = cli.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("can't do challenge passing")
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusFound {
|
|
||||||
t.Errorf("wanted %d, got: %d", http.StatusFound, resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
found := false
|
|
||||||
for _, cookie := range resp.Cookies() {
|
|
||||||
t.Logf("%#v", cookie)
|
|
||||||
if cookie.Name == t.Name() {
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if found && cookie.Domain != "local.cetacean.club" {
|
|
||||||
t.Errorf("cookie domain is wrong, wanted local.cetacean.club, got: %s", cookie.Domain)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
t.Errorf("Cookie %q not found", t.Name())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckDefaultDifficultyMatchesPolicy(t *testing.T) {
|
func TestCheckDefaultDifficultyMatchesPolicy(t *testing.T) {
|
||||||
|
|
16
lib/http.go
16
lib/http.go
|
@ -3,17 +3,17 @@ package lib
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/TecharoHQ/anubis"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Server) ClearCookie(w http.ResponseWriter) {
|
func ClearCookie(w http.ResponseWriter) {
|
||||||
http.SetCookie(w, &http.Cookie{
|
http.SetCookie(w, &http.Cookie{
|
||||||
Name: s.opts.CookieName,
|
Name: anubis.CookieName,
|
||||||
Value: "",
|
Value: "",
|
||||||
Expires: time.Now().Add(-1 * time.Hour),
|
Expires: time.Now().Add(-1 * time.Hour),
|
||||||
MaxAge: -1,
|
MaxAge: -1,
|
||||||
Domain: s.opts.CookieDomain,
|
SameSite: http.SameSiteLaxMode,
|
||||||
Partitioned: s.opts.CookiePartitioned,
|
|
||||||
SameSite: http.SameSiteLaxMode,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue