Alle Seiten
Auf dieser Seite

Das Relay über einen SSH-Tunnel erreichen

Das Relay kann eine SSH-Verbindung zu einem deiner Server öffnen und diesen Server bitten, einen Port zurück an das Relay weiterzuleiten. Du erreichst das Relay dann unter der Adresse des Servers, und zu Hause lauscht nichts in Richtung Internet. Das Relay startet, überwacht und startet den ssh-Prozess selbst neu.

Nimm das, wenn du schon einen Server mit öffentlicher Adresse und SSH-Zugang hast. Hast du keinen Server, braucht der eingebaute Cloudflare Tunnel keinen.

Was du brauchst #

  • Der Relay-Host hat ein ssh-Programm in seinem PATH (oder du nennst mit command ein anderes, siehe unten).
  • Eine SSH-Anmeldung auf dem Server, die mit einem Key und ohne Nachfrage funktioniert. Das Relay führt ssh im Batch-Modus aus und kann daher weder ein Passwort noch eine Key-Passphrase eintippen. Ein Key mit Passphrase funktioniert nur, wenn ein SSH-Agent ihn hält. Teste es zuerst vom Relay-Host aus: ssh relay@relay.example.com muss sich anmelden, ohne etwas zu fragen.
  • Einen freien Port auf dem Server für den Tunnel (remotePort).
  • Etwas auf dem Server, das diesen Port über HTTPS ausliefert: einen Webserver oder Reverse Proxy wie Caddy oder nginx. Der Tunnel trägt einfaches HTTP; ohne das gehen dein Token und deine Konversationen unverschlüsselt durchs Internet, sobald der Port erreichbar ist. Siehe Das Relay hinter einem Reverse Proxy betreiben.

Das Relay braucht keine besondere sshd-Einstellung. Mit remoteBind auf loopback fordert es die Weiterleitung ohne Bind-Adresse an, sodass die eigene Einstellung GatewayPorts des Servers entscheidet, wo sich der Port öffnet. Beim Standard no ist das nur der Server selbst, und der Reverse Proxy ist das Einzige, was zum Internet hin offen ist. Mit public fordert das Relay alle Schnittstellen an (*:), und diese Anforderung wirkt nur, wenn das sshd des Servers sie erlaubt (GatewayPorts auf clientspecified oder yes).

Einrichten #

Füge einen Block sshTunnel in config.json ein (siehe Wo die Datei liegt) und starte das Relay mit oar restart neu.

"sshTunnel": {
  "mode": "managed",
  "required": false,
  "user": "relay",
  "host": "relay.example.com",
  "remotePort": 4444,
  "remoteBind": "loopback",
  "identityFile": "~/.ssh/id_ed25519"
}

Leite auf dem Server HTTPS an den Tunnel-Port. Mit Caddy:

relay.example.com {
    reverse_proxy localhost:4444
}

Öffne https://relay.example.com/ in einem Browser und melde dich mit deinem Zugriffstoken an. Das Startbanner des Relays gibt eine Zeile Remote: aus, und oar url gibt eine Zeile Tunnel URL: mit QR-Code aus. Beide sind https:// plus der Wert von host, ohne Port und ohne Prüfung, ob der Tunnel steht; sie stimmen also nur, wenn dein Proxy HTTPS auf dem Standardport ausliefert.

Die Keys von sshTunnel #

Alle Keys stehen im Block sshTunnel. Die Tabelle stimmt mit config.json überein, wo dieselbe Liste steht.

Key Typ Standard Was er tut
mode "disabled" oder "managed" "disabled" Mit managed startet das Relay den Tunnel und startet ihn neu.
user String keiner Der SSH-Benutzer auf dem Server. Pflicht bei managed.
host String keiner Der Hostname oder die Adresse des Servers. Pflicht bei managed.
remotePort Zahl keiner Der Port, der sich auf dem Server öffnet, 1 bis 65535. Pflicht bei managed.
remoteBind "loopback" oder "public" "loopback" loopback fordert den Port ohne Bind-Adresse an (GatewayPorts des Servers entscheidet, siehe Was du brauchst); public fordert alle Schnittstellen an. Jeder andere Wert zählt als loopback. Unabhängig von localhostOnly.
identityFile String keiner Die Datei mit dem privaten Key; ~ wird aufgelöst. Ohne sie nutzt ssh deinen Agent und seine Standard-Keys.
command String ssh Das SSH-Programm, auch für das Aufräumen auf dem Server. Ein Name wird im PATH gesucht; ein Pfad mit Schrägstrich wird so genommen, wie er ist, und ein relativer zählt vom Programmordner des Relays aus.
required Boolean false Solange der Tunnel nicht verbunden ist, nimmt das Relay keine Nachrichten aus der Queue. Siehe Pflicht.
autoReclaimPort Boolean true Ist der Remote-Port schon belegt, auf dem Server aufräumen und es erneut versuchen. Nur false schaltet das ab.
reclaimStaleSshSessions Boolean false Beim Aufräumen außerdem deine eigenen untätigen SSH-Sessions auf dem Server beenden.
remoteCleanupCommand String eingebaut Dein eigener Aufräumbefehl, der auf dem Server statt des eingebauten läuft.

Die alte Form "enabled": true funktioniert weiterhin und bedeutet "mode": "managed", aber nur, wenn mode nicht auf disabled oder managed gesetzt ist.

Das Relay leitet den remotePort des Servers an seinen eigenen Port auf 127.0.0.1 weiter (3333, sofern du port nicht geändert hast). Mit public wird die Weiterleitung als *:remotePort angefordert, sonst als remotePort.

Diese Einstellungen sind fest und haben keine Keys: Das Relay führt ssh -N mit ServerAliveInterval=30, ServerAliveCountMax=3, BatchMode=yes, StrictHostKeyChecking=accept-new und ExitOnForwardFailure=yes aus. ssh gibt eine Verbindung nach drei unbeantworteten Prüfungen im Abstand von 30 Sekunden auf.

Fehlt ein Pflichtwert (user, host oder ein gültiger remotePort im Modus managed), startet das Relay wie gewohnt, schreibt ins Log, welcher es ist, und lässt den Tunnel aus.

Wie er sich verhält #

Start. Das Relay startet den Tunnel, sobald es lauscht. Es zählt den Tunnel als verbunden, wenn ssh 1,2 Sekunden gelaufen ist und der Server die Weiterleitung nicht abgelehnt hat. Das Log sagt dann Tunnel up to user@host remote port N.

Neu verbinden. Wenn ssh endet, startet das Relay es nach einer wachsenden Wartezeit neu: 5, 10, 20, 40 und dann 60 Sekunden, und bleibt bei 60. Die Zählung beginnt von vorn, sobald eine Verbindung abbricht, die länger als 30 Sekunden gehalten hat. Wird das Relay gestoppt, stoppen auch der Tunnel und die Wiederholungen.

Host-Keys. Ein Server, den der Relay-Host noch nie gesehen hat, wird bei der ersten Verbindung akzeptiert und gemerkt. Danach lässt ein geänderter Host-Key ssh die Verbindung verweigern, bis du den alten Eintrag aus known_hosts entfernst.

Pflicht #

Mit required: true bleibt die Queue vom Start an pausiert, bis der Tunnel verbunden ist, und erneut, sobald er abbricht. Nachrichten, die du schickst, warten in der Queue und laufen, sobald der Tunnel steht. Nutze das, wenn das Relay ohne den Tunnel für dich nutzlos ist. Ein Tunnel, der wegen eines fehlenden Werts nicht starten kann, hält die Queue ebenfalls pausiert.

Wenn der Remote-Port belegt ist #

Sagt der Server, dass der Port nicht gebunden werden kann, ist die übliche Ursache eine übrig gebliebene Session eines früheren Tunnels, bei der der Server nicht bemerkt hat, dass sie tot ist. Sofern autoReclaimPort nicht false ist, verbindet sich das Relay dann ein zweites Mal mit dem Server und führt dort einen Aufräumbefehl aus.

Diese Optionen führen Befehle auf deinem Server aus

Das Aufräumen läuft unter deiner SSH-Anmeldung und beendet Prozesse. Es beendet jeden Prozess dieses Benutzers, der auf dem Remote-Port lauscht, ob es ein übrig gebliebener Tunnel ist oder nicht. Mit reclaimStaleSshSessions beendet es außerdem jede untätige SSH-Session dieses Benutzers, darunter auch andere ssh -N -R-Tunnel derselben Anmeldung auf anderen Ports. remoteCleanupCommand führt aus, was immer du hineinschreibst. Lies, was jeder Schritt tut, bevor du sie eingeschaltet lässt.

Das eingebaute Aufräumen tut Folgendes, in dieser Reihenfolge:

  1. Es prüft, ob der Port belegt ist (mit ss, sonst durch Lesen der Liste lauschender Sockets im Kernel). Wenn nicht, hört es auf und meldet Erfolg.
  2. Es sucht die Prozesse, die auf dem Port lauschen (mit lsof, sonst fuser, je nachdem, was vorhanden ist), schickt ihnen ein Beendigungssignal (kill) und wartet eine Sekunde. Ein Prozess, dem deine Anmeldung kein Signal schicken darf, bleibt. Ist der Port jetzt frei, meldet es Erfolg.
  3. Nur mit reclaimStaleSshSessions: true und nur, wenn der Port noch belegt ist: Es schickt jeder sshd-Session deines Benutzers ohne Kindprozesse ein Beendigungssignal und wartet dann eine Sekunde. Das sind die Sessions, die ein ssh -N -R hinterlässt. Sessions, in denen eine Shell, der Remote-Server eines Editors oder ein anderes Programm läuft, haben Kindprozesse und werden übersprungen, ebenso die Session, in der das Aufräumen selbst läuft.
  4. Ist der Port immer noch belegt, endet das Aufräumen mit Code 3, was das Relay als is still held protokolliert.

Ist remoteCleanupCommand gesetzt, läuft dein Befehl statt all dessen, und reclaimStaleSshSessions hat keine Wirkung. Das Relay führt ihn als sh -lc '<command>' über eine zweite ssh-Anmeldung mit demselben Key im Batch-Modus aus. Er muss mit 0 enden, wenn der Port frei ist, und mit einem anderen Code, wenn er noch belegt ist.

Nach einem Aufräumen, das den Port freigegeben hat, versucht es das Relay nach einer Sekunde erneut, bis zu dreimal hintereinander. Nach einem fehlgeschlagenen Aufräumen oder mit abgeschaltetem autoReclaimPort nutzt es die normale Wartezeit oben.

Den Zustand des Tunnels sehen #

Der SSH-Tunnel wird nicht an den Stellen angezeigt, an denen der Cloudflare Tunnel erscheint: oar status und oar doctor melden nur den Cloudflare Tunnel, und der Statuspunkt wird für einen SSH-Tunnel nicht bernsteinfarben (siehe Der Statuspunkt). Sieh stattdessen hier nach:

  • Im Log des Relays. Jede Zeile beginnt mit [ssh-tunnel]. Es zeigt die ssh-Befehlszeile, die es ausführt, was ssh ausgibt, Tunnel up to ..., Reconnecting in Ns (attempt N)... und die Aufräumschritte. Wo das Log liegt, hängt davon ab, wie das Relay läuft; siehe Dateien und Ordner.
  • Im Startbanner, mit einer Zeile Remote: und einer Zeile Tunnel: mode=loopback (oder public), wenn der verwaltete Tunnel eingerichtet ist. Sie zeigen die Konfiguration, nicht, ob der Tunnel steht. oar url gibt dieselbe Adresse als Tunnel URL: aus.
  • In /api/status, mit einem Objekt sshTunnel mit connected, connectedSince, reconnectAttempts, lastError, blocking, required, mode, valid, host, remotePort und remoteBindMode. Ruf es mit deinem Token ab.

lastError ist exit:<code>, nachdem ssh geendet hat, remote-forward-bind-failed:<code>, nachdem der Server den Port abgelehnt hat, invalid-config oder die Meldung zum fehlenden Wert, wenn der Block unvollständig ist, und leer, solange der Tunnel steht.

Sicherheit #

Sobald der Tunnel steht, ist das Relay aus dem Internet erreichbar, und nur das Zugriffstoken steht zwischen einem Fremden und deinen Agents, die Befehle auf deinem Rechner ausführen. Deshalb:

  • Lass remoteBind auf loopback und stell auf dem Server HTTPS davor. Mit public erreicht jeder, der den Tunnel-Port des Servers erreicht, das Relay über einfaches HTTP.
  • Das Zugriffstoken ist das einzige Schloss. Siehe Das Zugriffstoken und wer das Relay erreicht und Was OAR offenlegt.
  • Das Relay lehnt Anfragen an seine internen Session-Worker-Pfade ab, wenn sie den Cloudflare-Marker-Header cf-ray oder einen Header tragen, den du in tunnelMarkerHeaders einträgst. Ein einfacher Reverse Proxy fügt keinen davon hinzu; trag also den Header ein, den dein Proxy hinzufügt, falls er einen hinzufügt, den du gesperrt haben möchtest. Siehe config.json.

Fehlerbehebung #

Sieh dir zuerst die Zeilen [ssh-tunnel] im Log an. Zeilen, die mit stderr: beginnen, hat ssh selbst ausgegeben.

Du siehst Was es bedeutet Was zu tun ist
sshTunnel.user is required when sshTunnel.mode is "managed" (auch für host) Ein Pflichtwert fehlt. Der Tunnel bleibt aus. Setz ihn und führ oar restart aus.
sshTunnel.remotePort must be a positive integer when sshTunnel.mode is "managed" Der Port fehlt oder ist keine Zahl von 1 bis 65535. Korrigiere ihn und starte neu.
Tunnel mode disabled; running direct relay only. mode ist nicht managed. Setz "mode": "managed".
stderr: ... Permission denied (publickey) Der Server hat deinen Key nicht akzeptiert. Batch-Modus heißt: keine Passwortabfrage. Setz identityFile und teste dasselbe ssh user@host von Hand auf dem Relay-Host.
Process exited (code=N). Scheduling reconnect... wiederholt sich ssh konnte sich nicht verbinden oder anmelden. Führ den protokollierten ssh-Befehl von Hand aus und lies seinen Fehler.
stderr: ... remote port forwarding failed for listen port N, dann Attempting remote reclaim ... Der Port auf dem Server ist belegt. Warte auf das Aufräumen. Endet es mit is still held, gib den Port selbst frei oder wähl einen anderen remotePort.
Remote reclaim finished: listen port N is still held (code=3) Das Aufräumen konnte den Port nicht freigeben. Such den Inhaber auf dem Server, versuch reclaimStaleSshSessions: true oder setz remoteCleanupCommand.
stderr: ... Host key verification failed oder REMOTE HOST IDENTIFICATION HAS CHANGED Der Key des Servers weicht vom gespeicherten ab. Ist die Änderung erwartet, entferne den alten Eintrag aus known_hosts. Wenn nicht, verbinde dich nicht.
Reconnecting in 60s (attempt N) immer wieder, und der Tunnel sagt nie Tunnel up Die Verbindung schlägt jedes Mal fehl. Lies die Zeilen stderr: vor jeder davon.
Der Tunnel sagt Tunnel up, aber die Adresse antwortet nicht Die Seite des Relays ist in Ordnung, die Seite des Servers nicht. Prüf, ob der Reverse Proxy läuft und auf den remotePort zeigt und ob remoteBind dazu passt, wie der Proxy ihn erreicht.
Der Tunnel verbindet sich, bricht ab und verbindet sich in einer Schleife neu Etwas auf dem Server oder im Netzwerk beendet die Verbindung. Das Log zeigt den Exit-Code und die Meldungen von ssh. Prüf das sshd-Log des Servers.
Nachrichten bleiben in der Queue required ist true, und der Tunnel ist nicht verbunden. Repariere den Tunnel oder setz required auf false.