Archives

letsencrypt mit acme.sh und lighttpd

This entry is part 1 of 3 in the series acme.sh, reverse proxy, ssh und rsync

Vorgeschichte

Auf meinem Heimserver läuft owncloud, nein nicht nextcloud, sondern das Debian-Paket1. Konkret läuft das in einer virtuellen Maschine (troubadix) auf einem lighttpd. Ganz grob hatte ich das schonmal in owncloud mit lighttpd auf Debian Jessie beschrieben. An die VM komm ich aber nicht von außen ran, wenn ich das will, muss ich über den Reverse Proxy (nginx), der auf einer anderen VM (susan) läuft. Da mobile Geräte intern wie extern über die gleiche Adresse zugreifen sollen, gibt es dafür die Adresse owncloud.home.example.com2 und von extern landet man damit über die externe IPv4 und Port Forwarding im Router auf dem Reverse Proxy. Mein interner DNS3 löst die selbe Adresse als Alias bzw. cname direkt auf die VM mit der owncloud Instanz auf. Zugriffe sollen alle verschlüsselt ablaufen, zuvor sorgte ein von Hand installiertes Zertifikat von CAcert dafür. Der aufmerksame Leser wird sich denken können: dieses musste einmal im lighttpd (troubadix) und einmal im nginx (susan) installiert werden.

Das funktionierte alles soweit, bis das Zertifikat abgelaufen war. Aus diversen Gründen sollte das neue Zertifikat von letsencrypt kommen.

Zertifikate von letsencrypt

Was letsencrypt ist oder macht, die Erklärung spar ich mir an dieser Stelle. Fakt ist, dass es Alternativen zum offiziellen sogenannten certbot gibt und nicht zu knapp: ACME Client Implementations. Ich hatte mir kurz certbot und dehydrated angesehen, einige der gelisteten Implementierungen wird es ab Debian 9 (Stretch) auch direkt als Debian-Paket geben. Zum Einsatz kommt bei mir jetzt aber acme.sh, da ich es schnell verstanden habe und zügig einrichten konnte, naja fast. Ein bisschen tricky war es die webroot-Methode so einzurichten, dass auf der eigentlich voll verschlüsselten Domain das acme challenge Verfahren unverschlüsselt ablaufen kann.

acme.sh

Zur Installation von acme.sh hab ich als normaler User (alex) das GitHub-Repository geklont und etwas widerwillig mit ./acme.sh --install --accountemail post@lespocky.de installiert. Der Empfehlung das als root zu machen, würde ich nicht folgen. Eher legt man sich dafür noch einen extra User an, ich hab hier meinen normalen User benutzt, was aus security-Sicht auch nicht zu empfehlen ist. ;-) Letztlich braucht dieser Nutzer nur Schreibrechte in seinem eigenen $HOME und in einem Ordner, den der Webserver als webroot ausliefern kann.

Blöd: acme.sh hängt ungefragt folgende Zeile an meine ~/.zshrc an:

Der Inhalt dessen:

Die beiden Zeilen hab ich aus der ~/.zshrc entfernt und stattdessen richtigerweise an ~/.zshenv angehängt. Ein entsprechendes Ticket upstream ist angelegt. Davon abgesehen, funktioniert das Skript aber sauber. Folgenden Aufruf hab ich benutzt:4

Wie man schon sieht, werden die challenge sachen später in einem webroot in oder unterhalb von /var/www/html/le landen. Die Dateirechte und Gruppenzugehörigkeit dieses Ordners sind so gesetzt, dass der unpriviligierte Nutzer mit seinem cronjob drin schreiben und der webserver draus lesen kann.

Die nötige Zeile für /etc/sudoers, damit der cronjob auch den Webserver neu starten kann:

Daneben interessant ist sicherlich der post-hook, der nötig ist, weil lighttpd Zertifikat und private key zusammen in einer Datei erwartet, die Datei sieht so aus:

lighttpd

Der erste Teil der lighty-Konfiguration steht bei mir in /etc/lighttpd/lighttpd.conf und inkludiert meine ssl-Optionen:

Diese Datei hab ich mir vom Mozilla SSL Configuration Generator erstellen lassen und von Hand angepasst. Die für den Betrieb mit acme.sh angepassten Zeilen:

Wobei die erste Datei diejenige ist, die unser post-hook Skript generiert und die zweite das CA Zertifikat, das acme.sh runterlädt.

Fehlen noch zwei Puzzlestücke. Bei Debian schmeißt man die typischerweise nach /etc/lighttpd/conf-available und kann sie später mit lighty-enable-mod und lighty-disable-mod leicht aktivieren und deaktivieren. Teil eins ist etwas kürzer und steckt hier in der Datei 53-le.conf und sieht so aus:

Gut zu sehen, das zuvor in der acme Konfiguration angegebene webroot. Knifflig und etwas umfangreicher jetzt die owncloud-Konfiguration, hier mal aus der Datei /etc/lighttpd/conf-available/59-owncloud.conf in voller Länge:

Was hier realisiert wird, ist die Umleitung von allem unverschlüsselten Traffic auf https außer für den Teil, der für die acme challenge unverschlüsselt bleiben muss. Dazu weitere well-known URLs für einfacheren Kalenderzugriff aus bestimmten Anwendungen, das Verbot das Datenverzeichnis zu erreichen und die Deaktivierung von directory listing, alles für owncloud, hat nichts mehr mit acme zu tun, auch der unterste Abschnitt dient nur nochmal zum Zugriff auf owncloud über andere Adressen.

Erzwingt man hier keinen Zugriff über https, kann man sich einiges der Konfiguration sparen, aber der Sinn von letsencrypt ist ja grad, möglichst viel über https abzuwickeln.

Übertragen der Zertifikate auf Reverse Proxy

In der Praxis hab ich den lighttpd nicht direkt am Netz hängen, davor ist noch ein nginx als Reverse Proxy, der HTTPS mit dem gleichen Zertifikat sprechen soll. Weil das hier thematisch nicht mehr so gut dazu passt, hab ich eine kleine Serie von Beiträgen draus gemacht. Zu Teil 2 hier entlang: Unbeaufsichtigte Aktionen mit SSH.

  1. das wird noch ein Spaß mit dem Upgrade beim nächsten Release :-/ []
  2. Adresse von der Redaktion geändert []
  3. fli4l rockt! []
  4. streng genommen, war es ein Aufruf mit mehr Domains, alle mit eigenem -d davor, angepasst auf meine lokalen Bedürfnisse []

Unbeaufsichtigte Aktionen mit SSH

This entry is part 2 of 3 in the series acme.sh, reverse proxy, ssh und rsync

Ein bisschen sperrig die Überschrift heute, also worum soll es gehen? Ich will sicher und automatisiert verschiedene Dateien von einem Server (troubadix) auf einen anderen (susan) übertragen und dann noch extra Kommandos ausführen und das ganze aber so gut wie möglich absichern. Ganz konkret geht es darum die Zertifikatsdateien die mir acme.sh in der virtuellen Maschine für owncloud beschert, auf die andere VM mit dem reverse proxy zu übertragen.

Dies ist Teil zwei einer Mini-Serie, im ersten Teil ging’s um letsencrypt mit acme.sh und lighttpd. Im zweiten Teil hier beschreibe ich den unbeaufsichtigten Zugriff mit SSH, beispielsweise für mit cron auszuführende Skripte. Im dritten Teil wird das ganze dann aufgebohrt für die Verwendung mit rsync.

SSH-Zugriff mit Zertifikat

Auftritt OpenSSH. Die Secure Shell kann sehr sehr nützlich sein. Für den automatisierten Betrieb ohne Eingriff des Nutzers bieten sich Zertifikate an. Da diese dann kein Passwort haben dürfen, das könnte ja niemand eingeben, will man den Zugriff gesondert absichern, doch zunächst erstellen wir erstmal auf dem Quellsystem so ein Zertifikat mit ssh-keygen:

Wie man leicht sieht, hab ich den per default angegebenen Pfad geändert, um einen separaten Key nur für diesen Zweck zu erstellen. Bei der Passphrase drückt man zweimal direkt Enter ohne etwas einzugeben. Wichtig ist dann die Datei ~/.ssh/id_rsa_acme.pub mit dem public key. Den Inhalt dieser Datei hängen wir auf dem Zielsystem an die Datei ~/.ssh/authorized_keys an:1

Erst:

Dann copy und paste und:

An dieser Stelle empfiehlt es sich den Zugriff über diesen Key vom Quell- auf das Zielsystem zu testen:

Das sollte ohne Passworteingabe funktionieren!

Posthook für acme.sh

Auf dem Zielsystem benötigen wir für den nginx zwei verschiedene Dateien, die wir mit rsync übertragen wollen. Außerdem soll dort der nginx diese auch einlesen, muss also einmal neu geladen werden. Die entsprechenden Befehle sind schnell ausgemacht und in ein Skript posthook.sh übertragen, das dann in den Optionen von acme.sh eingetragen wird.

Der Zielordner auf dem anderen System soll /home/alex/.cert sein und wurde zuvor angelegt und natürlich muss der interne DNS susan als hostname sinnvoll auflösen können. Die Änderung an der /etc/sudoers auf dem Zielrechner ist trivial:

Kann man bis hierher nochmal testen, soweit so gut, aber der Zugriff soll jetzt eingeschränkt werden.

SSH auf ein Kommando einschränken

Bis hierhin könnte nun jeder, der auf der Maschine troubadix in den Besitz des Schlüssels id_rsa_acme gelangt sich auf susan einloggen und dort als Nutzer alex beliebigen Unfug anstellen. Das soll weiter eingeschränkt werden und dazu nutzen wir die bereits bekannte Datei ~/.ssh/authorized_keys. In der Manpage2 heißt es im Abschnitt AUTHORIZED_KEYS FILE FORMAT zur Option command:

Specifies that the command is executed whenever this key is used for authentication. The command supplied by the user (if any) is ignored. The command is run on a pty if the client requests a pty; otherwise it is run without a tty. If an 8-bit clean channel is required, one must not request a pty or should specify no-pty. A quote may be included in the command by quoting it with a backslash. This option might be useful to restrict certain public keys to perform just a specific operation. An example might be a key that permits remote backups but nothing else. Note that the client may specify TCP and/or X11 forwarding unless they are explicitly prohibited. The command originally supplied by the client is available in the SSH_ORIGINAL_COMMAND environment variable. Note that this option applies to shell, command or subsystem execution. Also note that this command may be superseded by either a sshd_config(5) ForceCommand directive or a command embedded in a certificate.

Wir öffnen also auf dem Zielsystem nochmal die Datei ~/.ssh/authorized_keys und suchen die zuvor hinzugefügte Zeile. Durch Voranstellen der Option command lässt sich nun das auszuführende Kommando festlegen. Mit anderen Worten: bei Authentifizierung mit dem zuvor generierten Key, kann der Nutzer nichts anderes mehr ausführen, es wird nur das in authorized_keys festgelegte Kommando ausgeführt. Beispiel:

Versuche ich mich jetzt von der Quellmaschine einzuloggen und ein anderes Kommando (hier bspw. uname) auszuführen:

Wie man sieht, wurde nicht uname -a ausgeführt, sondern /bin/date – diesen Mechanismus werden wir nun nutzen, um nur die in unserem posthook-Skript benötigten Befehle zu erlauben. Ein kleines Problem gibt es noch: wir können nur ein Kommando in authorized_keys eintragen, wollen aber mehrere Kommandos (rsync, Neustart des nginx) erlauben. Dazu schreiben wir uns auf dem Zielsystem ein Wrapper-Skript, das wir dann in der authorized_keys eintragen.

Einfaches Wrapper-Skript für SSH

In der oben zitierten Manpage wurde die Umgebungsvariable SSH_ORIGINAL_COMMAND erwähnt, die wir uns jetzt zunutze machen werden. Zunächst mal mit einem sehr einfachen Beispiel:

Auf dem Zielsystem susan wird jetzt in authorized_keys dieses Skript eingetragen, statt dem zuvor probeweise eingetragenem /bin/date und dann passiert folgendes: Wenn SSH_ORIGINAL_COMMAND gesetzt ist, wird dessen Inhalt in der Datei $HOME/ssh-command-log geloggt und dieser Befehl dann ausgeführt. Test hier: der Befehl muss erfolgreich ausgeführt werden und der korrekte Befehl muss in der Logdatei stehen. Führen wir auf dem Quellsystem troubadix jetzt beispielsweise das zu Anfang erstellte posthook Skript von acme.sh aus, tauchen derartige Einträge in der Logdatei auf susan auf:

Im dritten Teil der Serie wird es dann darum gehen, die Zugriffe auf Basis von SSH_ORIGINAL_COMMAND einzuschränken.

  1. Profis können auch ssh-copy-id benutzen. []
  2. man 5 authorized_keys []

Automatisches rsync über SSH absichern

This entry is part 3 of 3 in the series acme.sh, reverse proxy, ssh und rsync

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:

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:

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:

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:

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:

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:

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:

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:

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.