- Go 92.3%
- Shell 6.7%
- Dockerfile 1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
Some checks failed
ci / test (push) Has been cancelled
Reviewed-on: #1 Reviewed-by: jochen <jochen@bocc.de> |
||
| .forgejo/workflows | ||
| deploy | ||
| internal | ||
| .containerignore | ||
| .gitignore | ||
| Containerfile | ||
| go.mod | ||
| main.go | ||
| README.md | ||
mcp-auth-sidecar
Kleiner Sidecar, der einen bestehenden MCP-Server bzw. ein MCP-Gateway für die MCP-Autorisierung (OAuth 2.1) fit macht, ohne einen vollwertigen Identity-Aware-Proxy wie Pomerium. Als Authorization Server dient Keycloak, davor kann eine WAF wie FortiWeb stehen.
Ein Go-Binary ohne Fremdabhängigkeiten (nur Standardbibliothek), Container ca. 10 MB, keine Datenbank.
Was er liefert
| Endpunkt | Zweck |
|---|---|
/.well-known/oauth-protected-resource und /.well-known/oauth-protected-resource/<pfad> |
Protected Resource Metadata (RFC 9728), zeigt auf Keycloak als Authorization Server |
/.well-known/oauth-authorization-server, …/oauth-authorization-server/<issuer-pfad>, …/openid-configuration/<issuer-pfad> |
Spiegel der Keycloak-Discovery (RFC 8414), gecacht. Hilft älteren MCP-Clients und Clients, die „path insertion“ nutzen |
| alle übrigen Pfade (Proxy-Modus) | Bearer-Token prüfen, bei Erfolg an das Upstream weiterreichen, sonst spec-konform 401 mit WWW-Authenticate: Bearer resource_metadata="…" |
/-/healthz, /-/readyz |
Liveness bzw. Readiness (bereit, sobald JWKS geladen) |
Ablauf
sequenceDiagram
participant C as MCP-Client (z. B. Claude)
participant F as FortiWeb
participant S as mcp-auth-sidecar
participant G as MCP-Gateway
participant K as Keycloak
C->>F: POST /mcp (ohne Token)
F->>S: weiter
S-->>C: 401 WWW-Authenticate: resource_metadata=…
C->>S: GET /.well-known/oauth-protected-resource/mcp
S-->>C: authorization_servers: [Keycloak-Realm]
C->>K: Discovery, Dynamic Client Registration, Login (PKCE)
K-->>C: Access Token (aud = MCP-Ressource)
C->>F: POST /mcp, Authorization: Bearer …
F->>S: weiter
S->>S: Signatur (JWKS), iss, aud, exp, typ, Scopes prüfen
S->>G: weiter mit X-Auth-Subject/Email/…
G-->>C: Antwort / SSE-Stream (ungepuffert)
Betriebsarten
proxy (Standard): Der Sidecar sitzt vor dem Gateway und prüft selbst.
Er streamt SSE und Streamable HTTP ohne Pufferung und reicht Mcp-Session-Id
durch. Eingehende Header mit dem Identitäts-Präfix (X-Auth-) werden immer
entfernt, damit sie nicht gefälscht werden können.
metadata: Nur die /.well-known/-Endpunkte. Die WAF routet diese Pfade
zum Sidecar und prüft Tokens selbst. Das funktioniert nur, wenn die WAF bei
fehlendem oder ungültigem Token ein echtes 401 mit korrektem
WWW-Authenticate-Header liefert. Sonst startet der Client den OAuth-Flow nie.
Konfiguration
Alle Variablen haben das Präfix MCPAUTH_. Vorlage: deploy/quadlet/sidecar.env.example.
| Variable | Standard | Beschreibung |
|---|---|---|
RESOURCE_URL |
– (Pflicht) | Öffentliche URL des MCP-Endpunkts, z. B. https://mcp.example.de/mcp |
ISSUER_URL |
– (Pflicht) | Keycloak-Realm, z. B. https://kc.example.de/realms/mcp |
UPSTREAM_URL |
– (Pflicht im Proxy-Modus) | MCP-Gateway, z. B. http://mcp-gateway:3000 |
MODE |
proxy |
proxy oder metadata |
LISTEN_ADDR |
:8080 |
Listen-Adresse |
AUDIENCE |
RESOURCE_URL |
Erlaubte aud-Werte (Komma/Leerzeichen-getrennt) |
REQUIRED_SCOPES |
– | Pflicht-Scopes, sonst 403 insufficient_scope |
SCOPES_SUPPORTED |
REQUIRED_SCOPES |
Für die Metadaten |
RESOURCE_NAME, RESOURCE_DOCUMENTATION |
– | Optionale Metadatenfelder |
JWKS_URL |
aus Discovery | Nur setzen, wenn abweichend |
ALLOWED_ALGS |
RS*, PS*, ES*, EdDSA | Nur asymmetrische Verfahren möglich |
CLOCK_SKEW |
60s |
Toleranz für exp/nbf/iat |
JWKS_REFRESH_INTERVAL |
15m |
Periodisches Neuladen |
JWKS_MIN_REFRESH_INTERVAL |
30s |
Rate-Limit fürs Nachladen bei unbekannter kid (Schlüsselrotation) |
DISCOVERY_CACHE_TTL |
10m |
Cache für Keycloak-Discovery |
HTTP_TIMEOUT |
10s |
Timeout für Keycloak-Abrufe |
SERVE_AS_METADATA |
true |
AS-Metadaten spiegeln |
FORWARD_IDENTITY |
true |
X-Auth-Subject, -Email, -Username, -Client-Id, -Scopes setzen |
HEADER_PREFIX |
X-Auth- |
Präfix der Identitäts-Header |
STRIP_AUTHORIZATION |
false |
Token nicht ans Upstream weitergeben |
PRESERVE_HOST |
false |
Original-Host ans Upstream |
TRUST_FORWARDED_HEADERS |
false |
X-Forwarded-* vom vorgelagerten Proxy übernehmen (hinter FortiWeb: true) |
ALLOW_PREFLIGHT |
true |
CORS-Preflights ohne Token durchlassen |
REQUIRE_BEARER_TYP |
true |
Keycloak-Claim typ muss Bearer sein (blockt ID-/Refresh-Tokens) |
ALLOW_INSECURE_HTTP |
false |
http:// für Resource/Issuer erlauben (nur Tests) |
LOG_LEVEL, LOG_FORMAT |
info, json |
Logging |
Keycloak
deploy/keycloak/setup-realm.sh richtet einen
eigenen Realm per kcadm.sh ein. Das Skript ist erneut ausführbar.
- Client Scope
mcpmit Audience-Mapper aufMCP_RESOURCE_URL, als Realm-Default für neue Clients. - Anonyme Dynamic Client Registration nur für Redirect-URIs auf vertrauenswürdigen Hosts (Standard
claude.ai, Callbackhttps://claude.ai/api/mcp/auth_callback). Die übrigen Keycloak-Default-Policies (Consent Required, Max Clients, Full Scope Disabled) bleiben aktiv. - PKCE S256 für alle Clients des Realms per Client Policy. Ersetzt vorhandene Client Policies des Realms.
Den eigentlichen Login (AD/LDAP, Entra ID, SAML-IdP) bindet man im Realm als User Federation oder Identity Provider an. Keycloak übernimmt dann nur den MCP-spezifischen OAuth-Teil.
FortiWeb davor
- Server-Pool auf den Sidecar zeigen lassen. Die
/.well-known/-Pfade müssen ohne Authentisierung erreichbar sein. - Für SSE: keine Response-Pufferung bzw. Body-Inspection auf
text/event-stream, großzügige Idle-Timeouts. - Header
Authorization,Mcp-Session-IdundWWW-Authenticateunverändert durchreichen. - Optional für Claude-Connectoren: IP-Allowlist auf die veröffentlichten Egress-IPs von Anthropic.
Deployment mit Podman/Quadlet
podman build -t git.bocc.de/claude/mcp-auth-sidecar:0.1.0 --build-arg VERSION=0.1.0 .
podman push git.bocc.de/claude/mcp-auth-sidecar:0.1.0
sudo install -d -m 700 /etc/mcp-auth-sidecar
sudo install -m 600 deploy/quadlet/sidecar.env.example /etc/mcp-auth-sidecar/sidecar.env # anpassen
sudo cp deploy/quadlet/mcp.network deploy/quadlet/mcp-auth-sidecar.container /etc/containers/systemd/
sudo systemctl daemon-reload && sudo systemctl start mcp-auth-sidecar
Das bestehende Gateway kommt mit Network=mcp.network ins selbe Netz und
veröffentlicht selbst keinen Port mehr. Es ist dann nur noch über den Sidecar erreichbar.
Entwicklung
go vet ./...
go test -race ./...
go build -o mcp-auth-sidecar .
Tests decken ab: Signaturprüfung (RS256, PS256, ES256, EdDSA), Ablehnung von
alg=none, manipulierten Payloads, falschen iss/aud, abgelaufenen Tokens und
ID-Tokens, schwachen oder ungültigen JWKs, Schlüsselrotation mit Rate-Limit,
401/403-Challenges, Header-Spoofing, CORS-Preflight und ungepuffertes SSE.
Sicherheitshinweise
- Keine Token-Durchreichung an Dritt-APIs: Das Gateway ist selbst die Ressource. Braucht es weitere APIs, holt es eigene Tokens (z. B. per Token Exchange).
- Tokens werden nie geloggt. Abgelehnte Tokens werden mit Grund, Remote-Adresse und Pfad auf
infoprotokolliert. - Upstream-Port nicht direkt veröffentlichen, sonst lässt sich der Sidecar umgehen.
Offen / zu prüfen
setup-realm.shgegen die eingesetzte Keycloak-Version testen. Admin-API-Details, z. B. der Client-Policy-Conditionany-client, können sich zwischen Versionen unterscheiden.- Basis-Images im
Containerfileper Digest pinnen. - Runner-Label in
.forgejo/workflows/ci.ymlan die eigene Runner-Konfiguration anpassen. - Neuere MCP-Spezifikationen erlauben statt DCR auch Client-ID-Metadata-Dokumente. Ob Keycloak und die genutzten Clients das bereits unterstützen, ist zu prüfen. Am Sidecar ändert das nichts.