Anubis works!
+ +If you see this, everything has gone according to keikaku.
+ +
diff --git a/cmd/anubis/main.go b/cmd/anubis/main.go
index f47acec..47bafd1 100644
--- a/cmd/anubis/main.go
+++ b/cmd/anubis/main.go
@@ -50,8 +50,9 @@ var (
socketMode = flag.String("socket-mode", "0770", "socket mode (permissions) for unix domain sockets.")
robotsTxt = flag.Bool("serve-robots-txt", false, "serve a robots.txt file that disallows all robots")
policyFname = flag.String("policy-fname", "", "full path to anubis policy document (defaults to a sensible built-in policy)")
+ redirectDomains = flag.String("redirect-domains", "", "list of domains separated by commas which anubis is allowed to redirect to. Leaving this unset allows any domain.")
slogLevel = flag.String("slog-level", "INFO", "logging level (see https://pkg.go.dev/log/slog#hdr-Levels)")
- target = flag.String("target", "http://localhost:3923", "target to reverse proxy to")
+ target = flag.String("target", "http://localhost:3923", "target to reverse proxy to, set to an empty string to disable proxying when only using auth request")
healthcheck = flag.Bool("healthcheck", false, "run a health check against Anubis")
useRemoteAddress = flag.Bool("use-remote-address", false, "read the client's IP address from the network request, useful for debugging and running Anubis on bare metal")
debugBenchmarkJS = flag.Bool("debug-benchmark-js", false, "respond to every request with a challenge for benchmarking hashrate")
@@ -195,9 +196,14 @@ func main() {
return
}
- rp, err := makeReverseProxy(*target)
- if err != nil {
- log.Fatalf("can't make reverse proxy: %v", err)
+ var rp http.Handler
+ // when using anubis via Systemd and environment variables, then it is not possible to set targe to an empty string but only to space
+ if strings.TrimSpace(*target) != "" {
+ var err error
+ rp, err = makeReverseProxy(*target)
+ if err != nil {
+ log.Fatalf("can't make reverse proxy: %v", err)
+ }
}
policy, err := libanubis.LoadPoliciesOrDefault(*policyFname, *challengeDifficulty)
@@ -252,6 +258,20 @@ func main() {
slog.Warn("generating random key, Anubis will have strange behavior when multiple instances are behind the same load balancer target, for more information: see https://anubis.techaro.lol/docs/admin/installation#key-generation")
}
+ var redirectDomainsList []string
+ if *redirectDomains != "" {
+ domains := strings.Split(*redirectDomains, ",")
+ for _, domain := range domains {
+ _, err = url.Parse(domain)
+ if err != nil {
+ log.Fatalf("cannot parse redirect-domain %q: %s", domain, err.Error())
+ }
+ redirectDomainsList = append(redirectDomainsList, strings.TrimSpace(domain))
+ }
+ } else {
+ slog.Warn("REDIRECT_DOMAINS is not set, Anubis will only redirect to the same domain a request is coming from, see https://anubis.techaro.lol/docs/admin/configuration/redirect-domains")
+ }
+
s, err := libanubis.New(libanubis.Options{
Next: rp,
Policy: policy,
@@ -261,6 +281,7 @@ func main() {
CookiePartitioned: *cookiePartitioned,
OGPassthrough: *ogPassthrough,
OGTimeToLive: *ogTimeToLive,
+ RedirectDomains: redirectDomainsList,
Target: *target,
WebmasterEmail: *webmasterEmail,
})
diff --git a/docs/docs/CHANGELOG.md b/docs/docs/CHANGELOG.md
index d824edd..128014c 100644
--- a/docs/docs/CHANGELOG.md
+++ b/docs/docs/CHANGELOG.md
@@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Refactor check logic to be more generic and work on a Checker type
- Add more AI user agents based on the [ai.robots.txt](https://github.com/ai-robots-txt/ai.robots.txt) project
- Embedded challenge data in initial HTML response to improve performance
+- Added support to use Nginx' `auth_request` directive with Anubis
+- Added support to allow to restrict the allowed redirect domains
- Whitelisted [DuckDuckBot](https://duckduckgo.com/duckduckgo-help-pages/results/duckduckbot/) in botPolicies
- Improvements to build scripts to make them less independent of the build host
- Improved the OpenGraph error logging
diff --git a/docs/docs/admin/configuration/redirect-domains.mdx b/docs/docs/admin/configuration/redirect-domains.mdx
new file mode 100644
index 0000000..4181143
--- /dev/null
+++ b/docs/docs/admin/configuration/redirect-domains.mdx
@@ -0,0 +1,94 @@
+---
+title: Redirect Domain Configuration
+---
+
+import Tabs from "@theme/Tabs";
+import TabItem from "@theme/TabItem";
+
+Anubis has an HTTP redirect in the middle of its check validation logic. This redirect allows Anubis to set a cookie on validated requests so that users don't need to pass challenges on every page load.
+
+This flow looks something like this:
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant Challenge
+ participant Validation
+ participant Backend
+
+ User->>+Challenge: GET /
+ Challenge->>+User: Solve this challenge
+ User->>+Validation: Here's the solution, send me to /
+ Validation->>+User: Here's a cookie, go to /
+ User->>+Backend: GET /
+```
+
+However, in some cases a sufficiently dedicated attacker could trick a user into clicking on a validation link with a solution pre-filled out. For example:
+
+```mermaid
+sequenceDiagram
+ participant Hacker
+ participant User
+ participant Validation
+ participant Evil Site
+
+ Hacker->>+User: Click on yoursite.com with this solution
+ User->>+Validation: Here's a solution, send me to evilsite.com
+ Validation->>+User: Here's a cookie, go to evilsite.com
+ User->>+Evil Site: GET evilsite.com
+```
+
+If this happens, Anubis will throw an error like this:
+
+```text
+Redirect domain not allowed
+```
+
+## Configuring allowed redirect domains
+
+By default, Anubis will limit redirects to be on the same HTTP Host that Anubis is running on (EG: requests to yoursite.com cannot redirect outside of yoursite.com). If you need to set more than one domain, fill the `REDIRECT_DOMAINS` environment variable with a comma-separated list of domain names that Anubis should allow redirects to.
+
+:::note
+
+These domains are _an exact string match_, they do not support wildcard matches.
+
+:::
+
+Complete example
+
+```nginx
+# /etc/nginx/conf.d/nginx.local.cetacean.club.conf
+
+server {
+ listen 443 ssl;
+ listen [::]:443 ssl;
+ server_name nginx.local.cetacean.club;
+ ssl_certificate /etc/techaro/pki/nginx.local.cetacean.club/tls.crt;
+ ssl_certificate_key /etc/techaro/pki/nginx.local.cetacean.club/tls.key;
+ ssl_protocols TLSv1.2 TLSv1.3;
+ ssl_ciphers HIGH:!aNULL:!MD5;
+
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+
+ location /.within.website/ {
+ proxy_pass http://localhost:8923;
+ auth_request off;
+ }
+
+ location @redirectToAnubis {
+ return 307 /.within.website/?redir=$scheme://$host$request_uri;
+ auth_request off;
+ }
+
+ location / {
+ auth_request /.within.website/x/cmd/anubis/api/check;
+ error_page 401 = @redirectToAnubis;
+ root /usr/share/nginx/html;
+ index index.html index.htm;
+ }
+}
+```
+
+
If this is unset, Anubis may redirect to any domain which could cause security issues in the unlikely case that an attacker passes a challenge for your browser and then tricks you into clicking a link to your domain. |
+| `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`. |
+| `USE_REMOTE_ADDRESS` | unset | If set to `true`, Anubis will take the client's IP from the network socket. For production deployments, it is expected that a reverse proxy is used in front of Anubis, which pass the IP using headers, instead. |
+| `WEBMASTER_EMAIL` | unset | If set, shows a contact email address when rendering error pages. This email address will be how users can get in contact with administrators. |
For more detailed information on configuring Open Graph tags, please refer to the [Open Graph Configuration](./configuration/open-graph.mdx) page.
diff --git a/lib/anubis.go b/lib/anubis.go
index f6445fb..8ca6964 100644
--- a/lib/anubis.go
+++ b/lib/anubis.go
@@ -14,6 +14,7 @@ import (
"net/http"
"net/url"
"os"
+ "slices"
"strconv"
"strings"
"time"
@@ -64,10 +65,11 @@ var (
)
type Options struct {
- Next http.Handler
- Policy *policy.ParsedConfig
- ServeRobotsTXT bool
- PrivateKey ed25519.PrivateKey
+ Next http.Handler
+ Policy *policy.ParsedConfig
+ RedirectDomains []string
+ ServeRobotsTXT bool
+ PrivateKey ed25519.PrivateKey
CookieDomain string
CookieName string
@@ -148,9 +150,10 @@ func New(opts Options) (*Server, error) {
mux.HandleFunc("POST /.within.website/x/cmd/anubis/api/make-challenge", result.MakeChallenge)
mux.HandleFunc("GET /.within.website/x/cmd/anubis/api/pass-challenge", result.PassChallenge)
+ mux.HandleFunc("GET /.within.website/x/cmd/anubis/api/check", result.maybeReverseProxyHttpStatusOnly)
mux.HandleFunc("GET /.within.website/x/cmd/anubis/api/test-error", result.TestError)
- mux.HandleFunc("/", result.MaybeReverseProxy)
+ mux.HandleFunc("/", result.maybeReverseProxyOrPage)
result.mux = mux
@@ -172,6 +175,36 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.mux.ServeHTTP(w, r)
}
+func (s *Server) ServeHTTPNext(w http.ResponseWriter, r *http.Request) {
+ if s.next == nil {
+ redir := r.FormValue("redir")
+ urlParsed, err := r.URL.Parse(redir)
+ if err != nil {
+ templ.Handler(web.Base("Oh noes!", web.ErrorPage("Redirect URL not parseable", s.opts.WebmasterEmail)), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
+ return
+ }
+
+ if len(urlParsed.Host) > 0 && len(s.opts.RedirectDomains) != 0 && !slices.Contains(s.opts.RedirectDomains, urlParsed.Host) {
+ templ.Handler(web.Base("Oh noes!", web.ErrorPage("Redirect domain not allowed", s.opts.WebmasterEmail)), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
+ return
+ } else if urlParsed.Host != r.URL.Host {
+ templ.Handler(web.Base("Oh noes!", web.ErrorPage("Redirect domain not allowed", s.opts.WebmasterEmail)), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
+ return
+ }
+
+ if redir != "" {
+ http.Redirect(w, r, redir, http.StatusFound)
+ return
+ }
+
+ templ.Handler(
+ web.Base("You are not a bot!", web.StaticHappy()),
+ ).ServeHTTP(w, r)
+ } else {
+ s.next.ServeHTTP(w, r)
+ }
+}
+
func (s *Server) challengeFor(r *http.Request, difficulty int) string {
fp := sha256.Sum256(s.priv.Seed())
@@ -187,7 +220,15 @@ func (s *Server) challengeFor(r *http.Request, difficulty int) string {
return internal.SHA256sum(challengeData)
}
-func (s *Server) MaybeReverseProxy(w http.ResponseWriter, r *http.Request) {
+func (s *Server) maybeReverseProxyHttpStatusOnly(w http.ResponseWriter, r *http.Request) {
+ s.maybeReverseProxy(w, r, true)
+}
+
+func (s *Server) maybeReverseProxyOrPage(w http.ResponseWriter, r *http.Request) {
+ s.maybeReverseProxy(w, r, false)
+}
+
+func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request, httpStatusOnly bool) {
lg := slog.With(
"user_agent", r.UserAgent(),
"accept_language", r.Header.Get("Accept-Language"),
@@ -233,7 +274,7 @@ func (s *Server) MaybeReverseProxy(w http.ResponseWriter, r *http.Request) {
switch cr.Rule {
case config.RuleAllow:
lg.Debug("allowing traffic to origin (explicit)")
- s.next.ServeHTTP(w, r)
+ s.ServeHTTPNext(w, r)
return
case config.RuleDeny:
s.ClearCookie(w)
@@ -264,21 +305,21 @@ func (s *Server) MaybeReverseProxy(w http.ResponseWriter, r *http.Request) {
if err != nil {
lg.Debug("cookie not found", "path", r.URL.Path)
s.ClearCookie(w)
- s.RenderIndex(w, r, rule)
+ s.RenderIndex(w, r, rule, httpStatusOnly)
return
}
if err := ckie.Valid(); err != nil {
lg.Debug("cookie is invalid", "err", err)
s.ClearCookie(w)
- s.RenderIndex(w, r, rule)
+ s.RenderIndex(w, r, rule, httpStatusOnly)
return
}
if time.Now().After(ckie.Expires) && !ckie.Expires.IsZero() {
lg.Debug("cookie expired", "path", r.URL.Path)
s.ClearCookie(w)
- s.RenderIndex(w, r, rule)
+ s.RenderIndex(w, r, rule, httpStatusOnly)
return
}
@@ -289,14 +330,14 @@ func (s *Server) MaybeReverseProxy(w http.ResponseWriter, r *http.Request) {
if err != nil || !token.Valid {
lg.Debug("invalid token", "path", r.URL.Path, "err", err)
s.ClearCookie(w)
- s.RenderIndex(w, r, rule)
+ s.RenderIndex(w, r, rule, httpStatusOnly)
return
}
if randomJitter() {
r.Header.Add("X-Anubis-Status", "PASS-BRIEF")
lg.Debug("cookie is not enrolled into secondary screening")
- s.next.ServeHTTP(w, r)
+ s.ServeHTTPNext(w, r)
return
}
@@ -304,7 +345,7 @@ func (s *Server) MaybeReverseProxy(w http.ResponseWriter, r *http.Request) {
if !ok {
lg.Debug("invalid token claims type", "path", r.URL.Path)
s.ClearCookie(w)
- s.RenderIndex(w, r, rule)
+ s.RenderIndex(w, r, rule, httpStatusOnly)
return
}
challenge := s.challengeFor(r, rule.Challenge.Difficulty)
@@ -312,7 +353,7 @@ func (s *Server) MaybeReverseProxy(w http.ResponseWriter, r *http.Request) {
if claims["challenge"] != challenge {
lg.Debug("invalid challenge", "path", r.URL.Path)
s.ClearCookie(w)
- s.RenderIndex(w, r, rule)
+ s.RenderIndex(w, r, rule, httpStatusOnly)
return
}
@@ -329,16 +370,22 @@ func (s *Server) MaybeReverseProxy(w http.ResponseWriter, r *http.Request) {
lg.Debug("invalid response", "path", r.URL.Path)
failedValidations.Inc()
s.ClearCookie(w)
- s.RenderIndex(w, r, rule)
+ s.RenderIndex(w, r, rule, httpStatusOnly)
return
}
slog.Debug("all checks passed")
r.Header.Add("X-Anubis-Status", "PASS-FULL")
- s.next.ServeHTTP(w, r)
+ s.ServeHTTPNext(w, r)
}
-func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, rule *policy.Bot) {
+func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, rule *policy.Bot, returnHTTPStatusOnly bool) {
+ if returnHTTPStatusOnly {
+ w.WriteHeader(http.StatusUnauthorized)
+ w.Write([]byte("Authorization required"))
+ return
+ }
+
lg := slog.With(
"user_agent", r.UserAgent(),
"accept_language", r.Header.Get("Accept-Language"),
@@ -470,6 +517,19 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
timeTaken.Observe(elapsedTime)
response := r.FormValue("response")
+ urlParsed, err := r.URL.Parse(redir)
+ if err != nil {
+ templ.Handler(web.Base("Oh noes!", web.ErrorPage("Redirect URL not parseable", s.opts.WebmasterEmail)), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
+ return
+ }
+
+ if len(urlParsed.Host) > 0 && len(s.opts.RedirectDomains) != 0 && !slices.Contains(s.opts.RedirectDomains, urlParsed.Host) {
+ templ.Handler(web.Base("Oh noes!", web.ErrorPage("Redirect domain not allowed", s.opts.WebmasterEmail)), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
+ return
+ } else if urlParsed.Host != r.URL.Host {
+ templ.Handler(web.Base("Oh noes!", web.ErrorPage("Redirect domain not allowed", s.opts.WebmasterEmail)), templ.WithStatus(http.StatusInternalServerError)).ServeHTTP(w, r)
+ return
+ }
challenge := s.challengeFor(r, rule.Challenge.Difficulty)
diff --git a/lib/anubis_test.go b/lib/anubis_test.go
index 60b7913..baa92a4 100644
--- a/lib/anubis_test.go
+++ b/lib/anubis_test.go
@@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"net/http/httptest"
+ "os"
"testing"
"github.com/TecharoHQ/anubis"
@@ -184,6 +185,7 @@ func TestCookieSettings(t *testing.T) {
}
if resp.StatusCode != http.StatusFound {
+ resp.Write(os.Stderr)
t.Errorf("wanted %d, got: %d", http.StatusFound, resp.StatusCode)
}
diff --git a/package.json b/package.json
index 4041096..afdf705 100644
--- a/package.json
+++ b/package.json
@@ -23,4 +23,4 @@
"postcss-import-url": "^7.2.0",
"postcss-url": "^10.1.3"
}
-}
+}
\ No newline at end of file
diff --git a/test/k8s/cert-manager/selfsigned-issuer.yaml b/test/k8s/cert-manager/selfsigned-issuer.yaml
new file mode 100644
index 0000000..07d2b7b
--- /dev/null
+++ b/test/k8s/cert-manager/selfsigned-issuer.yaml
@@ -0,0 +1,6 @@
+apiVersion: cert-manager.io/v1
+kind: ClusterIssuer
+metadata:
+ name: selfsigned
+spec:
+ selfSigned: {}
\ No newline at end of file
diff --git a/test/k8s/deps/cert-manager.yaml b/test/k8s/deps/cert-manager.yaml
new file mode 100644
index 0000000..f3e17fa
--- /dev/null
+++ b/test/k8s/deps/cert-manager.yaml
@@ -0,0 +1,13 @@
+apiVersion: helm.cattle.io/v1
+kind: HelmChart
+metadata:
+ name: cert-manager
+ namespace: kube-system
+spec:
+ repo: https://charts.jetstack.io
+ chart: cert-manager
+ targetNamespace: cert-manager
+ createNamespace: true
+ set:
+ installCRDs: "true"
+ "prometheus.enabled": "false"
\ No newline at end of file
diff --git a/test/nginx-external-auth/conf.d/default.conf b/test/nginx-external-auth/conf.d/default.conf
new file mode 100644
index 0000000..e9e5a78
--- /dev/null
+++ b/test/nginx-external-auth/conf.d/default.conf
@@ -0,0 +1,25 @@
+server {
+ listen 80;
+ listen [::]:80;
+ server_name nginx.local.cetacean.club;
+
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+
+ location /.within.website/ {
+ proxy_pass http://localhost:8923;
+ auth_request off;
+ }
+
+ location @redirectToAnubis {
+ return 307 /.within.website/?redir=$scheme://$host$request_uri;
+ auth_request off;
+ }
+
+ location / {
+ auth_request /.within.website/x/cmd/anubis/api/check;
+ error_page 401 = @redirectToAnubis;
+ root /usr/share/nginx/html;
+ index index.html index.htm;
+ }
+}
\ No newline at end of file
diff --git a/test/nginx-external-auth/deployment.yaml b/test/nginx-external-auth/deployment.yaml
new file mode 100644
index 0000000..f4b408b
--- /dev/null
+++ b/test/nginx-external-auth/deployment.yaml
@@ -0,0 +1,50 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: nginx-external-auth
+spec:
+ selector:
+ matchLabels:
+ app: nginx-external-auth
+ template:
+ metadata:
+ labels:
+ app: nginx-external-auth
+ spec:
+ volumes:
+ - name: config
+ configMap:
+ name: nginx-cfg
+ containers:
+ - name: www
+ image: nginx:alpine
+ resources:
+ limits:
+ memory: "128Mi"
+ cpu: "500m"
+ requests:
+ memory: "128Mi"
+ cpu: "500m"
+ ports:
+ - containerPort: 80
+ volumeMounts:
+ - name: config
+ mountPath: /etc/nginx/conf.d
+ readOnly: true
+ - name: anubis
+ image: ttl.sh/techaro/anubis-external-auth:latest
+ imagePullPolicy: Always
+ resources:
+ limits:
+ cpu: 500m
+ memory: 128Mi
+ requests:
+ cpu: 250m
+ memory: 128Mi
+ env:
+ - name: TARGET
+ value: " "
+ - name: REDIRECT_DOMAINS
+ value: nginx.local.cetacean.club
+
+
diff --git a/test/nginx-external-auth/ingress.yaml b/test/nginx-external-auth/ingress.yaml
new file mode 100644
index 0000000..6fc8737
--- /dev/null
+++ b/test/nginx-external-auth/ingress.yaml
@@ -0,0 +1,25 @@
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: nginx-external-auth
+ labels:
+ name: nginx-external-auth
+ annotations:
+ cert-manager.io/cluster-issuer: "selfsigned"
+spec:
+ ingressClassName: traefik
+ tls:
+ - hosts:
+ - nginx.local.cetacean.club
+ secretName: nginx-local-cetacean-club-public-tls
+ rules:
+ - host: nginx.local.cetacean.club
+ http:
+ paths:
+ - pathType: Prefix
+ path: "/"
+ backend:
+ service:
+ name: nginx-external-auth
+ port:
+ name: http
diff --git a/test/nginx-external-auth/kustomization.yaml b/test/nginx-external-auth/kustomization.yaml
new file mode 100644
index 0000000..7410f97
--- /dev/null
+++ b/test/nginx-external-auth/kustomization.yaml
@@ -0,0 +1,10 @@
+resources:
+ - deployment.yaml
+ - service.yaml
+ - ingress.yaml
+
+configMapGenerator:
+ - name: nginx-cfg
+ behavior: create
+ files:
+ - ./conf.d/default.conf
diff --git a/test/nginx-external-auth/service.yaml b/test/nginx-external-auth/service.yaml
new file mode 100644
index 0000000..d2e018c
--- /dev/null
+++ b/test/nginx-external-auth/service.yaml
@@ -0,0 +1,13 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: nginx-external-auth
+spec:
+ selector:
+ app: nginx-external-auth
+ ports:
+ - name: http
+ protocol: TCP
+ port: 80
+ targetPort: 80
+ type: ClusterIP
diff --git a/test/nginx-external-auth/start.sh b/test/nginx-external-auth/start.sh
new file mode 100755
index 0000000..044238a
--- /dev/null
+++ b/test/nginx-external-auth/start.sh
@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+# Build container image
+(
+ cd ../.. \
+ && npm ci \
+ && npm run container -- \
+ --docker-repo ttl.sh/techaro/anubis-external-auth \
+ --docker-tags ttl.sh/techaro/anubis-external-auth:latest
+)
+
+kubectl apply -k .
+echo "open https://nginx.local.cetacean.club, press control c when done"
+
+control_c() {
+ kubectl delete -k .
+ exit
+}
+trap control_c SIGINT
+
+sleep infinity
\ No newline at end of file
diff --git a/test/pki/.gitignore b/test/pki/.gitignore
new file mode 100644
index 0000000..c96a04f
--- /dev/null
+++ b/test/pki/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
\ No newline at end of file
diff --git a/test/shared/www/index.html b/test/shared/www/index.html
new file mode 100644
index 0000000..8c55c8c
--- /dev/null
+++ b/test/shared/www/index.html
@@ -0,0 +1,17 @@
+
+
+
If you see this, everything has gone according to keikaku.
+ +This is just a check endpoint for your reverse proxy to use.
+Time | Iters |
---|
Loading...
Loading...
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err }