ssh als VPN Tool

von Markus Berger am Mittwoch, 7. April 2021

Einleitung

Manchmal möchte man auf einen entfernten Rechner sicher zugreifen, dafür gibt es natürlich ssh. Was aber tun, wenn die IP-Adresse des Rechners nicht bekannt und dieser hinter einer Firewall und/oder zusätzlich einem NAT-Gateway steckt? Dann könnte man natürlich über einen externen Anbieter eine Verbindung aufbauen z.B. ein VPN, aber dafür muss normalerweise UDP-Trafik erlaubt sein und man muss dem Anbieter vertrauen. Gut wäre eine Lösung, wo auf einem Remote-Rechner keine Geheimnisse hinterlegt sein müssen.

Die Idee

Da ssh ein flexibles Werkzeug ist und auch für das Weiterleiten von TCP-Ports verwendet werden kann, kann ich auch entfernte Dienste an anderen Orten verfügbar machen. Durch die sichere Authentifizierung mittels Schlüssel ist auch Fremden der Zugriff nicht möglich und diese Schlüssel können mit den eingebauten Funktionen selbst erzeugt und verteilt werden.

Dafür läuft auf dem Remote-Client ein "Service", der den für den Dienst nötigen Port an einen Server mit dem festen Namen oder IP weiterleitet. Wenn wir nun diesen Port des Servers nutzen oder selbst weiterleiten, dann haben wir Zugriff auf den Dienst des Remote-Client.

Server Setup

Auf den Server ist eigentlich keine weitere Einstellung nötig, außer um das System robuster zu gestalten, sollten in der Datei /etc/ssh/sshd_config die folgenden Parameter gesetzt werden:

ClientAliveInterval 60
ClientAliveCountMax 3

Dies bewirkt, dass erst nach 180s die Verbindung abgebaut würde, womit kurze Netzunterbrechungen toleriert werden.

Sinnvoll ist evtl. auch ein separater Nutzer, der für die Anmeldung der Remote-Clients verwendet wird. Damit kann man dann später gezielt nach der Verbindung suchen und diese z.B. abbauen, falls doch mal etwas nicht wie gewünscht funktioniert. Hierzu ein Beispiel:

Match User emrcall
    AllowTcpForwarding yes
    X11Forwarding no
    PermitTunnel no
    GatewayPorts no
    AllowAgentForwarding no
    ClientAliveInterval 60
    ClientAliveCountMax 3
    ForceCommand date

Dieser User benötigt noch nicht einmal ein Passwort, da ja die Anmeldung ausschließlich per ssh-key erfolgt. Je nach System ist dies aber für User ohne Passwort gesperrt. Eine Änderung im 2. Feld der Datei /etc/shadow von ! auf x behebt dies.

Remote Client Setup

Dieser Rechner soll einen oder mehrere Dienste auf dem Server anbieten. Dafür nutzen wir ssh mit der Option -R (Remote Port Forwarding). Das Programm sollte in einer Dienstüberwachung wie z. B. runit laufen, denn die Verbindung kann durch verschiedene Situationen abbrechen und damit endet auch die Programmausführung.

Zuerst erstellen wir ein neues ssh-Schüsselpaar für diese Aufgabe, wobei wir den Öffentlichen-Schüssel in die Datei ~/.ssh/authorized_keys des für diesen Zweck verwendeten Users eintragen. Die Datei darf nur vom User beschreibbar sein (0600).

Mit dem oben angegebenen Beispiel-Setup sollte eine ssh-Verbindung mit dem gerade erzeugten Key die Uhrzeit des Servers liefern.

Um unsere Portweiterleitung als Dienst laufen zu lassen, bietet es sich an, die Parameter in ein kleines Script zu schreiben, z. B. nach /etc/rpfs.sh. Hier ein Beispiel:

#!/bin/sh

ssh -q -o ConnectTimeout=45 -o ServerAliveInterval=30 -o ServerAliveCountMax=3 \
    -N -R 4022:localhost:22 -i /home/user/.ssh/id_rsa_ermcall \
    ermcall@server_ip_or_name

Dieses Script machen wir mit chmod +x /etc/rpfs.sh ausführbar. Wenn wir dies nun direkt starten, können wir auf den Server über Port 4022 auf den ssh-Server des Remote-Clients zugreifen. Eine Anmeldung sollte aber nicht möglich sein, da auf dem Server nicht die notwendigen Schlüssel verfügbar sind und auch nicht sein sollten (Private Schlüssel sollten auch Privat bleiben).

Damit dies auch immer zur Verfügung steht, erstellen wir dafür einen Dienst, bei runit mit:

mkdir /etc/sv/emrcall
cd /etc/sv/emrcall
echo -en '#!/bin/sh\n\nexec /etc/rpfs.sh' > run
chmod +x run
ln -s /run/runit/supevrise-emrcall supervise
cd
ln -s /etc/sv/emrcall /var/service

Je nach System können die Pfade etwas anders sein, schaut einfach in die Dokumentation.

Frank hat stattdessen eine Service-Definition erstellt, was natürlich auch funktioniert.

[Unit]
Description=Keeps a tunnel to 'server_ip_or_name' open
After=network-online.target ssh.service

[Service]
User=user
Environment="AUTOSSH_PORT=0"
Environment="AUTOSSH_GATETIME=0"
RestartSec=30
Restart=always

ExecStart=/usr/bin/autossh -NT -o "ExitOnForwardFailure=yes" \
        -R 4022:localhost:22 -p 22 -l ermcall server_ip_or_name \
        -i /home/user/.ssh/id_rsa_ermcall
ExecStop=killall -s KILL autossh
TimeoutStopSec=10

[Install]
WantedBy=multi-user.target

Client Setup

Auf dem Rechner, wo wir diesen Dienst nutzen wollen, benötigen wir das Schlüsselmaterial, um auf unseren Remote-Client zugreifen zu können. Der passende Öffentliche Schlüssel muss auf dem Remote-Client eingetragen sein. Sind diese Vorarbeiten erledigt, bietet es sich an, in der lokalen ssh Konfiguration, normalerweise unter ~/.ssh/config dafür einen eignen Eintrag zu erstellen. Hier ein Beispiel:

Host server1
Hostname server_ip_o_name
IdentityFile ~/.ssh/id_rsa_for_server_access
User server_user_account

Host rhost1
Hostname localhost
Port 4022
IdentityFile ~/.ssh/id_rsa_for_remote_client
ProxyCommand ssh -W %h:%p server1

Nun ist mit ssh rhost1 ein Zugriff auf den entfernten Rechner möglich und wir haben nirgends auf dem Weg Geheimnisse hinterlegen müssen.

Bemerkung

Es können über eine Verbindung natürlich auch mehre Ports freigegeben oder auch bezogen werden.

In sehr seltenen Fällen und bei einer nach jetzigen Erfahrungen sehr schlechten Netzverbindung, geht evtl. die Weiterleitung kaputt und die Client-Seite beendet nicht das Programm. Dann ist es notwendig, den passenden sshd Prozess serverseitig zu beenden.