Zwei Server an verschiedenen Standorten bei irgendeinem der großen Hosting-Provider: Irgendwie möchte man die sogar verbinden. Nur wie? Eine Lösung ganz ohne VPN, mit SSH.

Zurück zur Ausgangssituation: Zwei Server, direkt im öffentlichen Internet, an zwei verschiedenen Standorten wollen Daten austauschen. Als Beispiel nehmen wir einen MariaDB-Cluster sowie syslog. Maria läuft wie MySQL auf Port 3306/tcp, syslog auf 514/udp. Ok, syslog ginge auch via tcp. Aber syslog Nachrichten per tcp? Nein, wenn es irgendwie anders geht, dann anders. Udp ist für syslog einfach das schönere Protokoll.

Neue Lösungen braucht das Land

Was könnte man also tun? Denkbar wäre eine direkte Verbindung übers Internet. Jeweils eine Firewall auf beiden Systemen und für die Datenbank als auch syslog nur Verbindungen vom jeweils anderen Node annehmen. Dir läuft es gerade bei dem Gedanken daran eiskalt den Rücken herunter? Gut mir auch, also nächster Ansatz.

Also dann doch mit vernünftiger Verschlüsselung. Ein VPN wäre ganz nett. Hm, klar – kann man machen. Da hab ich jetzt allerdings keine Lust drauf. Ein IpSec zwischen zwei einzelnen Rechnern ohne Netzwerk dahinter? Naja… OpenVPN: Keine Lust auf Serverkonfig. WireGuard? Nein, ich will kein VPN. Next!

Aber jetzt: ssh port forwarding. Das ist super, leicht einzurichten und funktioniert. Wobei: ssh jedes mal neustarten wenn der Rechner neustartet? Das geht super via autossh (https://www.harding.motd.ca/autossh/README.txt). Der Vorteil von autossh gegenüber dem Start via systemd: Falls der Tunnel während der Laufzeit abbricht startet ihn autossh einfach neu. Das ist sehr schön. Weniger schön: Port forwarding bei ssh? Das geht nur mit tcp. Das will ich aber gar nicht für syslog haben. Da wäre mir udp lieber. Ihr kennt den Text? Richtig, next!

Ssh und autossh war schon toll. Allerdings kann ssh neben port forwarding auch noch die Möglichkeit des tunnel device forwarding. Dabei entsteht ein Point-to-Point tun-Interface auf beiden Seiten. Schon kann man auf diese virtuelle Netzwerkkarte zugreifen, womit man will. Die eigentlichen Daten werden allerdings, wie gewünscht, per ssh übers Internet übertragen. Für meinen Einsatzzweck ist das mehr wie ausreichend.

 

Umsetzung

Und wie funktioniert das nun? Einfach das unten angefügte Skript auf beiden Servern installieren und sobald alles fertig ist ausführen. Einer der Server ist „Master“, der andere der „Slave“. Beide starten zunächst ein tun Interface und richten eine Point-to-Point IP darauf ein. In meinem Beispiel hat der Master die 10.255.255.1, der Slave die 10.255.255.2. Achtung, da point-to-point: Die Maske lautet 255.255.255.255. bzw. /32. Es braucht kein Netz, die einzelnen IPs reichen völlig aus.

Nachdem dies erledigt ist, wird auf dem Master via autossh eine SSH-Verbindung auf den slave eingerichtet mit dem device forwarding welche von autossh kontrolliert wird. In dem Beispiel verwende ich den Benutzer „autossh“ welcher auf dem Slave angelegt sein muss. Dieser braucht keine shell oder der gleichen, lediglich ein Home-Verzeichnis in dem in authorized_keys der verwendete public key des masters hinterlegt ist.

Das ganze wurde um folgende Funktion ergänzt: Falls ein ping auf die IP der Gegenseite funktioniert, dann muss man nichts tun. Falls nicht lege ggf. das tun Interface an bzw. setze die grundlegenden Einstellungen erneut. Falls autossh läuft wird dieser Prozess beendet. Irgendwas scheint ja offensichtlich nicht zu funktionieren – sonst hätte der erste ping check geklappt.

#!/bin/bash

#
# Richtet Grundlegend das tun-Interface für den SSH-Tunnel ein.
#

# 2023-05-17, gehirn-mag-net: Init...



# Ein paar Einstellungen
TUNNO=9
TUN=tun$TUNNO
IP=/usr/sbin/ip
IPMASTER=10.255.255.1
IPSLAVE=10.255.255.2
HOSTNAMEMASTER=tglic1
HOSTNAMESALVE=tglic2
PING=/usr/bin/ping
AUTOSSH=/usr/bin/autossh
SSHSLAVE="autossh@123.xxx.yyy.zzz"


# Wer ist wer?
if [ "$HOSTNAME" == "$HOSTNAMEMASTER" ]; then
        ROLE="MASTER"
        IPADDR=$IPMASTER
        IPPEER=$IPSLAVE
else
        ROLE="SLAVE"
        IPADDR=$IPSLAVE
        IPPEER=$IPMASTER
fi
echo "${ROLE}: $IPADDR p2p $IPPEER"

# Solange ping sauber durchläuft muss nichts getan werden
$PING -c1 -W5 $IPPEER
if [ $? == 0 ]; then
        echo "Peer erreichbar."
        exit 0
fi

# ggf. tun Device anlegen, Adresse zuweisen
echo "Prüfe Device tun${TUNNO}..."
$IP link show dev $TUN &> /dev/null || $IP tuntap add dev $TUN mode tun
$IP addr show dev $TUN | grep "$IPADDR" &> /dev/null || $IP addr add dev $TUN ${IPADDR}/32 peer $IPPEER
$IP link set dev $TUN up
$IP addr show dev $TUN

# AutoSSH starten - sofern Master
if [ "$HOSTNAME" == "$HOSTNAMEMASTER" ]; then
        echo "Prüfe AutoSSH..."

        # AutoSSH starten - ggf. beenden falls noch aktiv
        AUTOSSHPID=$(ps aux | grep ssh | grep " \-w${TUNNO}\:${TUNNO} " | tr -s ' ' | cut -d' ' -f2 | tr '\n' ' ')
        if [ -n "$AUTOSSHPID" ]; then
                echo "Stopping existing autossh with pid $AUTOSSHPID..."
                kill $AUTOSSHPID
        fi
        echo "Starte autossh..."
        $AUTOSSH -f -M 0 \
                -o "ServerAliveInterval 60" \
                -o "ServerAliveCountMax 3" \
                -o "ExitOnForwardFailure yes" \
                -N -T -w${TUNNO}:${TUNNO} $SSHSLAVE
        ps aux | grep "[a]utossh"

        # Fertig
        sleep 2
        $PING -c1 -W5 $IPPEER
        exit 0

fi

# Fertig
echo "Warten auf Gegenseite..."
exit 0

 

Nun braucht es noch eine kleine Änderung auf dem slave: Folgende Zeile muss in der sshd_config vorhanden sein. Ohne diese würde OpenSSH die Verwendung des tunnel interfaces verbieten. Wer es ganz elegant mag: Per Match auf den autossh-Benutzer auf diesen beschränken.

PermitTunnel yes

Fehlt noch ein automatischer Aufruf des Skriptes beim Systemstart. Das könnte man per systemd erledigen. Allerdings hat mich die Erfahrung gelehrt, dass cron die bessere Alternative ist. Vor allem weil mit cron sehr leicht die regelmäßie Kontrolle realisiert werden kann. Im folgenden Beispiel starte ich alle 15 Minuten das Skript auf beiden Servern erneut. Falls es Probleme geben sollte, würden diese automatisch bereinigt werden.

@reboot      /usr/local/sbin/gmn-tun.sh 2>&1 | /usr/bin/logger -t gmn-tun
*/15 * * * * /usr/local/sbin/gmn-tun.sh 2>&1 | /usr/bin/logger -t gmn-tun

Sobald das Skript auf beiden Servern gestartet ist kann der Master mit der IP 10.255.255.1 die 10.255.255.2 erreichen: Den slave. Und umgekehrt. Syslog funktioniert per 514/udp, ebenso kann MariaDB via tcp synchron gehalten werden. Wunderbar 🙂

Fazit

Ist diese Lösung perfekt? Ein VPN mag für die zu übertragenden Daten einen geringeren Resourcen-Verbrauch haben. Dafür muss man ein VPN einrichten. Von daher finde ich diese Lösung über ssh ganz nett. Vor allem, und das war eigentlich das Hauptanliegen dieses Beitrags, zeigt es wieder einmal aufs Neue wie vielseitig ssh sein kann. Vor allem dann, wenn kein VPN installiert ist bzw. installiert werden kann. Ein ssh allerdings vorhanden ist…