13 KiB
Authentifizierung, Client-Verwaltung und Überlastschutz
Betriebshandbuch für den HTTP-Betrieb. Lokal über stdio gibt es keine Authentifizierung und keine Limits pro Client, denn dort startet der Nutzer den Prozess selbst.
- Überblick
- Einrichtung im Pod
- Clients verwalten (CLI und Admin-API)
- Einstellungen pro Client
- Netzwerk-Clients und Proxys
- Abläufe: anlegen, sperren, rotieren
- Überlastschutz
- Sicherheitshinweise
- Ausblick: Keycloak
Überblick
┌──────────────────────────── Pod (Replica 1..N) ────────────────────────────┐
Client ──HTTP──► │ :8080 uvicorn ─► Auth (Token/Netz) ─► Limit pro Client ─► Queue ─► Worker │
│ :9090 Admin-API (nur 127.0.0.1) │
└────────────┬──────────────────────────────────────────────────▲────────────┘
│ schreibt (Lock, atomar) │ liest bei Änderung
▼ │ (alle 5 s geprüft)
/data/clients.json + /data/audit.log (RWX-PVC, alle Replicas)
Es gibt drei Betriebsarten:
| Konfiguration | Verhalten |
|---|---|
MCP_CALC_CLIENTS_FILE gesetzt |
Empfohlen. Clients aus der Datei, änderbar zur Laufzeit per CLI oder Admin-API |
nur MCP_CALC_AUTH_TOKENS=name:token,… |
einfache statische Tokens (z. B. aus einem Secret), Änderung nur per Neustart |
| nichts gesetzt | keine Authentifizierung (Warnung im Log, wenn nicht an localhost gebunden) |
Beide Quellen lassen sich kombinieren, die Client-Namen müssen dann eindeutig sein.
Jeder Request wird einem Client zugeordnet. Der Name erscheint in Logs (tool=… client=…) und
Metriken (client-Label). Bei Überlast ist dadurch sofort sichtbar, wer sie verursacht.
Einrichtung im Pod
| Einstellung | Wert | Zweck |
|---|---|---|
| PVC | RWX, gemountet z. B. unter /data |
gemeinsame Client-Datei für alle Replicas |
MCP_CALC_CLIENTS_FILE |
/data/clients.json |
aktiviert die Authentifizierung; Datei darf anfangs fehlen (= keine Clients) |
MCP_CALC_AUDIT_FILE |
Default /data/audit.log |
Änderungsprotokoll |
MCP_CALC_ADMIN_TOKENS |
ops:<langes-zufälliges-token> aus einem Secret |
aktiviert die Admin-API; mehrere Admins kommagetrennt |
MCP_CALC_ADMIN_HOST / _PORT |
127.0.0.1 / 9090 |
Admin-API nur im Pod erreichbar (siehe unten) |
MCP_CALC_TRUSTED_PROXIES |
IP/CIDR des OpenShift-Routers | nur nötig für Netz-Einschränkungen hinter der Route |
Admin-Token erzeugen: python -c "import secrets; print(secrets.token_urlsafe(32))".
Zugriff auf die Admin-API. Mit dem Default 127.0.0.1 ist sie nur aus dem Pod heraus erreichbar:
oc port-forward deploy/mcp-calculator 9090:9090 # dann lokal: http://localhost:9090/admin/clients
# oder direkt die CLI im Pod:
oc exec deploy/mcp-calculator -- mcp-calculator-admin list
Soll ein anderer Dienst im Cluster die API nutzen, MCP_CALC_ADMIN_HOST=0.0.0.0 setzen, einen eigenen
internen Service ohne Route anlegen und per NetworkPolicy einschränken.
Wartung der Datei. Alle Replicas lesen /data/clients.json. Geschrieben wird unter einer exklusiven
POSIX-Sperre (fcntl.lockf, funktioniert auf NFS und CephFS) und atomar per Umbenennen, sodass nie eine
halb geschriebene Datei gelesen wird. Ist die Datei nach einer Änderung ungültig (z. B. Tippfehler bei
Handbearbeitung), behalten die Replicas den letzten gültigen Stand und loggen einen Fehler. Beim
Start führt eine ungültige Datei dagegen zum Abbruch. Die Datei gehört ins Backup.
Clients verwalten
CLI
export MCP_CALC_CLIENTS_FILE=/data/clients.json # im Pod bereits gesetzt
mcp-calculator-admin create opencode-team-a --max-concurrent 4 --expires 2027-03-31 --note "Team A, IDE"
mcp-calculator-admin create reporting-service --networks 10.130.4.0/24
mcp-calculator-admin create legacy-batch --network-only --networks 10.131.2.15
mcp-calculator-admin list
mcp-calculator-admin show opencode-team-a
mcp-calculator-admin disable opencode-team-a # sperren
mcp-calculator-admin enable opencode-team-a
mcp-calculator-admin set opencode-team-a --max-concurrent 8 --expires never --networks none
mcp-calculator-admin rotate opencode-team-a # neues Token, altes sofort ungültig
mcp-calculator-admin delete opencode-team-a --yes
mcp-calculator-admin --json list # maschinenlesbar
Beispielausgabe von list:
NAME STATUS AUTH LIMIT EXPIRES TOKEN NETWORKS
legacy-batch active network default - - 10.131.2.15/32
opencode-team-a active token 4 2027-03-31 mcpc_kFD5… -
reporting-service disabled token default - mcpc_x81Q… 10.130.4.0/24
Admin-API
Alle Aufrufe brauchen Authorization: Bearer <admin-token>.
| Methode | Pfad | Body | Ergebnis |
|---|---|---|---|
GET |
/admin/clients |
– | Liste aller Clients (ohne Hashes) |
POST |
/admin/clients |
{"name", "auth"?, "max_concurrent"?, "expires"?, "allowed_networks"?, "note"?} |
201, Client + Token (einmalig) |
GET |
/admin/clients/{name} |
– | ein Client |
PATCH |
/admin/clients/{name} |
{"enabled"?, "max_concurrent"?, "expires"?, "allowed_networks"?, "note"?} |
geänderter Client |
POST |
/admin/clients/{name}/rotate |
– | Client + neues Token (einmalig) |
DELETE |
/admin/clients/{name} |
– | {"deleted": name} |
GET |
/healthz |
– | ohne Token |
Fehler: 400 ungültige Eingabe, 401 fehlendes/falsches Admin-Token, 404 unbekannt, 409 existiert bereits.
A="Authorization: Bearer $ADMIN_TOKEN"
curl -s -H "$A" -H 'Content-Type: application/json' -X POST localhost:9090/admin/clients \
-d '{"name": "agent-reporting", "max_concurrent": 2, "expires": "2027-06-30"}'
curl -s -H "$A" -H 'Content-Type: application/json' -X PATCH localhost:9090/admin/clients/agent-reporting \
-d '{"enabled": false}'
Die Replica, die den Admin-Aufruf bearbeitet, übernimmt die Änderung sofort. Alle anderen Replicas
folgen nach spätestens MCP_CALC_CLIENTS_RELOAD_SECONDS (Default 5 s).
Clients verwenden
Authorization: Bearer mcpc_… (Standard, z. B. opencode "headers", Cline "headers")
X-API-Key: mcpc_… (Alternative für Dienste, die Authorization nicht setzen können)
Einstellungen pro Client
| Feld | Default | Bedeutung |
|---|---|---|
auth |
token |
token = Token nötig; network = nur über die Quell-IP (siehe unten) |
enabled |
true |
false sperrt den Client, ohne ihn zu löschen (Antwort 403 client is disabled) |
expires |
– | letzter gültiger Tag (UTC), danach 403 API token has expired |
max_concurrent |
Server-Default | gleichzeitige Berechnungen dieses Clients; 0 = unbegrenzt; nicht gesetzt = MCP_CALC_CLIENT_MAX_CONCURRENT |
allowed_networks |
– | IPs/CIDRs, aus denen der Client zugreifen darf; leer = überall |
note |
– | Freitext (Team, Ansprechpartner, Zweck) |
token_hint |
automatisch | die ersten Zeichen des Tokens zur Wiedererkennung (mcpc_kFD5…) |
created, updated |
automatisch | Zeitstempel |
Netzwerk-Clients und Proxys
Für einen anderen Dienst gibt es zwei Möglichkeiten:
- Token mit Netz-Einschränkung (empfohlen):
create reporting-service --networks 10.130.4.0/24. Das Token gilt nur aus diesem Netz. Ein gestohlenes Token ist von anderswo wertlos. - Nur Netz (
--network-only): kein Token, Zugang allein über die Quell-IP. Das ist bequem, aber schwächer:- In OpenShift wechseln Pod-IPs. Ein Netz muss daher den ganzen Bereich abdecken, aus dem der Dienst kommt.
- Hinter der Route sieht der Server nur die IP des Routers. Die echte Client-IP steht in
X-Forwarded-For, und der Server wertet sie nur aus, wenn der direkte Absender inMCP_CALC_TRUSTED_PROXIESsteht. Sonst könnte jeder den Header fälschen. - Innerhalb des Clusters (Aufruf über den Service, ohne Route) sieht der Server die echte Pod-IP.
- Ergänzend sollte eine NetworkPolicy den Zugriff auf Namespace- oder Label-Ebene begrenzen.
Reihenfolge der Prüfung: Wird ein Token mitgeschickt, muss es gültig sein. Ein falsches Token wird auch aus einem freigegebenen Netz abgelehnt. Nur ohne Token greifen Netzwerk-Clients, bei mehreren Treffern gewinnt das spezifischste Netz.
Signierte Header (HMAC) sind vorgesehen, aber nicht umgesetzt, siehe BACKLOG.md.
Abläufe
Neuer Nutzer oder Dienst: create, das Token sicher übergeben (Passwort-Manager, Secret) und
im Client als Header eintragen. Das Token lässt sich nicht erneut anzeigen. Geht es verloren: rotate.
Sofort sperren (z. B. Token geleakt oder Agent läuft Amok): disable <name>. Das wirkt auf der
bearbeitenden Replica sofort, auf allen anderen nach spätestens 5 s. Danach rotate und das neue
Token ausgeben, zum Schluss enable.
Regelmäßig rotieren: expires setzen. Vor Ablauf rotate, dann set --expires <neues Datum>.
Überblick: list bzw. die Metriken mcp_calc_clients{status} und
mcp_calc_auth_failures_total{reason}. Häufige invalid_token-Fehler deuten auf falsch
konfigurierte Clients oder Rateversuche hin.
Audit: /data/audit.log, eine JSON-Zeile pro Änderung, z. B.
{"time": "2026-09-11T17:30:42+00:00", "actor": "admin:ops", "action": "create", "client": "demo", "details": {"max_concurrent": 2, "token_hint": "mcpc_cv1k"}}
actor ist admin:<name> (Admin-API) oder cli:<user> (CLI). Tokens und Hashes werden nie protokolliert.
Überlastschutz
Mehrere Schutzschichten greifen nacheinander. Jede lehnt früh und billig ab, statt Last aufzustauen:
| # | Schicht | Einstellung (Default) | Wirkung bei Überschreitung | Metrik-Status |
|---|---|---|---|---|
| 1 | Verbindungen | MCP_CALC_MAX_CONNECTIONS (200) |
HTTP 503 von uvicorn |
– |
| 2 | Limit pro Client | MCP_CALC_CLIENT_MAX_CONCURRENT (10) bzw. max_concurrent |
Tool-Fehler „client busy“ | busy |
| 3 | Gesamtkapazität | MCP_CALC_WORKERS + MCP_CALC_MAX_QUEUE (2 + 20) |
Tool-Fehler „server busy“ | busy |
| 4 | Wartezeit in der Queue | MCP_CALC_MAX_QUEUE_WAIT_SECONDS (30) |
Tool-Fehler „no worker became free“ | busy |
| 5 | Rechenzeit | MCP_CALC_TIMEOUT_SECONDS (10) |
Worker wird beendet, Tool-Fehler „timeout“ | timeout |
| 6 | Verbindungsabbruch | immer aktiv | wartende Berechnung verworfen, laufende abgebrochen (Worker beendet) | cancelled |
Die Ablehnungen der Schichten 2–5 sind Tool-Fehler mit Hinweis „retry“. Das LLM bzw. der Agent sieht, dass es später erneut versuchen soll. Schicht 6 verhindert, dass Berechnungen weiterlaufen, auf deren Ergebnis niemand mehr wartet.
Alle Zähler gelten pro Replica. Das passt zum Ziel Überlastschutz: Jede Replica schützt sich selbst, ein gemeinsamer Zähler ist nicht nötig. Mit mehr Replicas steigt die Gesamtkapazität linear.
Hinweise zur Dimensionierung:
CLIENT_MAX_CONCURRENTkleiner alsWORKERS + MAX_QUEUEwählen, sonst kann ein einzelner Client alles belegen.MAX_QUEUE_WAIT_SECONDSunter dem Timeout der Clients halten, sonst warten Anfragen, die niemand mehr abholt. Die Schicht 6 fängt das zusätzlich ab.- Alarm z. B. auf
mcp_calc_inflight_computations / mcp_calc_capacity > 0.8oder aufrate(mcp_calc_tool_calls_total{status="busy"}[5m]) > 0, siehe README, Abschnitt Monitoring. - Lastspitzen einzelner Clients erkennt man an
mcp_calc_client_inflight_computations{client}undmcp_calc_tool_calls_total{client, status="busy"}.
Sicherheitshinweise
- Nur Hashes gespeichert: Wer die Datei lesen kann, kann damit nicht zugreifen, aber Clients anlegen oder entsperren, wenn er schreiben kann. PVC-Zugriff daher auf den Pod beschränken. Die Datei wird mit Rechten
0640geschrieben. - Tokens sind 256 Bit zufällig (
mcpc_+ 43 Zeichen). Admin-Tokens mindestens ebenso lang wählen (Mindestlänge 16 Zeichen wird erzwungen). - Admin-API nie über eine Route veröffentlichen. Default ist
127.0.0.1. /healthz,/readyzund/metricssind ohne Token erreichbar. Sie enthalten keine Rechen-Eingaben,/metricsaber Client-Namen. Bei Bedarf per NetworkPolicy einschränken oderMCP_CALC_METRICS=falsesetzen.- Abgelehnte Anfragen werden mit Grund, Client (falls bekannt) und IP geloggt (
auth rejected: reason=…).
Ausblick: Keycloak
Geplant ist die Prüfung von Keycloak-JWTs (OIDC) über den TokenVerifier des MCP SDK, zusätzlich zu den
API-Tokens. Die Identität aus dem JWT (z. B. azp/Client-ID oder ein Claim) wird dann genauso wie
heute ein Client behandelt: gleiche Limits, gleiche Metriken. API-Tokens bleiben für Dienste und
Clients, die kein OIDC können. Offene Frage vor der Umsetzung: ob opencode und Cline den OAuth-Ablauf
von MCP gegen Keycloak unterstützen. Details siehe BACKLOG.md.