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/oderAUTH_PROXY_TOKEN_HASHES(kommagetrennte Hash-Liste).
- Antwortet mit:
200(leer) bei gueltigem Token,401bei fehlendem/ungueltigem Header oder ungueltigem Token.
GET /healthzist 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
- Default:
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) odersha256:abcdef...(64 hex)
AUTH_PROXY_LOG_LEVEL- Default:
info - Werte wie
debug,info,warn,error
- Default:
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.sha256entspricht Klartext-Token:demo-token-1 - Datei
demo-token-2.sha256entspricht 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
- startet den Server mit
Debug authproxy (delve, demo hashes)- startet den Server im Delve-Debugger (
dlv debug ./cmd/authproxy) - nutzt dieselben Demo-ENV-Werte
- startet den Server im Delve-Debugger (
Check authproxy 200 (demo-token-1)- prueft per
curl, dass ein gueltiger Demo-Token den Status200liefert
- prueft per
Check authproxy 401 (wrong token)- prueft per
curl, dass ein ungueltiger Token den Status401liefert
- prueft per
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:
- Fuer normalen Betrieb den Task
Run authproxy (demo hashes)starten. - Fuer Debugging direkt die Konfiguration
F5: Debug authproxy (demo hashes)mitF5starten (ohne vorherigen Run-Task). - Optional die Check-Tasks fuer
200und401ausfuehren.
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
ClusterIPbelassen (bereits in Referenzmanifesten umgesetzt) - Keine externe Ingress-Route auf
/healthzpublizieren - 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
200und401
Default-Verhalten:
- User:
torben - Tag:
latest - Runtime: automatisch
docker, sonstpodman
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
dockerwird ein temporäresDOCKER_CONFIG-Verzeichnis verwendet. - Fuer
podmanwird eine temporäreREGISTRY_AUTH_FILEverwendet. - 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
latestnur fuer stabile TagsvX.Y.Z- Pre-Releases (z. B.
v1.2.3-rc1) werden nicht alslatestmarkiert
- Trigger auf Tags
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.yamldeploy/k3s/service.yamldeploy/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 validatesAuthorization: Bearer <token>headers for gitea-mcp access control.Image:
gitea.nehmer.net/torben/gitea-mcp-auth-proxy:<tag>Published to the Gitea container registry atgitea.nehmer.net. Stable releases usevX.Y.Ztags;latestis only updated for stable releases, never for pre-releases.How it works:
- Returns
200for a valid bearer token,401otherwise.GET /healthzis 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:8080AUTH_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 withsha256:)- 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, defaultinfoSecret: Create a Kubernetes Secret named
gitea-mcp-auth-proxy-tokenscontaining 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-proxyas a read-only volume.Generate a hash with:
echo -n "my-token" | sha256sum | awk '{print $1}'Deployment constraints:
- Service must be
ClusterIPonly — 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 animagePullSecretreferencing 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>:1234erreichbar. - Annahme: Registry-Pfad ist
gitea.nehmer.net/torben/gitea-mcp-auth-proxy. - Annahme: Traefik laeuft in
kube-systemmit Labelapp.kubernetes.io/name=traefik. - Annahme: Prometheus laeuft in
monitoringmit Labelapp.kubernetes.io/name=prometheus.