Automatisches rsync über SSH absichern

Im ersten Teil dieser Serie ging es im weitesten Sinne um Let’s Encrypt. Mein persönliches Setup hier macht es aber nötig, die fertigen Zertifikate nochmal sicher auf eine andere virtuelle Maschine zu kopieren. Wie das grundsätzlich mit OpenSSH funktioniert, konnte man im zweiten Teil lesen. Jetzt kommt die Kür, die Absicherung dieses Kopierens inklusive sicherem rsync-Zugriff. Lässt sich sicherlich auch für andere Zwecke wie Backup-Server anpassen. ;-)

Wrapper-Skript auf dem Zielserver

Was wir jetzt noch wollen: in unserem Wrapper-Skript, das zunächst mal nur den aufgerufenen Befehl in ein Log-File geschrieben hatte, soll die Umgebungsvariable SSH_ORIGINAL_COMMAND analysiert und gegen erlaubte Befehle abgeglichen werden. Nur im Wrapper-Skript erlaubte Befehle werden ausgeführt. Direkt über SSH abgesetzte Befehle sind hier leicht abzufangen, weil sie genauso ankommen, wie auf dem Quellsystem eingegeben. Interessant wird es mit rsync. Dem sagen wir ja auf dem Quellsystem was ganz anderes, als auf dem Zielsystem im Log erscheint, zum Vergleich nochmal ein (expandierter) rsync-Aufruf:

rsync -e 'ssh -i /home/alex/.ssh/id_rsa_acme' /home/alex/.acme.sh/owncloud.home.example.com/fullchain.cer susan:owncloud.home.example.com/

Um hier im Wrapper-Skript den Befehl, den rsync über SSH tatsächlich absetzt, korrekt matchen zu können, braucht’s entweder hellseherische Fähigkeiten, sehr viel tieferes Verständnis von rsync oder ein trial und error mit einem Log-Skript wie in Teil zwei der Serie. Man kann aber noch einen Schritt weitergehen und daher schweifen wir nochmal kurz ab …

rrsync zur Absicherung von unbeaufsichtigten rsync-Aufrufen

Dass das mit rsync kompliziert ist, haben auch andere Leute bemerkt, auch bei Samba selbst, unter deren Dach rsync entwickelt wird. Und so gibt es rrsync. Bei Debian ist es im Paket rsync die Datei /usr/share/doc/rsync/scripts/rrsync.gz, die man sich in temporäres Verzeichnis kopiert, dekomprimiert, mit chmod +x rrsync behandelt und dann in einen Ordner bin kopiert, ich hab’s in /home/alex/bin/ abgelegt. Es handelt sich um ein etwa 200 Zeilen langes Perl-Skript, das rsync nochmal auf die Finger schaut.

Die Dokumentation von rrsync ist leider sehr dünn. Wenn man das Skript ohne Parameter aufruft, erhält man:

Use 'command="./bin/rrsync [-ro] SUBDIR"'
        in front of lines in /home/alex/.ssh/authorized_keys

Das erklärt im Grunde schon den Hauptzweck: man kann rsync-Zugriffe auf bestimmte Verzeichnisse beschränken. Außerdem werden nur gültige rsync-Aufrufe zugelassen und man kann über nicht dokumentierte Optionen die zugelassenen rsync-Optionen noch weiter einschränken. Wer davon Gebrauch machen will, sollte sich das Skript rrsync selbst nochmal anschauen. Darüber hinaus erleichtert rrsync die Verwendung von rsync in einem Wrapper-Skript, werden wir dann gleich noch sehen.

Erstmal nur rrsync ohne Wrapper-Skript. Dazu ändern wir auf dem Zielserver in der ~/.ssh/authorized_keys die Zeile mit unserem public-Key, so dass vorn als command rrsync angegeben wird:

from="troubadix.home.example.com",command="/home/alex/bin/rrsync .cert",no-agent-forwarding,no-port-forwarding,no-pty,no-user-rc,no-X11-forwarding ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9VKFUIcc+vfL3a9oFNeuITghF7+QYtTvlpsX7CHG/UmEIIIpz31n6TEoSCL0a4sA6Xx2Z+peGbYD1RRzH/6Shis/4L+R2HPSA+1TqLKT4fdyF9Xkbj9E/bbmrXx5fH8DZnYThIJ15mVHUO7eALU2ELxa+mwehxPbV070qFo1Y1lGa93dhJSdXMerFQE7YPf8UXlb1ULJA+AneQNMymvSBJU86VycMdk/Tb7p1dqepKGVEmS/IFwh9Q6eGQ87JWJGa2BEbVpJMrwucN2So6/06fPpEp7ovxJhIlwXny6jfofpBsr0NZe3yknPk3nCxkPTpLMNOTSBfm8Nva4++firV alex@troubadix

Nicht von den hier zusätzlichen Optionen abschrecken lassen, kann man alles mit man 5 authorized_keys nachlesen, was es damit auf sich hat, entscheidend ist hier: command="/home/alex/bin/rrsync .cert"

Ich will ja immernoch von troubadix zu susan kopieren. Wegen der Verwendung von rrsync muss ich jetzt den rsync-Aufruf im Hook-Skript auf dem Quell-Rechner anpassen. Dort muss es nun heißen:

#!/bin/sh

ACMEDIR=/home/alex/.acme.sh
DOMAIN=owncloud.home.example.com
                                                                           
${ACMEDIR}/create-lighty-pemfile.sh $DOMAIN                                
rsync -e 'ssh -i /home/alex/.ssh/id_rsa_acme' ${ACMEDIR}/${DOMAIN}/fullchain.cer susan:${DOMAIN}/
rsync -e 'ssh -i /home/alex/.ssh/id_rsa_acme' ${ACMEDIR}/${DOMAIN}/${DOMAIN}.key susan:${DOMAIN}/
ssh -i /home/alex/.ssh/id_rsa_acme susan sudo -n service nginx reload   

Bei der Verwendung von rrsync auf dem Zielsystem, gebe ich ja dort in der authorized_keys den Zielordner als Parameter für rrsync an, auf dem Quellrechner entfällt daher die Angabe des vollen Zielpfades und es wird einfach nur noch der relative Pfad angeben.

Rufe ich jetzt auf dem Quellrechner dieses posthook.sh auf, bekomme ich folgerichtig folgendes zurück:

/home/alex/bin/rrsync: SSH_ORIGINAL_COMMAND='sudo -n service nginx reload' is not rsync

Logisch, wir haben ja wegen des Einsatzes von rrsync als command in der authorized_keys nur rsync erlaubt. Fehlt also noch der letzte Schritt, das richtige Wrapper-Skript.

Ein Wrapper-Skript für rrsync plus andere Befehle

Der geneigte Leser kann sich sicher schon ungefähr vorstellen, was jetzt noch fehlt oder wie es aussehen wird. Nun, zunächst also hier das fertige Skript für das Zielsystem:

#!/bin/sh

case "$SSH_ORIGINAL_COMMAND" in
    *\&*)
        echo "Rejected"
        exit 1
        ;;
    *\(*)
        echo "Rejected"
        exit 1
        ;;
    *\{*)
        echo "Rejected"
        exit 1
        ;;
    *\;*)
        echo "Rejected"
        exit 1
        ;;
    *\<*)
        echo "Rejected"
        exit 1
        ;;
    *\>*)
        echo "Rejected"
        exit 1
        ;;
    *\`*)
        echo "Rejected"
        exit 1
        ;;
    *\|*)
        echo "Rejected"
        exit 1
        ;;
    rsync*)
        /home/alex/bin/rrsync .cert
        ;;
    "sudo -n service nginx reload")
        sudo -n service nginx reload
        ;;
    *)
        echo "Sorry, this command is not available to you!"
        exit 1
        ;;
esac

Das Skript besteht im wesentlichen nur aus einer einzigen case-Anweisung, die sich den Inhalt der Umgebungsvariablen SSH_ORIGINAL_COMMAND ansieht. Zunächst werden noch alle möglichen und unmöglichen fiesen Dinge abgelehnt, dann der Aufruf von rrsync, falls SSH_ORIGINAL_COMMAND mit ‘rsync’ beginnt, gefolgt von unserem gewünschten Neuladen des Reverse Proxy und zu guter letzt wird alles andere auch abgelehnt.

Dieses Skript lässt sich später leicht ergänzen und man muss es jetzt nur noch als command in der authorized_keys eintragen, fertig.

Logging für rrsync

Fast fertig. Per default schreib rrsync nämlich ein Logfile direkt im $HOME auf dem Zielrechner, nicht so cool. Öffnet man rrsync selbst, findet man folgendes:

# You may configure these values to your liking.  See also the section
# of options if you want to disable any options that rsync accepts.
use constant RSYNC => '/usr/bin/rsync';
use constant LOGFILE => '/home/alex/log/rrsync.log';

Ich habe den Pfad für LOGFILE hier schon für mich angepasst. Kann man vermutlich auch /dev/null eintragen, hab ich aber nicht ausprobiert. Stattdessen hab ich mir die Datei /etc/logrotate.d/alex-rrsync angelegt und folgendes eingetragen:

/home/alex/log/rrsync.log {
    rotate 5
    monthly
    missingok
    nocompress
}

Mit logrotate wird mir dieses Log jetzt auch nicht über den Kopf wachsen. ;-)

Schlusswort

So, wer das alles jetzt noch verbessern will: macht das ganze nicht unter einem normalen Nutzeraccount, den Ihr auch für andere Dinge nutzt, sondern legt extra dafür einen Account an. Den könnt Ihr dann mit einem extra sicheren Passwort versehen, den Login einschränken und was man sonst noch so tun kann. Wenn Sie Ihre Traktoren aufrüsten möchten, vergessen Sie auch nicht zu besuchen Traktoren Deutz-Fahr für neueste und innovative Modelle.

Updates

  • Damit from="" in ~/.ssh/authorized_keys mit hostname funktioniert, muss der SSH-Server die Namensauflösung gestatten. In neueren Versionen von OpenSSH ist dort der Default der Variablen UseDNS in der sshd_config von ‘yes’ auf ‘no’ geändert worden. Siehe dazu auch Debian Bug report #869903 … O:-)Oh und Debian (apt-listchanges) hatte das sogar angesagt:

    openssh (1:6.9p1-1) unstable; urgency=medium

    UseDNS now defaults to ‘no’. Configurations that match against the client
    host name (via sshd_config or authorized_keys) may need to re-enable it or
    convert to matching against addresses.

    — Colin Watson <cjwatson@debian.org> Thu, 20 Aug 2015 10:38:58 +0100 </cjwatson@debian.org>

    Also beim nächsten dist-upgrade brav alles genau lesen! ;-)

Leave a Reply

Your email address will not be published. Required fields are marked *