git-Server mit https und Client-Zertifikaten

Für ein neues (geschäftliches) Projekt fiel die Wahl für das Versionskontrollsystem auf git. Unglücklicherweise sind die einzigen Protokolle, was sämtliche Firmenfirewalls (halbwegs) unbeschadet passieren, http und https. Als weitere Sicherungsmaßnahme soll die Authentisierung über Client-Zertifiakte erfolgen.

Benötigt habe ich auf Serverseite apache2, die Module für webdav und ssl, sowie natürlich git. Alles zusammen sicher kein Hexenwerk, aber bis man die einzelnen Informationen zusammengesammelt hat dauert es ein wenig… damit’s beim nächsten Mal schneller geht, notiere ich das hier als Kochrezept :-)

Eigene CA aufsetzen, https konfigurieren

Zum Erzeugen und Einrichten eines selbstsignierten Zertifikats gibt es reichlich Anleitungen im Netz. Da ich aber auch Clientzertifikate verwalten muß, habe ich nach etwas komfortablerem gesucht. TinyCA war hierfür früher ein heißer Kandidat – da ich (leider) recht viel unter Windows arbeiten muß, habe ich mich schließlich für XCA entschieden: EIne praktische keine GUI zum erzeugen von Schlüsseln, Zertifikaten und dem Bearbeiten von Zertifikatsanträgen.

Hiermit habe ich also meine selbstsignierte Root-CA sowie generiert und hiermit das Serverzertifikat signiert. Die zugehörige Apache-Config sieht bei mir so aus:

SSLEngine on
SSLCertificateKeyFile /etc/apache2/ssl/serverkey.der
SSLCertificateFile /etc/apache2/ssl/servercertificate.crt

git über http(s)

Es gibt zwei grundsätzliche Möglichkeiten, git über http zur Verfügung zu stellen: Entweder via WebDAV oder über das seit git 1.7 vorhandene git http backend. WebDAV hat eine Reihe von Nachteilen: Zum einen ist es relativ langsam, zum anderen werden serverseitig keine Hooks ausgeführt – sehr ärgerlich, wenn man beispielsweise die Integration von git und Trac nutzen will. Die man-Page des git http backends hat hierzu reichlich Konfigurationsbeispiele, was das Leben an dieser Stelle sehr angenehm machte. Die Konfiguration sieht hierfür folgendermaßen aus (bei mir liegen alle Git-Repsitories unter http://githost/projekt.git):

DocumentRoot /var/www/githost
SetEnv GIT_PROJECT_ROOT /var/www/githost
SetEnv GIT_HTTP_EXPORT_ALL
AliasMatch ^/(.*\.git/objects/[0-9a-f]{2}/[0-9a-f]{38})$ /var/www/githost/$1
AliasMatch ^/(.*\.git/objects/pack/pack-[0-9a-f]{40}.(pack|idx))$ /var/www/githost/$1
ScriptAliasMatch "(?x)^/(.*/(HEAD | info/refs | objects/info/[^/]+ | git-(upload|receive)-pack))$" /usr/libexec/git-core/git-http-backend/$1

Repo-Browser mittels gitweb

Mit gitweb läßt sich das Repository (und die History) sehr einfach im Browser anzeigen. In meinem Beispiel sollte dies unter http://githost/ verfügbar sein.

Alias /gitweb.css /usr/share/gitweb/gitweb.css
Alias /git-logo.png /usr/share/gitweb/git-logo.png
Alias /git-favicon.png /usr/share/gitweb/git-favicon.png
ScriptAlias /index /usr/lib/cgi-bin/gitweb.cgi

Sicherung mit Client-Zertifikaten

Momentan wäre die Seite noch vollkommen offen - jedermann könnte über das git-Interface lesen und schreiben. Für die Sicherung eines Webservers (u.a. für den Einsatz von Client-Zertifikaten) bietet das SSL/TLS Strong Encryption Howto alle nötigen Informationen. Ich wollte erzwingen, daß nur Nutzer mit einem Clientzertifikat, das direkt von meiner selbstgenerierten CA signiert wurde, Zugriff erhalten. Der zugehörige Abschnitt in der Konfiguration sieht folgendermaßen aus:

SSLVerifyClient require
SSLVerifyDepth 1
SSLCACertificateFile /etc/apache2/ssl/MyDevelopmentCA.crt

Damit ist der Zugang auf Inhaber eines gültigen Zertifikats beschränkt. Möchte man aber beispielsweise den Zugriff auf die eigentlichen git-Repositories auf einige bestimmte Zertifikate einschränken, so kann man dies folgendermaßen erreichen:

<LocationMatch "/.*\.git/">
  SSLOptions +FakeBasicAuth
  AuthName "Authentication";
  AuthType Basic
  AuthUserFile /var/www/githost.passwd
  Require valid-user
</LocationMatch>;

Damit kann jeder Zertifikatsinhaber zwar den Quellcode via gitweb browsen, der Schreibzugriff ist jedoch auf die Zertifikate beschränkt, die in der Datei githost.passwd angegeben sind. Diese hat folgendes Format:

/CN=User 1/emailAddress=user.1@example.com:xxj31ZMTZzkVA /CN=User 2/emailAddress=user.2@example.com:xxj31ZMTZzkVA

Der „Username“ eines Zertifikats kann mit dem Befehl

openssl x509 -noout -subject -in certificate.crt

ermittelt werden (in obigem Beispiel waren nur die Felder Common Name und E-Mail Address gesetzt). Der Password-Hash hat nur eine Dummy-Funktion, um das Dateiformat zu erhalten – er besteht aus dem (DES-gekrypteten) Wort „password“ :-) Es wird niemals für eine echte Passwortabfrage benutzt.

Einrichten der Zertifikate im lokalen git

Um git den Zugriff zu erlauben, mußten nun noch das Zertifikat der CA sowie mein Clientzertifikat und der zugehörige Schlüssel in git konfiguriert werden. Dazu habe ich das selbstsignierte CA-Zertifikat und mein Client-Zertifikat als .crt-Datei und den Client-Schlüssel im .pem-Format exportiert. Nun mußte nur noch die entsprechende git-Konfiguration vorgenommen werden (die global-Option ist je nach Bedarf zu nutzen):

git config [--global] http.sslCAInfo /path/to/MyDevelopmentCA.crt
git config [--global] http.sslKey /path/to/ClientCert.pem
git config [--global] http.sslCert /path/to/ClientCert.crt

Troubleshooting

Falls es Probleme geben sollte: Als sehr hilfreich hat sich die Option von git erwiesen, mit der die http-Header aus der (intern verwendeten) libcurl ausgegeben werden. Dies wird über eine Umgebungsvariable gesteuert, beispielsweise folgendermaßen:

GIT_CURL_VERBOSE=1 git fetch

Beim Committen einer großen Datenmenge warf mir git folgenden Fehler entgegen:

fatal: expected ok/error, helper said '2004�f��,'�c6��}{�c�eM��#�&gt;�'

Das Problem tritt offenbar nur bei git via http(s) auf; die Lösung (hier entdeckt) ist, den Puffer (auf der Client-Seite) für http-Commits zu erhöhen (im Beispiel hier auf 100 MB):

git config http.postBuffer 104857600

Das war’s auch schon. Eigentlich recht schmerzfrei - ich bin gespannt, welche Probleme der Praxisbetrieb bringt :-)

Edit: Inzwischen habe ich auch das Gegenstück, ein Howto für die Einrichtung von lokalen git-Clones geschrieben.