Jedem Admin wird flau im Magen, wenn es darum geht einen öffentlich zugänglichen CVS-Server aufzusetzen. Das von CVS genutzte pserver-Protokoll hat gerade in puncto Sicherheit einige Mängel. Allein der Umstand, dass die Datenübertragung zwischen Client und Server ohne Verschlüsselung stattfindet und Passwörter und Dateien aus dem Repository im Klartext durch's Netz wandern, erzeugt wenig Vertrauen. Zudem hatte GNU CVS in der Vergangenheit häufig mit Sicherheitsproblemen zu kämpfen.
Abhilfe schafft hier der Einsatz von SSH als Übertragungsprotokoll. Wenn man den CVS-Server dann auch noch in einer chroot-Umgebung betreibt, sollte man als Admin wieder ruhig schlafen können.
Wie man einen so gesicherten CVS-Server unter OpenBSD konfiguriert möchte ich in dem folgenden Artikel beschreiben.
Die Partition, auf der das “Gefängnis” für den CVS-Server und das Repository später untergebracht werden soll, sollte nicht mit den Optionen “nodev“ und “noexec“ gemountet sein. Wird die chroot-Umgebung wie im nachfolgenden Text auf der /home-Partition eingerichtet, dann muss vorher in der Datei /etc/fstab geprüft werden, ob die vorher genannten Optionen gesetzt sind und diese dann ggf. entfernt werden.
So könnte ein für den geplanten Einsatzzweck tauglicher Eintrag für die /home-Partition aussehen:
# grep »/home« /etc/fstab
/dev/sd0h /home ffs rw,nosuid,softdep 1 2
Damit CVS und SSH in der chroot-Umgebung laufen, muss ein bißchen Vorarbeit geleistet werden. Unter anderem müsen ein paar benötigte Programme und Bibliotheken kopiert werden. Zuerst erstellen wir ein Verzeichnis, in dem die chroot-Umgebung eingerichtet werden soll. In unserem Fall soll die chroot-Umgebung im Verzeichnis /home/cvs_chroot ihre neue Heimat finden. Als nächstes erstellen wir eine grundlegende Verzeichnisstruktur:
# mkdir /home/cvs_chroot
# cd /home/cvs_chroot/
# mkdir -p bin dev etc lib libexec/auth sbin tmp \
var/empty var/run var/chroot/sshd
# ln -s . usr
# chmod 1777 tmp
Es wird außerdem noch ein Link usr der auf die Wurzel der Chroot-Umgebung zeigt. Das vereinfacht die Verzeichnisstruktur. Zudem ändern wir noch Berechtigungen des tmp-Verzeichnisses auf 1777.
Als nächstes kopieren wir alle benötigten Dateien und Bibliotheken:
# cd /home/cvs_chroot
# cp /bin/ksh /bin/ls /bin/rm /bin/mkdir /bin/cp \
/usr/bin/cvs /usr/bin/passwd /usr/bin/login bin \
/bin/mv /bin/echo
# cp /sbin/nologin /usr/sbin/pwd_mkdb \
/usr/sbin/sshd sbin
# cp /etc/master.passwd /etc/group etc
# cp /usr/libexec/ld.so libexec
# cp -rf /etc/ssh etc
# cp /etc/login.conf etc
# cp /usr/libexec/auth/login_passwd libexec/auth
# cp `ldd bin/passwd sbin/sshd \
libexec/auth/login_passwd | \
grep -v -e 'libexec' -e 'bin' -e 'sbin' \
-e 'Start' | awk '{print $7}' | sort | \
uniq` lib
Zum Schluss erstellen wir noch fehlende Gerätedateien im dev-Verzeichnis:
# cd /home/cvs_chroot/dev
# /dev/MAKEDEV std pty0 random
An diesem Punkt editieren wir die Nutzer- und Gruppendateien und werfen alle nicht gebrauchten Einträge raus und fügen gewünschte Einträge ein:
# cd /home/cvs_chroot
# vi etc/group
wheel:*:0:root
sshd:*:27:
_shadow:*:65:
nogroup:*:32766:
nobody:*:32767:
cvs:*:500:
# vi etc/master.passwd
root:*:0:0:daemon:0:0:root:/root:/bin/ksh
sshd:*:27:27::0:0:sshd privsep:/var/empty:/sbin/nologin
nobody:*:32767:32767::0:0:Unprivileged user:/nonexistent:/sbin/nologin
cvs:*:500:500::0:0:cvs:/cvs:/sbin/nologin
ahein:*:1000:500::0:0:Andreas Hein:/home/ahein:/bin/ksh
# mkdir /home/ahein
# chmod 555 /home/ahein
Wir haben also in unserer User-Datenbank einen Nutzer ahein für den wir auch nach dem Editieren der Datei master.passwd ein Homeverzeichnis anlegen. Dieses verzeichnis ist wichtig, wenn der Nutzer später seinen SSH-PublicKey hinterlegen möchte. Ansonsten könnte man auch /var/empty als Homeverzeichnis angegeben werden.
Jetzt müssen nur noch die fertigen Userdatenbanken generiert werden:
# chroot /home/cvs_chroot
# passwd ahein
Changing local password for ahein.
New password:
Retype new password:
# exit
Wenn keine Fehler auftreten scheint soweit alles zu funktionieren.
Nachdem die chroot-Umgebung nun fertig eingerichtet ist können wir uns daran machen, den chroot-SSH-Server zu konfigurieren. Zuerst generieren wir dazu für den CVS-Server eigene SSH-Schlüsselpaare. Beim Ausführen der folgenden Befehle wird gefragt, ob die vorhandenen Schlüssel überschrieben werden können. Diese Frage sollte mit “ja” beantwortet werden, da es sich hierbei um die (alten) Schlüssel handelt, die wir in Schritt 1 mit in die chroot-Umgebung kopiert haben.
# cd /home/cvs_chroot/etc/ssh
# /usr/bin/ssh-keygen -t rsa1 -b 1024 -f ssh_host_key -N ''
# /usr/bin/ssh-keygen -t dsa -f ssh_host_dsa_key -N ''
# /usr/bin/ssh-keygen -t rsa -f ssh_host_rsa_key -N ''
Als nächstes müssen wir dem chroot-sshd noch beibringen, einen anderen Port als den Standard-Port 22 zu nutzen. Da auf dem Host-System wahrscheinlich schon ein SSH-Server auf diesem Port läuft, käme es ansonsten zu einer Kollision. Dazu bearbeiten wir die Datei /etc/ssh/sshd_config mit dem Editor unserer Wahl. Nachfolgend sind die Änderungen an der Datei sshd_config aufgelistet:
# cd /home/cvs_chroot/etc/ssh
# vi sshd_config
Port 2022
…
X11Forwarding no
AllowTcpForwarding no
ForceCommand cvs server
Unser Server wird also auf dem Port 2022 auf Verbindungen warten. Die letzten drei Einträge am Ende der Datei sind nicht unbedingt nötig, sollte aber auch ein anonymer read-only CVS-Zugriff ermöglicht werden, sollten die Einträge nicht fehlen, um Missbrauch des Servers zu verhindern. Die Einträge beschränken alle Nutzer auf die Nutzung von cvs (kein Shell-Aufruf mehr möglich), verbieten X11-Forwarding und das Tunneln von TCP-Verbindungen.
Das Starten des Servers ist relativ einfach:
# chroot /home/cvs_chroot /sbin/sshd
Da unser CVS-Server nicht auf dem Standard-Port läuft, auf dem der ssh-Client ihn erwarten würde, müssen wir dem Client beibringen, beim Zugriff den neuen Port 2022 zu nutzen. Da man dem CVS-Kommando leider die Portnummer nicht mitgeben kann, muss der ssh-Client selber diese Aufgabe übernehmen. Dazu ist vom CVS-Nutzer die eigene SSH-Konfiguration anzupassen:
$ vi ~/.ssh/config
Host cvs.myserver.org
Port 2022
Dieser Lösungsansatz setzt allerdings vorraus, das man dem CVS-Server einen eigenen Namen im DNS geben kann. Gibt man hier z.B. die IP des Servers oder den “normalen” Namen des Servers an, dann wird ein Anmelden am System über den “richtigen” SSH-Server etwas schwieriger, da ssh zuerst immer den Port 2022 zu kontaktieren versucht. Es reicht aber aus, einen Eintrag in der Datei /etc/hosts zu machen.
Den Zugriff auf den chroot-SSH testen wir nun, indem wir versuchen uns anzumelden:
$ ssh ahein@cvs.myserver.org
The authenticity of host '[cvs.myserver.org]:2022 ([::1]:2022)'
can't be established.
RSA key fingerprint is
b1:73:73:d7:ae:1f:cd:79:dd:fb:8b:e6:64:7b:30:bf.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '[cvs.myserver.org:2022' (RSA) to the
list of known hosts.
ahein@cvs.myserver.org's password:
^C
$
Beim ersten Anmeldeversuch sollte ssh über den unbekannten RSA-Schlüssel informieren. Daran erkennen wir auch, dass das Generieren der Schlüssel in Schritt 3 funktioniert hat. Wenn wir nun das korrekte Passwort unseres CVS-Nutzers eingeben sollte gar nichts passieren. Der Cursor bleibt einfach stehen und es passiert nichts. Und genau so soll es auch sein. Bei der Konfiguration des Servers haben wir Shell-Access ausgeschlossen, weil nur cvs-Kommandos erlaubt sind. Mit Strg-C kann man zur Shell zurückkehren. Der chroot-SSH-Server funktioniert.
Nachdem nun der Server läuft kann das Repository vom Client aus initialisiert werden. Dazu müssen nur die CVS-Umgebungsvariablen CVSROOT und CVS_RSH gesetzt werden:
$ export CVSROOT=:ext:ahein@cvs.myserver.org:/cvs
$ export CVS_RSH=ssh
$ cvs init
ahein@cvs.myserver.org's password:
$
Bemerkung: Die beiden Zeilen zum Setzen der Umgebungsvariablen können in der Datei ~/.profile des Nutzers gespeichert werden. So sind sie nach dem Login automatisch gesetzt.
Nun, nachdem das Repository erfolgreich angelegt wurde sollte man noch die Berechtigungen überprüfen. Das machen wir auf dem Server mit folgenden Kommandos:
Unser gesicherter CVS-Server ist jetzt fertig und einsatzbereit.
Damit der Server auch automatisch beim Hochfahren des OpenBSD-Systems gestartet wird, sollte man die folgende Zeile in der Datei /etc/rc.local unterbringen:
echo ' cvs';
/usr/sbin/chroot /home/cvs_chroot /sbin/sshd
Hinweis: Unter Linux sollte man sich ein kleines Init-Script schreiben, das diese Aufgabe übernimmt.
Will man den CVS-Server beenden, so findet man die ProcessID in der Datei /home/cvs_chroot/var/run/sshd.pid. Das führt uns zu folgendem Aufruf von kill(1):
# kill `cat /home/cvs_chroot/var/run/sshd.pid`
Das Anlegen und Löschen von CVS-Nutzern ist durch das chroot etwas komplizierter. Es muss die Datei /home/cvs_chroot/etc/master.passwd bearbeitet werden und anschließend wie in Punkt 2 mit dem Befehl pwd_mkdb neue Userdatenbanken erzeugt werden. Wenn man häufig neue User hinzufügt oder löscht sollte man sich dafür ein kleines Shell-Script schreiben. Eine andere Möglichkeit wäre es, die Tools useradd und userdel mit in die chroot-umgebung zu kopieren — das ist aber meiner Meinung nach keine so gute Idee, da so ein chroot-Gefängnis nur das nötigste enthalten sollte.
Um die ständigen Passwort-Abfragen beim Nutzen von CVS zu vermeiden, können Nutzer ihren SSH-Publickey in ihrem Homeverzeichnis im chroot ablegen. Das kann root z.B. so erledigen:
# mkdir -p /home/cvs_chroot/home/ahein/.ssh
# cp id_rsa.pub /home/cvs_chroot/home/ahein/.ssh/authorized_keys
Viel Spass mit dem neuen, gesicherten CVS-Server den man so auch der Öffentlichkeit zugänglich machen kann, ohne schlaflose Nächte zu haben.
»Teile Dein Wissen. Das ist ein Weg, Unsterblichkeit zu erlangen.« — Dalai Lama