Caputchin
Proxy-Seiten-Gate

Reverse-Proxy-Rezepte und der Callback-Vertrag

Als Markdown ansehen

Jeder Reverse-Proxy braucht dieselben zwei Teile: eine Autorisierer-Prüfung, die Caputchin fragt, ob eine Anfrage einen gültigen Pass trägt, und einen Callback auf deinem Origin, der einen gelösten Pass in das cpt_gate-Cookie verwandelt. Die nginx-Durchgehung zeigt die Form von Ende zu Ende; diese Seite gibt die anderen Proxys, den genauen Callback-Vertrag, und ein volles Authelia-Beispiel. Ersetze YOUR_SITE_KEY durchgehend durch deinen öffentlichen Key.

Die Endpunkte sind überall dieselben:

  • Autorisierer: GET https://verify.caputchin.com/v1/gate/authz?site=YOUR_SITE_KEY, mit weitergeleitetem Cookie-Header des Besuchers. 204 heißt erlauben, 401 heißt Aufgabe.
  • Aufgabe: leite den Browser zu https://verify.caputchin.com/v1/gate/challenge?site=YOUR_SITE_KEY&return=<die ursprüngliche URL> um.

Der Autorisierer antwortet nur 204 oder 401. Alles andere, Timeout eingeschlossen, heißt, dass Caputchin nicht erreichbar ist, und was dann passiert, ist dein Fail-Modus: die Anfrage blockieren (fail closed, sicherer für ein Login-Portal) oder sie durchlassen (fail open). Setz ihn am Site-Key, und das erzeugte Snippet passt dazu. Gib der Autorisierer-Subrequest ein kurzes Timeout, damit ein Ausfall nicht jede Anfrage aufhalten kann, während er entscheidet.

Traefik

Nutze eine forwardAuth-Middleware für den Autorisierer. Traefik leitet den eingehenden Cookie-Header standardmäßig weiter. Bei einem 401 von der Middleware leite den Browser zur Aufgabe um (eine Error-Page-Middleware, die auf eine kleine Route zeigt, die den 302 ausgibt, oder behandle den 401 an deinem Edge).

http:
  middlewares:
    cpt-gate:
      forwardAuth:
        address: "https://verify.caputchin.com/v1/gate/authz?site=YOUR_SITE_KEY"
        # The 401 from forwardAuth is your signal to send the browser to:
        #   https://verify.caputchin.com/v1/gate/challenge?site=YOUR_SITE_KEY&return=<original-url>
  routers:
    portal:
      rule: "Host(`auth.example.com`)"
      middlewares: ["cpt-gate"]
      service: your-app

Caddy

Nutze forward_auth für den Autorisierer und handle_errors, um den 401 in eine Umleitung zu verwandeln. Das ist ein Ausgangspunkt; pass die Direktiven-Syntax an deine Caddy-Version an.

auth.example.com {
  forward_auth https://verify.caputchin.com {
    uri /v1/gate/authz?site=YOUR_SITE_KEY
    copy_headers Cookie
  }
  handle_errors {
    @challenge expression {http.error.status_code} == 401
    redir @challenge https://verify.caputchin.com/v1/gate/challenge?site=YOUR_SITE_KEY&return={scheme}://{host}{uri} 302
  }
  reverse_proxy your-app:8080
}

Der Callback

Die gehostete Verifizierungsseite schließt ab, indem sie ein POST-Formular an https://<dein-origin>/__cpt/callback mit zwei Feldern im Body auto-absendet: cpt_gate (dem Pass) und to (wohin der Besucher geschickt wird). Hast du den Callback-Pfad am Site-Key geändert, geht der POST stattdessen dorthin. Füge diese eine Route zu deinem Origin hinzu (oder als eigene Location auf dem Proxy). Sie muss:

  1. cpt_gate und to aus dem POST-Body lesen, nie der URL (ein Pass in einer URL leckt in Logs und Referrer).
  2. Bestätigen, dass to auf deinem eigenen Origin liegt, und alles andere ablehnen, sodass der Callback nicht in einen Open Redirect verwandelt werden kann.
  3. Das Cookie mit den Attributen unten setzen.
  4. (303) zu to umleiten.
// Any tiny handler on your origin works. Express shown; ~15 lines.
app.post("/__cpt/callback", express.urlencoded({ extended: false }), (req, res) => {
  const pass = String(req.body.cpt_gate || "");
  const to = String(req.body.to || "/");
  const target = new URL(to, `https://${req.headers.host}`);
  if (target.host !== req.headers.host) return res.status(400).end(); // same-origin only
  res.setHeader(
    "Set-Cookie",
    `cpt_gate=${pass}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=1800`
  );
  res.redirect(303, target.pathname + target.search);
});

Die Cookie-Attribute sind das ganze Sicherheitsmodell. Weil der Pass nicht an ein Gerät gebunden ist, sind HttpOnly (blockt Skript-Diebstahl), Secure (nur HTTPS), SameSite=Lax, und eine kurze Max-Age (passend zu deiner Freigabe-TTL) das, was ihn schützt. Außerdem: logge den cpt_gate-Wert nicht in deinen Access-Logs, und behalte Referrer-Policy: no-referrer auf dem Callback, sodass der Pass nie in einem Referrer-Header mitreist.

Fail-Modus je Proxy

Caputchin sieht eine Fail-Modus-Entscheidung nie, also taucht sie nie in deiner Statistik auf: dein Proxy trifft sie lokal, wenn er den Autorisierer nicht erreicht.

  • nginx: proxy_intercept_errors on; plus error_page 500 502 503 504 = @cpt_unreachable;, wobei diese benannte Location return 403 (geschlossen) oder return 204 (offen) macht.
  • Traefik: die forwardAuth-Middleware läuft bei einer nicht erreichbaren Adresse auf einen Fehler; route das auf einen Service, der 403 zurückgibt (geschlossen), oder nimm die Middleware hinter einem Healthcheck aus dem Router (offen).
  • Caddy: erweitere handle_errors um einen Matcher auf 5xx, der entweder respond 403 (geschlossen) oder respond 204 (offen) macht.

Pfad-Scoping je Proxy

Nur diese Pfade abdecken und Diese Pfade nie abdecken formen die erzeugte Konfiguration; durchgesetzt werden sie von deinem Proxy.

  • nginx: ein location ^~ <Präfix> pro abgesichertem Pfad, und auth_request off; in jedem ausgeschlossenen. Ein Endungs-Muster wie *.php wird zu location ~* \.php$.
  • Traefik: häng die cpt-gate-Middleware nur an die Router, deren Regeln auf die abgesicherten Pfade passen, und lass sie beim Rest weg.
  • Caddy: wickle forward_auth in einen Pfad-Matcher, zum Beispiel @gated path /admin/* /login und dann forward_auth @gated ....

Sichere nie die Autorisierer-Subrequest oder deinen Callback-Pfad ab.

Durchgearbeitetes Beispiel: Authelia

Authelias Login-Portal ist eine kompilierte Single-Page-App ohne Platz, das Widget einzubetten, und ohne Plugin-Hook, also ist das Gate der Weg, es zu schützen. Authelia sitzt bereits hinter einem Reverse-Proxy (so funktioniert sein eigenes ForwardAuth), und dieser Proxy ist der Ort für das Gate. Authelia selbst bleibt völlig unverändert.

Setze das Gate auf den vhost des Portals (zum Beispiel auth.example.com):

  • Sichere das Portal-HTML und den First-Factor-Login-Endpunkt (/api/firstfactor) ab.
  • Schließe Authelias eigenen ForwardAuth-verify-Endpunkt (/api/verify) aus, den der Proxy bei jeder geschützten Upstream-Anfrage aufruft. Ihn abzusichern würde jede geschützte App hinter Authelia brechen.
  • Schließe Health-Checks und die statischen Assets aus, die die Aufgabenseite selbst braucht.
  • Füge die /__cpt/callback-Route wie oben hinzu (oder deinen konfigurierten Callback-Pfad).

Genau das drücken die Pfad-Scope-Einstellungen aus: / und /api/firstfactor als abgesicherte Pfade, /api/verify und dein Healthcheck als ausgeschlossene.

Das Gate hält Bots vom Portal fern; Authelias eingebaute Regulation (Retry-Limit und Bann) deckelt weiterhin die Credential-Retries für jeden, der durchkommt, sodass die zwei Schichten sich stapeln. Das Gate schützt nur das interaktive Portal (die Login- und Consent-UI); es sichert die nicht-interaktiven Token-Endpunkte nicht ab, und sollte es auch nicht.

Siehe auch

Auf dieser Seite