Zum Inhalt

Homelab MCP – Netzwerk-Authentifizierung

Stand 08.09.2026: OAuth-Resource-Server, Trusted-Proxy-Modell und MCP-Tool-Audit des neuen homelab-mcp sind implementiert, automatisiert getestet und im aktuellen GitHub-Stand enthalten. CT 109 nutzt bereits den Audit-Pfad live, bleibt aber weiterhin ausschließlich über Loopback erreichbar, bis ein realer Authorization Server und der endgültige Ingress festgelegt sind.

Ziel

Der neue MCP-Endpunkt soll später nicht nur durch einen vorgeschalteten Tunnel geschützt werden, sondern selbst als standardkonformer OAuth-Resource-Server arbeiten. Zusätzlich darf er Forwarding-Header nur von explizit bekannten Reverse Proxys übernehmen. Jeder ausgeführte MCP-Tool-Aufruf muss anschließend über den privileggetrennten Audit-Writer nachvollziehbar sein.

Der Zugriffspfad ist technisch umgesetzt als:

flowchart LR
    CLIENT["MCP-Client"] --> PROXY["vertrauenswürdiger Reverse Proxy"]
    PROXY --> FORWARD["Proxy-IP · HTTPS · öffentlicher Host"]
    FORWARD --> TOKEN["Bearer Access Token"]
    TOKEN --> VERIFY["JWT/JWKS-Prüfung"]
    VERIFY --> META["Issuer · Audience · Ablauf · Scope"]
    META --> AUTH["ctx.http.authInfo"]
    AUTH --> POLICY["MCP Default-Deny-Policy"]
    POLICY --> AUDIT["Tool-Audit<br/>attempt"]
    AUDIT --> TOOL["freigegebenes Plugin-Tool"]
    TOOL --> RESULT["Audit<br/>success / error"]

Ein gültiges OAuth-Token ersetzt damit nicht die Proxy-Prüfung, die Tool-Policy oder das persistente Audit. Alle Schichten müssen erfolgreich durchlaufen werden.

OAuth-Resource-Server

Der neue Server orientiert sich am aktuellen MCP-v2-Authorization-Modell:

  • MCP-Server = OAuth-2.1-Resource-Server;
  • Access Token ausschließlich über Authorization: Bearer ...;
  • Protected Resource Metadata nach RFC 9728;
  • WWW-Authenticate bei 401/403;
  • Resource-/Audience-Bindung nach RFC 8707;
  • Token-Passthrough zu nachgelagerten Diensten ist verboten.

Für einen späteren externen Endpunkt wie beispielsweise:

https://mcp.example.com/mcp

wird die Resource-Metadaten-URL bereitgestellt unter:

https://mcp.example.com/.well-known/oauth-protected-resource/mcp

Zusätzlich wird der Root-Fallback unterstützt:

https://mcp.example.com/.well-known/oauth-protected-resource

JWT/JWKS-Verifier

Die Implementierung validiert JWT-Access-Tokens gegen einen entfernten JWKS-Endpunkt.

Geprüft werden:

  • kryptografische Signatur;
  • erlaubter Signaturalgorithmus;
  • exakter Issuer;
  • exakte Audience beziehungsweise MCP-Resource;
  • Ablaufzeitpunkt exp;
  • maximale Tokengröße;
  • verwendbare Client-Identität;
  • OAuth-Scopes.

Erlaubte Signaturalgorithmen:

RS256
ES256
EdDSA

Die Client-Identität wird in dieser Reihenfolge übernommen:

client_id
azp
sub

Scopes werden aus scope sowie gängigen scp-Darstellungen gelesen.

MCP-AuthInfo

Nach erfolgreicher Prüfung wird die Identität an den offiziellen MCP-Node-Adapter übergeben und steht im Tool-Handler unter:

ctx.http.authInfo

Dort stehen unter anderem zur Verfügung:

  • clientId;
  • scopes;
  • expiresAt;
  • resource;
  • minimale Issuer-/Subject-Metadaten;
  • geprüfte Proxy- und Client-IP-Metadaten.

Der rohe Access Token darf nicht in Logs oder Audit-Ereignisse übernommen werden.

Protected Resource Metadata

Beispiel:

{
  "resource": "https://mcp.example.com/mcp",
  "authorization_servers": [
    "https://auth.example.com"
  ],
  "bearer_methods_supported": [
    "header"
  ],
  "scopes_supported": [
    "mcp"
  ]
}

Fehlt ein Token oder ist es ungültig, antwortet der Server mit 401 und einer Bearer-Challenge einschließlich resource_metadata. Fehlt ein benötigter Scope, folgt 403 mit insufficient_scope.

OAuth-Konfiguration

OAuth bleibt vollständig deaktiviert, solange keine OAuth-Umgebungsvariablen gesetzt sind. Sobald eine Pflichtvariable gesetzt ist, müssen alle Pflichtvariablen vorhanden sein; eine Teilkonfiguration führt beim Start zu einem Fehler.

Pflichtvariablen:

HOMELAB_MCP_OAUTH_RESOURCE_URL
HOMELAB_MCP_OAUTH_ISSUER
HOMELAB_MCP_OAUTH_JWKS_URI

Optional:

HOMELAB_MCP_OAUTH_REQUIRED_SCOPES

Standard-Scope:

mcp

Generisches Beispiel:

Environment="HOMELAB_MCP_OAUTH_RESOURCE_URL=https://mcp.example.com/mcp"
Environment="HOMELAB_MCP_OAUTH_ISSUER=https://auth.example.com"
Environment="HOMELAB_MCP_OAUTH_JWKS_URI=https://auth.example.com/.well-known/jwks.json"
Environment="HOMELAB_MCP_OAUTH_REQUIRED_SCOPES=mcp"

Diese Werte sind keine Secrets. Client-Secrets oder private Schlüssel gehören weiterhin ausschließlich in den Secret Store beziehungsweise in die sichere lokale Ablage.

Trusted-Proxy-Modell

Nichtlokaler Betrieb darf nur über explizit freigegebene Reverse Proxys erfolgen.

Die direkten Proxy-IP-Adressen werden konfiguriert über:

HOMELAB_MCP_TRUSTED_PROXIES

Generisches Beispiel:

Environment="HOMELAB_MCP_TRUSTED_PROXIES=192.0.2.10,192.0.2.11"

Die Anwendung prüft zuerst die tatsächliche TCP-Gegenstelle. Forwarding-Header werden erst ausgewertet, wenn diese direkte Gegenstelle in der Proxy-Allowlist steht. Ein normaler Client kann sich deshalb nicht durch selbst gesetzte X-Forwarded-*-Header als Proxy ausgeben.

HTTPS und öffentlicher Host

Der Proxy muss melden:

X-Forwarded-Proto: https

X-Forwarded-Host muss exakt zum Host von HOMELAB_MCP_OAUTH_RESOURCE_URL passen. Fehlt der Header, muss der normale Host-Header exakt passen. Mehrdeutige, kommagetrennte Forwarding-Werte werden abgewiesen.

Client-IP

Eine Client-IP wird nur übernommen, wenn dafür ausdrücklich ein einzelner Header konfiguriert ist:

HOMELAB_MCP_CLIENT_IP_HEADER

Für einen Cloudflare-Ingress wäre beispielsweise möglich:

Environment="HOMELAB_MCP_CLIENT_IP_HEADER=cf-connecting-ip"

Auch dieser Header wird ausschließlich nach erfolgreicher Proxy-IP-Prüfung ausgewertet und muss eine einzelne gültige IPv4- oder IPv6-Adresse enthalten. X-Forwarded-For wird absichtlich nicht pauschal vertraut.

Netzwerkdaten im Auth-Kontext

Nach erfolgreicher Proxy- und OAuth-Prüfung stehen zusätzlich zur Verfügung:

ctx.http.authInfo.extra.network.proxyAddress
ctx.http.authInfo.extra.network.clientAddress

Plugins müssen damit keine ungeprüften HTTP-Header selbst auswerten.

MCP-Tool-Audit

Das Tool-Audit ist inzwischen live auf CT 109 aktiv. Jeder MCP-Tool-Aufruf läuft nach dem Muster:

attempt -> Tool-Ausführung -> success / error

Der Audit-Actor wird aus der bereits validierten Identität aufgebaut:

OAuth clientId
+ vertrauenswürdige Client-IP
+ Plugin
+ Toolname
+ Risikoklasse
+ Ergebnis

Tool-Argumente und Tool-Antworten werden nicht in das äußere Audit kopiert.

Ist bereits der attempt-Eintrag nicht schreibbar, wird das Tool nicht ausgeführt. Schlägt nur der abschließende Audit-Eintrag fehl, nachdem die Aktion bereits lief, liefert MCP ausdrücklich den Hinweis, den Aufruf nicht automatisch zu wiederholen.

Der persistente Audit-Pfad auf CT 109:

homelab-mcp
  -> /run/homelab-mcp-audit/audit.sock
  -> homelab-mcp-audit-writer.service
  -> /var/log/homelab-mcp/YYYY-MM-DD.jsonl

Geprüfte Rechte:

/run/homelab-mcp-audit/audit.sock  root:homelab-mcp 0660
/var/log/homelab-mcp/             root:homelab-mcp 0750
/var/log/homelab-mcp/*.jsonl      root:homelab-mcp 0640

Der Laufzeitbenutzer kann den Socket nutzen, die Audit-Dateien aber nicht direkt verändern oder löschen.

Bind-Policy

Loopback ist weiterhin ohne Remote-Authentifizierung zulässig:

127.0.0.1
::1
localhost

Eine Bindung außerhalb von Loopback wird nur zugelassen, wenn alle drei Voraussetzungen erfüllt sind:

  1. vollständige OAuth-Resource-Server-Konfiguration;
  2. vollständige Trusted-Proxy-Konfiguration;
  3. persistentes Socket-Audit.

Fehlt eine dieser Schichten, verweigert der Dienst den Start.

Unsicherer HTTP-Testmodus

Für automatisierte lokale OAuth-Tests existiert:

HOMELAB_MCP_OAUTH_ALLOW_INSECURE_HTTP=1

Dieser Modus ist ausschließlich für Loopback erlaubt und darf nicht produktiv verwendet werden.

Mehrstufige Berechtigung

Der endgültige Zugriffspfad lautet:

vertrauenswürdiger Proxy
  -> HTTPS / öffentlicher Host
  -> OAuth Token
  -> Signatur / Issuer / Audience / Ablauf
  -> erforderlicher OAuth-Scope
  -> authentifizierter Client
  -> Plugin aktiviert?
  -> interne Capability verfügbar?
  -> Toolklasse freigegeben?
  -> expliziter Tool-Override?
  -> Audit-Intent erfolgreich?
  -> Ausführung
  -> Audit-Ergebnis

Damit kann ein Client ein technisch gültiges Token mit Scope mcp besitzen und trotzdem keinerlei Proxmox-Schreibrechte haben.

Automatisierte Tests

Der Auth-, Proxy- und Audit-Block ist mit echten, zur Laufzeit erzeugten RSA-Schlüsseln, temporärem JWKS-Server und dem offiziellen MCP-v2-Client getestet.

Abgedeckt sind unter anderem:

  • gültiges JWT;
  • falscher Issuer;
  • falsche Audience;
  • abgelaufenes Token;
  • Scope-Auswertung;
  • fehlender Bearer-Header → 401;
  • unzureichender Scope → 403;
  • RFC-9728-Metadaten;
  • offizieller MCP-v2-Client mit Bearer-JWT;
  • explizite Proxy-IP-Allowlist;
  • Prüfung von HTTPS und öffentlichem Host;
  • optionale vertrauenswürdige Client-IP;
  • vollständiger End-to-End-Pfad Trusted Proxy → OAuth → MCP;
  • Direktzugriff mit gültigem Token ohne korrekte Proxy-Metadaten wird abgewiesen;
  • Tool-Audit ohne Argumente/Antworten;
  • Tool wird bei fehlendem Audit-Intent nicht ausgeführt;
  • Live-Audit auf CT 109 mit attempt und success für system_status.

Stand auf CT 109

Der aktuelle GitHub-Stand mit OAuth, Trusted Proxy und Tool-Audit ist auf CT 109 installiert.

Live aktiv sind:

  • homelab-mcp.service;
  • homelab-mcp-audit-writer.service;
  • Socket-Audit;
  • ausschließlich das read-only Tool system_status.

Noch nicht produktiv konfiguriert sind OAuth und Trusted Proxy. Deshalb bleibt der MCP-Endpunkt:

127.0.0.1:3000/mcp

Warum noch keine LAN-/Cloudflare-Freigabe?

Die generischen Schutzschichten sind fertig. Offen ist die konkrete produktive Einbindung:

  1. tatsächlichen OAuth-/OIDC-Authorization-Server auswählen und konfigurieren;
  2. endgültigen Cloudflare-/Reverse-Proxy-Ingress zu CT 109 festlegen;
  3. konkrete Proxy-IP-Allowlist für die DieBrocks-Installation setzen;
  4. endgültigen öffentlichen Resource-Host setzen;
  5. Host-Firewall auf CT 109 so begrenzen, dass ein nichtlokaler MCP-Port nur vom vorgesehenen Proxy erreichbar wäre;
  6. Admin-HTTP-Zugang separat absichern, bevor er remote freigegeben wird.

Nächster Entwicklungsschritt

Als Nächstes wird die interne SSH-Capability umgesetzt und daran das zunächst rein lesende Proxmox-Plugin angebunden. SSH bleibt dabei eine interne Transportfähigkeit und wird nicht als allgemeines MCP-Tool exponiert.