Files
command-center/agent/managed-skills/anubis-guard-chain-flip/SKILL.md
T
Frank 9bfa0be85d chore(skills): promote runbook skills to managed-skills; drop superseded SKILL.md
- 31 new managed skills (anubis/arr/authentik/hermes/freetoken/podman/llm-swap
  runbooks etc.) now tracked.
- Removed superseded skills: omp_harness_config, peon_ping_integration
  (replaced by user-authored peon-ping-* managed skills),
  rocmfp4-strix-benchmark-orchestration.
- New memory-root skills: niri_compositor_ops, omp_git_workflow,
  omp_model_config, quadlet-media-migration, homelab-service-triage,
  hermes-dankclaw-ops, frank-default-large-ops, caddy-auth-dns-playbook.
2026-09-09 16:20:28 -07:00

2.9 KiB

name, description
name description
anubis-guard-chain-flip Runbook for moving a Caddy-fronted service behind the Anubis + Authentik embedded-outpost guard chain on Dankclaw (provider-first ordering, validate gate, verification probes).

Anubis + Authentik guard-chain flip (Dankclaw)

Move a public vhost from reverse_proxy <app>:<port> onto: client → TLS edge → Anubis challenge (:8923) → Authentik embedded outpost decision → return to internal http://:8017 listener → Host-matched handler → real upstream.

Order (never reorder)

  1. Provider first. Create the outpost ProxyProvider before touching routes, or authed traffic hits the outpost error page instead of the app:
    • Write a Python snippet to a local /tmp file that imports authentik.providers.proxy.models.ProxyProvider, constructs it with name, external_host="https://<vhost>/" (exact, trailing slash), internal_host="http://caddy:8017", sets p.pk = None, then p.save().
    • Pipe raw: ssh -o BatchMode=yes dankclaw 'podman exec -i authentik-server ak shell' < /tmp/x.py. Heredoc quoting through ssh SyntaxErrors; the raw-stdin pattern is the proven one (ak shell does exec(sys.stdin.read())).
  2. Route flip in ONE commit/deploy: vhost becomes reverse_proxy anubis:8923 { header_up X-Real-Ip {remote_host} }; inside the http://:8017 listener add @<svc> host <vhost> + handle @<svc> { reverse_proxy <upstream> }.
  3. Deploy via the repo deploy flow (commit first; deploy pushes main itself).

Verification (all three)

  • Unauthenticated probe from client: curl -s https://<vhost>/ → HTTP 200 challenge page, grep body for challenge markers (challenge|not a bot|anubis). A 502 here instead of the challenge = chain mis-wired.
  • Post-auth routing proof: grep the LIVE config inside the container — podman exec caddy sh -c "grep -n -A3 <handler> /etc/caddy/Caddyfile" — after the deploy prints its reload line; confirm the reload actually ran, don't trust exit 0.
  • Outpost reachability: podman exec authentik-server getent hosts caddy resolves the compose DNS alias (10.89.1.x) that internal_host dials.

Pitfalls

  • Probing http://localhost:8017/ from the host returns HTTP 000 (connection refused): the post-auth listener lives in the caddy container's netns and is not bound on host loopback. Probe inside the netns or grep the config instead.
  • Non-browser clients cannot solve the JS challenge. Before flipping, audit service-to-service traffic: ingestion/push paths (e.g. Alloy → loki:3100) must target internal addresses; any backend datasource URL pointing at a public hostname of a guarded service breaks the moment you flip.
  • Exempt-by-construction categories that must stay unguarded: the IdP itself (auth. — its login flow can't sit behind a challenge), git smart-HTTP endpoints (Gitea git clients), services with their own complete auth. Document the exemption in the vhost comment.