mirror of
https://github.com/TecharoHQ/anubis.git
synced 2026-04-05 16:28:17 +00:00
* refactor(http): split long line in respondWithStatus Signed-off-by: Max Chernoff <git@maxchernoff.ca> * feat(http): set `Cache-Control: no-store` on error responses Since #132, Anubis has set `Cache-Control: no-store` on challenge responses. However, this does not apply to deny responses, meaning that if Anubis is configured to block certain user agents and is behind a caching reverse proxy, this error page will be cached and served to all subsequent requests, even those with an allowed user agent. This commit configures the error page responder to also set the `Cache-Control` header, meaning that deny and challenge responses will now both have the same behaviour. Signed-off-by: Max Chernoff <git@maxchernoff.ca> * chore(spelling): add new words to allowlist Signed-off-by: Max Chernoff <git@maxchernoff.ca> * chore(actions): bump Go version to fix govulncheck errors Signed-off-by: Max Chernoff <git@maxchernoff.ca> --------- Signed-off-by: Max Chernoff <git@maxchernoff.ca> Signed-off-by: Xe Iaso <xe.iaso@techaro.lol> Co-authored-by: Xe Iaso <xe.iaso@techaro.lol>
226 lines
5.3 KiB
Go
226 lines
5.3 KiB
Go
package lib
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"testing"
|
|
|
|
"github.com/TecharoHQ/anubis"
|
|
"github.com/TecharoHQ/anubis/internal"
|
|
"github.com/TecharoHQ/anubis/lib/policy"
|
|
)
|
|
|
|
func TestSetCookie(t *testing.T) {
|
|
for _, tt := range []struct {
|
|
name string
|
|
host string
|
|
cookieName string
|
|
options Options
|
|
}{
|
|
{
|
|
name: "basic",
|
|
options: Options{},
|
|
host: "",
|
|
cookieName: anubis.CookieName,
|
|
},
|
|
{
|
|
name: "domain techaro.lol",
|
|
options: Options{CookieDomain: "techaro.lol"},
|
|
host: "",
|
|
cookieName: anubis.CookieName,
|
|
},
|
|
{
|
|
name: "dynamic cookie domain",
|
|
options: Options{CookieDynamicDomain: true},
|
|
host: "techaro.lol",
|
|
cookieName: anubis.CookieName,
|
|
},
|
|
} {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
srv := spawnAnubis(t, tt.options)
|
|
rw := httptest.NewRecorder()
|
|
|
|
srv.SetCookie(rw, CookieOpts{Value: "test", Host: tt.host})
|
|
|
|
resp := rw.Result()
|
|
cookies := resp.Cookies()
|
|
|
|
ckie := cookies[0]
|
|
|
|
if ckie.Name != tt.cookieName {
|
|
t.Errorf("wanted cookie named %q, got cookie named %q", tt.cookieName, ckie.Name)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClearCookie(t *testing.T) {
|
|
srv := spawnAnubis(t, Options{})
|
|
rw := httptest.NewRecorder()
|
|
|
|
srv.ClearCookie(rw, CookieOpts{Host: "localhost"})
|
|
|
|
resp := rw.Result()
|
|
|
|
cookies := resp.Cookies()
|
|
|
|
if len(cookies) != 1 {
|
|
t.Errorf("wanted 1 cookie, got %d cookies", len(cookies))
|
|
}
|
|
|
|
ckie := cookies[0]
|
|
|
|
if ckie.Name != anubis.CookieName {
|
|
t.Errorf("wanted cookie named %q, got cookie named %q", anubis.CookieName, ckie.Name)
|
|
}
|
|
|
|
if ckie.MaxAge != -1 {
|
|
t.Errorf("wanted cookie max age of -1, got: %d", ckie.MaxAge)
|
|
}
|
|
}
|
|
|
|
func TestClearCookieWithDomain(t *testing.T) {
|
|
srv := spawnAnubis(t, Options{CookieDomain: "techaro.lol"})
|
|
rw := httptest.NewRecorder()
|
|
|
|
srv.ClearCookie(rw, CookieOpts{Host: "localhost"})
|
|
|
|
resp := rw.Result()
|
|
|
|
cookies := resp.Cookies()
|
|
|
|
if len(cookies) != 1 {
|
|
t.Errorf("wanted 1 cookie, got %d cookies", len(cookies))
|
|
}
|
|
|
|
ckie := cookies[0]
|
|
|
|
if ckie.Name != anubis.CookieName {
|
|
t.Errorf("wanted cookie named %q, got cookie named %q", anubis.CookieName, ckie.Name)
|
|
}
|
|
|
|
if ckie.MaxAge != -1 {
|
|
t.Errorf("wanted cookie max age of -1, got: %d", ckie.MaxAge)
|
|
}
|
|
}
|
|
|
|
func TestClearCookieWithDynamicDomain(t *testing.T) {
|
|
srv := spawnAnubis(t, Options{CookieDynamicDomain: true})
|
|
rw := httptest.NewRecorder()
|
|
|
|
srv.ClearCookie(rw, CookieOpts{Host: "subdomain.xeiaso.net"})
|
|
|
|
resp := rw.Result()
|
|
|
|
cookies := resp.Cookies()
|
|
|
|
if len(cookies) != 1 {
|
|
t.Errorf("wanted 1 cookie, got %d cookies", len(cookies))
|
|
}
|
|
|
|
ckie := cookies[0]
|
|
|
|
if ckie.Name != anubis.CookieName {
|
|
t.Errorf("wanted cookie named %q, got cookie named %q", anubis.CookieName, ckie.Name)
|
|
}
|
|
|
|
if ckie.Domain != "xeiaso.net" {
|
|
t.Errorf("wanted cookie domain %q, got cookie domain %q", "xeiaso.net", ckie.Domain)
|
|
}
|
|
|
|
if ckie.MaxAge != -1 {
|
|
t.Errorf("wanted cookie max age of -1, got: %d", ckie.MaxAge)
|
|
}
|
|
}
|
|
|
|
func TestRenderIndexRedirect(t *testing.T) {
|
|
s := &Server{
|
|
opts: Options{
|
|
PublicUrl: "https://anubis.example.com",
|
|
},
|
|
}
|
|
req := httptest.NewRequest("GET", "/", nil)
|
|
req.Header.Set("X-Forwarded-Proto", "https")
|
|
req.Header.Set("X-Forwarded-Host", "example.com")
|
|
req.Header.Set("X-Forwarded-Uri", "/foo")
|
|
|
|
rr := httptest.NewRecorder()
|
|
s.RenderIndex(rr, req, policy.CheckResult{}, nil, true)
|
|
|
|
if rr.Code != http.StatusTemporaryRedirect {
|
|
t.Errorf("expected status %d, got %d", http.StatusTemporaryRedirect, rr.Code)
|
|
}
|
|
location := rr.Header().Get("Location")
|
|
parsedURL, err := url.Parse(location)
|
|
if err != nil {
|
|
t.Fatalf("failed to parse location URL %q: %v", location, err)
|
|
}
|
|
|
|
scheme := "https"
|
|
if parsedURL.Scheme != scheme {
|
|
t.Errorf("expected scheme to be %q, got %q", scheme, parsedURL.Scheme)
|
|
}
|
|
|
|
host := "anubis.example.com"
|
|
if parsedURL.Host != host {
|
|
t.Errorf("expected url to be %q, got %q", host, parsedURL.Host)
|
|
}
|
|
|
|
redir := parsedURL.Query().Get("redir")
|
|
expectedRedir := "https://example.com/foo"
|
|
if redir != expectedRedir {
|
|
t.Errorf("expected redir param to be %q, got %q", expectedRedir, redir)
|
|
}
|
|
}
|
|
|
|
func TestRenderIndexUnauthorized(t *testing.T) {
|
|
s := &Server{
|
|
opts: Options{
|
|
PublicUrl: "",
|
|
},
|
|
}
|
|
req := httptest.NewRequest("GET", "/", nil)
|
|
rr := httptest.NewRecorder()
|
|
|
|
s.RenderIndex(rr, req, policy.CheckResult{}, nil, true)
|
|
|
|
if rr.Code != http.StatusUnauthorized {
|
|
t.Errorf("expected status %d, got %d", http.StatusUnauthorized, rr.Code)
|
|
}
|
|
if body := rr.Body.String(); body != "Authorization required" {
|
|
t.Errorf("expected body %q, got %q", "Authorization required", body)
|
|
}
|
|
}
|
|
|
|
func TestNoCacheOnError(t *testing.T) {
|
|
pol := loadPolicies(t, "testdata/useragent.yaml", 0)
|
|
srv := spawnAnubis(t, Options{Policy: pol})
|
|
ts := httptest.NewServer(internal.RemoteXRealIP(true, "tcp", srv))
|
|
defer ts.Close()
|
|
|
|
for userAgent, expectedCacheControl := range map[string]string{
|
|
"DENY": "no-store",
|
|
"CHALLENGE": "no-store",
|
|
"ALLOW": "",
|
|
} {
|
|
t.Run(userAgent, func(t *testing.T) {
|
|
req, err := http.NewRequest(http.MethodGet, ts.URL, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
req.Header.Set("User-Agent", userAgent)
|
|
|
|
resp, err := ts.Client().Do(req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if resp.Header.Get("Cache-Control") != expectedCacheControl {
|
|
t.Errorf("wanted Cache-Control header %q, got %q", expectedCacheControl, resp.Header.Get("Cache-Control"))
|
|
}
|
|
})
|
|
}
|
|
}
|