Alle Seiten
Auf dieser Seite

Das Relay hinter einem Reverse Proxy betreiben

Ein Reverse Proxy ist ein eigener Webserver, der die Verbindung vom Browser annimmt und an das Relay weitergibt. Du brauchst einen, wenn du HTTPS für ein Relay in deinem LAN oder auf einem Server willst, wenn das Relay unter einem Unterpfad einer Website liegen soll, die du schon betreibst (https://example.com/oar/), oder wenn der Edge eines Tunnels (zum Beispiel Cloudflare) zwischen Browser und Relay sitzt. Ein Proxy auf dem eigenen Rechner des Relays funktioniert ohne jede Einstellung. Ein Proxy auf einem anderen Rechner braucht zwei oder drei, gezeigt weiter unten. Fünf Keys in config.json informieren das Relay über den Proxy, und jeder regelt etwas Bestimmtes; sie werden danach erklärt.

Was der Proxy tun muss #

Die Seite des Relays, seine API und seine Live-Verbindung nutzen alle einen Port (3333, sofern du port nicht geändert hast). Der Proxy muss:

  • WebSocket-Upgrades durchlassen. Die App hält eine Socket.IO-Verbindung zum Relay offen, auf dem Pfad /socket.io/ (mit Präfix <prefix>/socket.io/). Kommen Upgrades nicht durch, lädt die Seite, zeigt aber nie Live-Updates.
  • Langlebige Verbindungen nicht puffern und nicht per Timeout beenden. Dieser Socket bleibt offen, solange die Seite offen ist, und Turns werden in Echtzeit darüber gestreamt. Nimm lange Lese- und Leerlauf-Timeouts und puffere keine Antworten.
  • Den Header Host durchreichen oder X-Forwarded-Host senden. Das Relay baut daraus die Adresse eines geteilten Links und nimmt Neu in 0.9.11 die Live-Verbindung nur von einer Seite unter dieser Adresse an (siehe allowedOrigins). X-Forwarded-Host und X-Forwarded-Proto zählen nur von einem Proxy, dem das Relay vertraut.
  • X-Forwarded-Proto senden, wenn der Proxy TLS terminiert, damit das Relay weiß, dass der Browser HTTPS benutzt hat (siehe Cookies und HTTPS), und X-Forwarded-For, damit es die Adresse des Clients kennt.
  • Den Pfad unverändert lassen. Schreib ihn nicht um. Lieferst du das Relay unter einem Präfix aus, entfernt das Relay das Präfix selbst (siehe remotePath).

Du brauchst keine Cache-Regel. Das Relay sendet Cache-Control: no-store für seine Seite, seinen Service Worker, sein Manifest und seine API, außerdem CDN-Cache-Control: no-store und Cloudflare-CDN-Cache-Control: no-store. Leg keine Regel an, die /api/shared/* zwischenspeichert: Geteilte Ansichten fragen regelmäßig nach neuem Inhalt, und eine zwischengespeicherte Antwort würde Betrachtern einen alten Stand zeigen.

Ein minimales Caddy-Beispiel für ein Relay auf demselben Rechner. Caddy lässt WebSocket-Upgrades von selbst durch und fügt X-Forwarded-Proto hinzu:

relay.example.com {
    reverse_proxy 127.0.0.1:3333
}

Bei nginx oder einem anderen Proxy schau in dessen eigener Dokumentation nach, wie man Upgrades durchlässt und Timeouts setzt, und gleiche das mit der Liste oben ab. Zu Tunneln siehe SSH-Tunnel und Cloudflare Tunnel; zum Zugriffstoken siehe Zugriff.

Ein Proxy auf einem anderen Rechner Neu in 0.9.11 #

Die eingebauten Tunnel und ein Proxy auf dem eigenen Rechner des Relays erreichen das Relay über 127.0.0.1, und der Standard von trustProxy glaubt ihren weitergeleiteten Headern. Ein Proxy auf einem anderen Host oder in einem anderen Container oder einer anderen VM ist ein anderer Peer. Solange du ihm nicht vertraust, ignoriert das Relay seine Header X-Forwarded-For, X-Forwarded-Host und X-Forwarded-Proto, also:

  • hat jeder Besucher die Adresse des Proxys: eine Anmeldesperre für alle und eine Adresse in jedem Eintrag im Zugriffsprotokoll;
  • beginnen neue geteilte Links mit http:// und dem Host, den der Proxy geschickt hat;
  • wird das Anmelde-Cookie nicht als Secure markiert.

Setz drei Keys in der Config-Datei und starte das Relay neu:

{
  "localhostOnly": false,
  "trustProxy": "192.0.2.10",
  "allowedOrigins": ["https://relay.example.com"]
}
  • localhostOnly: false lässt das Relay über Loopback hinaus lauschen. Halte den Port für alles außer dem Proxy geschlossen.
  • trustProxy ist die Adresse des Proxys, sein Subnetz oder die Zahl der Proxys vor dem Relay (1 für einen). Siehe unten.
  • allowedOrigins brauchst du nur, wenn der Proxy den Header Host umschreibt und kein X-Forwarded-Host sendet. Siehe unten.

Ein Proxy auf dem eigenen Rechner des Relays braucht weder trustProxy noch localhostOnly: false, und allowedOrigins nur, wenn er Host umschreibt. Unter einem URL-Präfix setz außerdem remotePath.

Die Keys, die das Relay über den Proxy informieren #

Alle fünf stehen in der Tabelle auf config.json. Starte das Relay neu, nachdem du sie geändert hast.

trustProxy #

Standard "loopback". Er entscheidet, wessen X-Forwarded-*-Header das Relay glaubt. Mit dem Standard wird nur einem Proxy geglaubt, der sich vom selben Rechner aus verbindet; das passt für Caddy, nginx oder cloudflared neben dem Relay.

Was das Relay aus den Headern eines vertrauenswürdigen Proxys übernimmt:

  • ob die Anfrage über HTTPS kam; davon hängt ab, ob das Anmelde-Cookie das Flag Secure trägt, und das Schema eines neuen geteilten Links;
  • den Host, den der Browser angesprochen hat (X-Forwarded-Host), für neue geteilte Links und für die Origin-Prüfung der Live-Verbindung;
  • die Adresse des Clients, für die Anmeldesperre, das Anwesenheitslimit geteilter Ansichten und das Zugriffsprotokoll geteilter Links. Das Land in diesem Protokoll (cf-ipcountry) wird ebenfalls nur von einem vertrauenswürdigen Proxy gelesen.

Läuft dein Proxy auf einem anderen Rechner, sieht das Relay einen nicht lokalen Peer und ignoriert diese Header. Gib trustProxy das, was Express’ trust proxy annimmt: eine Hop-Anzahl (1), eine Adresse oder ein Subnetz ("10.0.0.0/8"), eine Liste davon oder "loopback".

Nur mit einem Proxy davor

Setz trustProxy nur dann weiter als "loopback", wenn wirklich jede Verbindung zum Relay über deinen Proxy kommt. Sonst kann jeder, der den Port erreicht, ein gefälschtes X-Forwarded-Proto senden.

Neu in 0.9.11 Jede Nutzung eines weitergeleiteten Headers folgt trustProxy. In 0.9.10 lasen das Anwesenheitslimit geteilter Ansichten und die Adresse eines neuen geteilten Links sie von jedem Client.

allowedOrigins Neu in 0.9.11 #

Standard []. Die Live-Verbindung (Socket.IO) nimmt den Handshake eines Browsers nur vom eigenen Origin des Relays an: Der Origin, den der Browser sendet, muss den Host haben, den der Browser angesprochen hat; das ist X-Forwarded-Host, wenn ein vertrauenswürdiger Proxy ihn sendet, sonst Host. Eine Seite auf einer anderen Website kann dein Anmelde-Cookie nicht nutzen, um eine Live-Verbindung zu öffnen. Clients, die keinen Origin senden, etwa Skripte, sind nicht betroffen.

Ein Proxy, der den Header Host auf die interne Adresse des Relays umschreibt und kein X-Forwarded-Host sendet, macht diesen Vergleich kaputt. Trag dann den öffentlichen Origin ein, genau so, wie der Browser ihn zeigt, mit Schema, Host und jedem Port außer dem Standardport:

"allowedOrigins": ["https://relay.example.com"]

Ohne das lädt die Seite und du kannst dich anmelden, aber nichts aktualisiert sich live, und das Relay schreibt einmal pro Origin ins Log:

[socket] Refused a handshake from origin https://relay.example.com (host 127.0.0.1:3333); list it in allowedOrigins if a proxy rewrites the Host header

remotePath #

Standard "". Setz ihn auf das Präfix, wenn der Proxy das Relay unter einem Unterpfad ausliefert, zum Beispiel "/oar". Ein führender Schrägstrich wird ergänzt, abschließende Schrägstriche werden entfernt.

Ist er gesetzt, dann:

  • nimmt das Relay das Präfix von jeder eingehenden Anfrage ab; der Proxy reicht den Pfad also mit dem Präfix weiter und entfernt es nicht;
  • setzt das Relay das Präfix in den Pfad des Anmelde-Cookies, den Socket-Pfad (/oar/socket.io/) und die Adressen von Dateien und geteilten Links.

Ohne ihn findet ein Relay unter einem Präfix seine eigenen Dateien und seinen Socket nicht, weil die Seite /socket.io/ und /api/... an der Wurzel der Website anfragt. Öffne das Relay mit abschließendem Schrägstrich (https://example.com/oar/).

Eine installierte App gehört zu der Adresse, von der aus sie installiert wurde; ein Relay unter einem Präfix ist also eine eigene App. Siehe Als App installieren.

publicHostnames #

Standard []. Trag die Hostnamen ein, über die das Relay selbst erreicht wird, zum Beispiel ["relay.example.com"]. Das Relay nutzt sie an einer Stelle: Die Preview-Lane lehnt eine previews.publicBaseUrl auf einem dieser Namen (oder auf localhost oder 127.0.0.1) ab, damit eine App in der Preview nie ein Anmelde-Cookie mit dem Relay teilt. Stellst du das Relay unter einem neuen Namen hinter einen Proxy und nutzt Previews, füge diesen Namen hier hinzu.

tunnelMarkerHeaders #

Standard []. Die Namen von Headern, die dein Proxy oder der Edge deines Tunnels jeder weitergeleiteten Anfrage hinzufügt. Das Relay nutzt sie für den Pfadschutz des Session-Workers unten. cf-ray, das Cloudflare hinzufügt, steht immer auf der Liste. Der Wert kann ein Array oder ein durch Kommas getrennter String sein. Die Umgebungsvariable COPILOT_TUNNEL_MARKER_HEADERS (durch Kommas getrennt) fügt weitere Namen hinzu; Neu in 0.9.11 die beiden Listen werden zusammengeführt, sodass tunnelMarkerHeaders: [] die Variable nicht mehr abschaltet. Siehe Umgebungsvariablen.

Der Statuspunkt

tunnelMarkerHeaders färbt den Statuspunkt nicht bernsteinfarben. Der Punkt ist bernsteinfarben, solange der eigene verwaltete Cloudflare Tunnel des Relays verbunden ist (siehe das Konversationsmenü). Ein Proxy, den du selbst betreibst, färbt ihn nie bernsteinfarben.

Der Pfadschutz des Session-Workers #

Jede Agent-Session läuft in einem Worker auf dem Rechner des Relays, und der Worker spricht mit dem Relay über einen WebSocket auf /api/session-worker/ws (mit Präfix <prefix>/api/session-worker/ws). Ein Proxy oder Tunnel leitet jeden Pfad seines Hostnamens an das Relay weiter; ohne Schutz wäre dieser Pfad also aus dem Internet erreichbar. Nur lokale Worker sollen ihn nutzen.

Neu in 0.9.11 Der Schutz lässt eine Anfrage an diesen Pfad nur durch, wenn sie vom eigenen Rechner des Relays kommt und nichts sie weitergeleitet hat:

  • der verbindende Peer ist Loopback (127.0.0.1 oder ::1);
  • sie trägt keinen Marker-Header: cf-ray oder einen aus tunnelMarkerHeaders;
  • sie trägt kein X-Forwarded-For, Forwarded oder X-Real-IP.

Alles andere wird abgelehnt, bevor die Anmeldung geprüft wird:

  • eine einfache Anfrage bekommt 403 mit {"error":"Session worker endpoints are reachable from this machine only"};
  • ein WebSocket-Upgrade bekommt 403 Forbidden, und die Verbindung wird geschlossen.

Jede Ablehnung wird als [tunnel-guard] Blocked session-worker path …: not local (…) protokolliert, mit dem Merkmal, das die Anfrage verraten hat. Lokale Worker verbinden sich direkt mit 127.0.0.1, ohne einen dieser Header, und sind nicht betroffen. Geteilte Links funktionieren weiter über den Tunnel: Der Schutz richtet sich nur nach dem Worker-Pfad, nicht danach, ob eine Anfrage anonym ist.

Cloudflare fügt cf-ray und Caddy X-Forwarded-For von selbst hinzu, der Schutz funktioniert mit ihnen also ohne Einstellung; bei nginx sende X-Forwarded-For mit proxy_set_header. Ein Weiterleiter, der gar nichts hinzufügt, etwa ein SSH-Reverse-Tunnel oder ein einfacher TCP-Proxy, sieht aus wie ein lokaler Prozess; der Pfad ist dann nur durch das Zugriffstoken geschützt. Lass einen solchen Weiterleiter einen Header mit festem, nicht leerem Wert hinzufügen, zum Beispiel X-Oar-Edge: 1, und trag seinen Namen ein: "tunnelMarkerHeaders": ["x-oar-edge"]. Oder sperr den Pfad vor dem Relay.

Endpunkte, die nur der Rechner des Relays nutzen darf #

Neu in 0.9.11 Zwei Dinge antworten nur auf Anfragen vom eigenen Rechner des Relays, die kein Proxy weitergeleitet hat, nach derselben Regel wie der Pfadschutz:

  • der Socket des Session-Workers, oben;
  • das Ausführen einer Bildoperation (POST /api/image-operations/…/execute), das die eigenen Worker des Relays aufrufen. Alles andere bekommt 403 Image operation execution is reachable from this machine only (Ausführen von Bildoperationen ist nur von diesem Rechner aus möglich).

Alles andere braucht nur das Zugriffstoken, egal woher die Anfrage kommt. Dazu gehören 🌄 Restart web relay (Web-Relay neu starten), das Zurückziehen eines eingereihten Neustarts mit dem Button Cancel (Abbrechen) auf seinem Banner sowie 🚮 Empty queue (Queue leeren): Jeder angemeldete Browser kann sie nutzen, auch über das LAN und durch jeden Proxy oder Tunnel.

Bis 0.9.10 waren diese drei nur für den Rechner des Relays gedacht, aber die Prüfung ließ alles durch einen Tunnel oder einen Proxy auf demselben Rechner durch und lehnte eine direkte LAN-Verbindung ab, während die Seite Erfolg meldete. Wenn Nutzer aus der Ferne das Relay nicht neu starten oder seine Queue nicht leeren dürfen, lass den Proxy POST /api/relay/shutdown, POST /api/relay/shutdown/cancel und POST /api/queue/empty ablehnen. oar restart auf dem Rechner des Relays funktioniert immer.

Cookies und HTTPS #

Das Anmelde-Cookie ist HttpOnly und SameSite=Lax, gilt 30 Tage und hat den Pfad / (oder das Präfix aus remotePath). Es trägt Secure, wenn das Relay die Anfrage als sicher ansieht.

  • Der Proxy terminiert TLS auf demselben Rechner. Der Proxy sendet X-Forwarded-Proto: https, der Standard von trustProxy glaubt das, und das Cookie ist Secure.
  • Der Proxy terminiert TLS auf einem anderen Rechner. Setz trustProxy wie oben. Bis dahin hat das Cookie kein Flag Secure, funktioniert aber trotzdem.
  • Gemischte Setups. Öffnest du das Relay im LAN zusätzlich direkt über einfaches http, ist diese Anfrage nicht sicher, und ihr Cookie hat kein Flag Secure; die über den Proxy laufenden HTTPS-Anfragen bekommen das Flag. Browser speichern Cookies pro Adresse.

Das Setup prüfen #

  1. Öffne die Adresse über den Proxy in einem Browser. Die Anmeldung oder die Konversationsliste erscheint.
  2. Sieh auf den Statuspunkt: Er wird grün, und die Seite zeigt Live-Updates, wenn du einen Turn startest. Ein endloses Neuverbinden heißt, dass der WebSocket nicht durchkommt.
  3. Unter einem Präfix öffne die Adresse mit abschließendem Schrägstrich. Die Seite, ihre Styles und die Live-Verbindung laden, und der Pfad des Cookies ist das Präfix (sieh in den Entwicklertools des Browsers nach).
  4. Erstelle einen geteilten Link (Teilen). Seine Adresse nutzt den öffentlichen Hostnamen und https und das Präfix, falls du eins gesetzt hast.
  5. Teste den Schutz von außen: Frag https://relay.example.com/api/session-worker/ws an. Du solltest 403 und eine Zeile [tunnel-guard] im Log des Relays sehen. Bekommst du eine andere Antwort, fügt dein Proxy weder einen Marker-Header noch X-Forwarded-For hinzu.
  6. Hinter einem Proxy auf einem anderen Rechner: Melde dich von zwei Geräten an und prüf unter Settings → Sharing (Einstellungen → Teilen), dass das Zugriffsprotokoll eines geteilten Links die Adressen der Betrachter zeigt, nicht die des Proxys. Zeigt es die des Proxys, passt trustProxy nicht zum Proxy.

Wenn es nicht funktioniert #

Was du siehst Wahrscheinliche Ursache
Die Seite lädt, verbindet sich aber endlos neu oder aktualisiert sich nie Der Proxy lässt keine WebSocket-Upgrades auf /socket.io/ durch oder schließt untätige Verbindungen.
Die Seite lädt, aber nichts aktualisiert sich live, und das Log sagt Refused a handshake from origin Der Proxy schreibt den Header Host um. Trag den öffentlichen Origin in allowedOrigins ein oder lass den Proxy Host durchreichen.
Die Anmeldung sagt Too many failed sign-in attempts (Zu viele fehlgeschlagene Anmeldeversuche) für alle gleichzeitig Das Relay vertraut einem Proxy auf einem anderen Rechner nicht, also teilen sich alle Besucher seine Adresse und seine Sperre. Setz trustProxy.
Unter einem Präfix ist die Seite leer, oder Dateien fehlen remotePath ist nicht gesetzt, oder der Proxy entfernt das Präfix. Der Proxy muss es durchreichen.
Unter einem Präfix wirst du immer wieder zur Anmeldung aufgefordert Der Pfad des Cookies ist das Präfix aus remotePath, also sendet der Browser es nur unter diesem Präfix. Öffne das Relay unter diesem Präfix und mit abschließendem Schrägstrich.
Geteilte Links oder Datei-Links haben die falsche Adresse oder kein Präfix remotePath ist nicht gesetzt, oder der Proxy sendet kein Host bzw. kein X-Forwarded-Host / X-Forwarded-Proto, oder das Relay vertraut dem Proxy nicht.
Geteilte Links beginnen mit http://, oder das Cookie hat kein Flag Secure, hinter einem Proxy auf einem anderen Rechner trustProxy ist noch "loopback".
Der Worker-Pfad antwortet über deinen Proxy ohne 403 Der Proxy fügt weder einen Marker-Header noch X-Forwarded-For hinzu. Siehe den Pfadschutz.
Der Punkt wird nie bernsteinfarben Erwartet: Das macht nur der eigene verwaltete Cloudflare Tunnel des Relays.
Previews starten unter dem neuen Namen nicht Füge den Hostnamen des Relays zu publicHostnames hinzu.

Zur ersten Anmeldung und zum Zugriffstoken siehe Erster Start.