Files
torben 4548aa161f
Build and Test / verify (push) Successful in 31s
Add FluxCD Copilot prompt for deployment instructions in README
2026-07-12 18:01:21 +02:00

10 KiB

gitea-mcp-forward-auth

Kleiner Go-basierter Traefik-ForwardAuth-Microservice, der Bearer-Tokens gegen eine konfigurierbare Menge gueltiger Tokens prueft.

Verhalten

  • Prueft Authorization: Bearer <token>.
  • Validiert Token gegen SHA-256-Hashes aus:
    • AUTH_PROXY_TOKEN_HASHES_DIR (jede Datei enthaelt genau einen Token-Hash), und/oder
    • AUTH_PROXY_TOKEN_HASHES (kommagetrennte Hash-Liste).
  • Antwortet mit:
    • 200 (leer) bei gueltigem Token,
    • 401 bei fehlendem/ungueltigem Header oder ungueltigem Token.
  • GET /healthz ist immer ohne Auth erreichbar.
  • Loggt jeden Auth-Versuch strukturiert, ohne Klartext-Token.

Sicherheitsaspekte

  • Token-Matching erfolgt auf Basis von SHA-256-Digests mit crypto/subtle.ConstantTimeCompare.
  • Der Service speichert nur Hashes der erlaubten Tokens, keine Klartext-Tokens in der Konfiguration.
  • Token werden nie im Klartext geloggt; es wird nur ein kurzer Fingerprint (sha256:...) geloggt.
  • Start bricht fail-fast ab, wenn keine gueltigen Tokens geladen werden konnten.

Konfiguration (ENV)

  • AUTH_PROXY_LISTEN_ADDR
    • Default: :8080
    • Beispiel: :8080
  • AUTH_PROXY_TOKEN_HASHES_DIR
    • Optional
    • Pfad auf ein Verzeichnis, in dem jede Datei einen SHA-256-Token-Hash enthaelt (z. B. Kubernetes Secret Volume)
  • AUTH_PROXY_TOKEN_HASHES
    • Optional
    • Kommagetrennte Liste von SHA-256-Hashes (64 Hex-Zeichen)
  • Hash-Format
    • abcdef... (64 hex) oder sha256:abcdef... (64 hex)
  • AUTH_PROXY_LOG_LEVEL
    • Default: info
    • Werte wie debug, info, warn, error

Hinweis: Es muss mindestens eine Hash-Quelle (AUTH_PROXY_TOKEN_HASHES_DIR oder AUTH_PROXY_TOKEN_HASHES) konfiguriert sein.

Demo-Daten

Fuer schnelle lokale Tests sind Demo-Hashes im Repo enthalten:

  • Verzeichnis: demo/token-hashes/
  • Datei demo-token-1.sha256 entspricht Klartext-Token: demo-token-1
  • Datei demo-token-2.sha256 entspricht Klartext-Token: demo-token-2

Die Dateien enthalten nur SHA-256-Hashes und koennen gefahrlos eingecheckt werden.

VS Code Tasks (Run + Debug + Checks)

Im Projekt sind vier Tasks hinterlegt:

  • Run authproxy (demo hashes)
    • startet den Server mit go run ./cmd/authproxy
    • nutzt automatisch AUTH_PROXY_TOKEN_HASHES_DIR=${workspaceFolder}/demo/token-hashes
  • Debug authproxy (delve, demo hashes)
    • startet den Server im Delve-Debugger (dlv debug ./cmd/authproxy)
    • nutzt dieselben Demo-ENV-Werte
  • Check authproxy 200 (demo-token-1)
    • prueft per curl, dass ein gueltiger Demo-Token den Status 200 liefert
  • Check authproxy 401 (wrong token)
    • prueft per curl, dass ein ungueltiger Token den Status 401 liefert

Starten in VS Code ueber: Terminal -> Run Task...

Zusaetzlich gibt es eine Launch-Konfiguration fuer Breakpoints:

  • .vscode/launch.json -> F5: Debug authproxy (demo hashes)

F5-Flow:

  1. Fuer normalen Betrieb den Task Run authproxy (demo hashes) starten.
  2. Fuer Debugging direkt die Konfiguration F5: Debug authproxy (demo hashes) mit F5 starten (ohne vorherigen Run-Task).
  3. Optional die Check-Tasks fuer 200 und 401 ausfuehren.

Go Package Registry (Gitea)

Dieses Repo ist auf die Gitea Go Package Registry ausgerichtet.

  • Registry-Muster: https://<gitea-host>/api/packages/{owner}/go
  • In dieser Konfiguration: https://gitea.nehmer.net/api/packages/torben/go

Beispiel lokal:

go env -w GOPROXY="https://gitea.nehmer.net/api/packages/torben/go,https://proxy.golang.org,direct"

Annahme: Deine Angabe https://gitea.example.com/api/packages/{owner}/go ist ein Muster/Template. Implementiert wurde konkret gitea.nehmer.net mit Owner torben.

Healthcheck Security

/healthz muss nicht world-readable sein. Fuer k3s Probes und Prometheus reicht interne Erreichbarkeit im Cluster.

Empfehlung:

  • Service als ClusterIP belassen (bereits in Referenzmanifesten umgesetzt)
  • Keine externe Ingress-Route auf /healthz publizieren
  • Zugriff auf Pod/Service-Netzwerkebene einschränken (z. B. NetworkPolicy), falls eure CNI/Policies das bereits vorsehen

Annahme: In eurem Setup ist der ForwardAuth-Service nur intern erreichbar und wird nicht direkt aus dem Internet exponiert.

Lokal bauen und starten

go test ./... -v
go vet ./...
go build ./cmd/authproxy

AUTH_PROXY_TOKEN_HASHES_DIR="./demo/token-hashes" \
AUTH_PROXY_LISTEN_ADDR=":8080" \
go run ./cmd/authproxy

Token-Hashes erzeugen:

echo -n "my-token-1" | sha256sum | awk '{print $1}'

Startbeispiel mit Hashes:

AUTH_PROXY_TOKEN_HASHES_DIR="./demo/token-hashes" \
AUTH_PROXY_LISTEN_ADDR=":8080" \
go run ./cmd/authproxy

Beispielaufrufe

# healthcheck (ohne Auth)
curl -i http://localhost:8080/healthz

# fehlender Token -> 401
curl -i http://localhost:8080/

# ungueltiger Token -> 401
curl -i -H "Authorization: Bearer wrong" http://localhost:8080/

# gueltiger Token -> 200
curl -i -H "Authorization: Bearer demo-token-1" http://localhost:8080/

Docker

Das Projekt enthaelt ein Multi-Stage-Dockerfile mit statisch gelinktem Binary (CGO_ENABLED=0) und non-root Runtime auf Distroless.

docker build -t gitea-mcp-auth-proxy:dev .
docker run --rm -p 8080:8080 \
	-e AUTH_PROXY_TOKEN_HASHES="<sha256-hash-1>,<sha256-hash-2>" \
	gitea-mcp-auth-proxy:dev

Registry-Image lokal testen

Fuer einen End-to-End-Test gegen die veroeffentlichte Gitea-Container-Registry gibt es das Script scripts/test-registry-image.sh.

Es orientiert sich am Gitea-Flow aus der Container-Registry-Doku:

  • Login gegen gitea.nehmer.net
  • Pull von gitea.nehmer.net/torben/gitea-mcp-auth-proxy:<tag>
  • lokaler Start des gezogenen Images mit den eingecheckten Demo-Hashes
  • HTTP-Pruefungen fuer 200 und 401

Default-Verhalten:

  • User: torben
  • Tag: latest
  • Runtime: automatisch docker, sonst podman

Der Registry-Login fragt das Passwort oder einen PAT interaktiv und unsichtbar ab.

Auth-Handling ist absichtlich ephemeral:

  • Das Script schreibt keine Credentials in ~/.docker/config.json.
  • Fuer docker wird ein temporäres DOCKER_CONFIG-Verzeichnis verwendet.
  • Fuer podman wird eine temporäre REGISTRY_AUTH_FILE verwendet.
  • Beides wird beim Script-Ende automatisch geloescht.

Beispiel:

./scripts/test-registry-image.sh

Bestimmten Tag testen:

./scripts/test-registry-image.sh --tag v1.2.3

Optional auf vorhandene Image-Labels pruefen:

./scripts/test-registry-image.sh \
	--require-label org.opencontainers.image.source \
	--require-label org.opencontainers.image.title=gitea-mcp-auth-proxy

Hinweis: Das Script validiert optionale Labels erst nach dem Pull. Aktuell definiert dieses Repo selbst noch keine OCI-Image-Labels im Build.

CI/CD (Gitea Actions)

  • Build-Workflow: .gitea/workflows/build.yaml
    • Push auf beliebige Branches
    • go vet, go test, go build
    • Kein Registry-Push
  • Release-Workflow: .gitea/workflows/release.yaml
    • Trigger auf Tags v*.*.*
    • Buildx-Remote-Builder und Push in Registry
    • latest nur fuer stabile Tags vX.Y.Z
    • Pre-Releases (z. B. v1.2.3-rc1) werden nicht als latest markiert

Wichtig: Dieses Repository baut und pusht nur Images. Das eigentliche Kubernetes-Deployment erfolgt separat ueber FluxCD Image Automation in einem anderen Repository.

Kubernetes-Referenzmanifeste

Referenzbeispiele fuer lokale Verifikation liegen unter:

  • deploy/k3s/deployment.yaml
  • deploy/k3s/service.yaml
  • deploy/k3s/networkpolicy.yaml

Diese Manifeste sind bewusst minimal und nicht als produktive FluxCD-Quelle gedacht.

FluxCD Copilot Prompt

The following prompt can be used to instruct GitHub Copilot in your FluxCD repository about this service:


Prompt for FluxCD repo Copilot:

I am deploying gitea-mcp-auth-proxy, a Traefik ForwardAuth service that validates Authorization: Bearer <token> headers for gitea-mcp access control.

Image: gitea.nehmer.net/torben/gitea-mcp-auth-proxy:<tag> Published to the Gitea container registry at gitea.nehmer.net. Stable releases use vX.Y.Z tags; latest is only updated for stable releases, never for pre-releases.

How it works:

  • Returns 200 for a valid bearer token, 401 otherwise.
  • GET /healthz is always reachable without auth (use for readiness/liveness probes).
  • Token validation uses SHA-256 hashes only — never plaintext tokens.

Required configuration (env):

  • AUTH_PROXY_LISTEN_ADDR — default :8080
  • AUTH_PROXY_TOKEN_HASHES_DIR — path to a directory where each file contains one SHA-256 token hash; ideal for a Kubernetes Secret volume mount (e.g. /var/run/secrets/auth-proxy)
  • AUTH_PROXY_TOKEN_HASHES — alternative: comma-separated list of SHA-256 hashes (64 hex chars each, optionally prefixed with sha256:)
  • At least one of the two hash sources must be configured; the service exits on startup if no tokens are loaded.
  • AUTH_PROXY_LOG_LEVEL — optional, default info

Secret: Create a Kubernetes Secret named gitea-mcp-auth-proxy-tokens containing one file per allowed token, where each file contains the SHA-256 hash of the token (not the token itself). Mount it at /var/run/secrets/auth-proxy as a read-only volume.

Generate a hash with: echo -n "my-token" | sha256sum | awk '{print $1}'

Deployment constraints:

  • Service must be ClusterIP only — do not expose externally.
  • Do not create a public Ingress route for /healthz.
  • The service is consumed by Traefik as a ForwardAuth middleware; only Traefik and internal probes need network access.
  • Apply a NetworkPolicy that allows ingress only from Traefik (by namespace/pod label) and the kubelet (for probes).
  • Image pull requires credentials for gitea.nehmer.net; configure an imagePullSecret referencing a Secret with registry credentials.

Reference manifests (minimal, non-authoritative) are available at: https://gitea.nehmer.net/torben/gitea-mcp-forward-auth/src/branch/main/deploy/k3s/


Annahmen

  • Annahme: Go-Version ist 1.26.
  • Annahme: Release-Build pusht initial nur linux/amd64.
  • Annahme: Remote BuildKit ist im Runner-Netz unter tcp://<default-gateway>:1234 erreichbar.
  • Annahme: Registry-Pfad ist gitea.nehmer.net/torben/gitea-mcp-auth-proxy.
  • Annahme: Traefik laeuft in kube-system mit Label app.kubernetes.io/name=traefik.
  • Annahme: Prometheus laeuft in monitoring mit Label app.kubernetes.io/name=prometheus.