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

280 lines
10 KiB
Markdown

# 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:
```bash
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
```bash
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:
```bash
echo -n "my-token-1" | sha256sum | awk '{print $1}'
```
Startbeispiel mit Hashes:
```bash
AUTH_PROXY_TOKEN_HASHES_DIR="./demo/token-hashes" \
AUTH_PROXY_LISTEN_ADDR=":8080" \
go run ./cmd/authproxy
```
## Beispielaufrufe
```bash
# 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.
```bash
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:
```bash
./scripts/test-registry-image.sh
```
Bestimmten Tag testen:
```bash
./scripts/test-registry-image.sh --tag v1.2.3
```
Optional auf vorhandene Image-Labels pruefen:
```bash
./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`.