git-Server mit https und Client-Zertifikaten

Für ein neues (ge­schäft­li­ches) Pro­jekt fiel die Wahl für das Ver­si­ons­kon­troll­sys­tem auf git. Un­glück­li­cher­wei­se sind die ein­zi­gen Pro­to­kol­le, was sämt­li­che Fir­men­fire­walls (halb­wegs) un­be­scha­det pas­sie­ren, http und https. Als wei­te­re Si­che­rungs­maß­nah­me soll die Au­then­ti­sie­rung über Cli­ent-Zer­ti­fi­ak­te er­fol­gen.

Be­nö­tigt habe ich auf Ser­ver­sei­te apa­che2, die Mo­du­le für web­dav und ssl, sowie na­tür­lich git. Alles zu­sam­men si­cher kein He­xen­werk, aber bis man die ein­zel­nen In­for­ma­tio­nen zu­sam­men­ge­sam­melt hat dau­ert es ein wenig… damit’s beim nächs­ten Mal schnel­ler geht, no­tie­re ich das hier als Koch­re­zept :-)

Ei­ge­ne CA auf­set­zen, https kon­fi­gu­rie­ren

Zum Er­zeu­gen und Ein­rich­ten eines selbst­si­gnier­ten Zer­ti­fi­kats gibt es reich­lich An­lei­tun­gen im Netz. Da ich aber auch Cli­ent­zer­ti­fi­ka­te ver­wal­ten muß, habe ich nach etwas kom­for­ta­ble­rem ge­sucht. Ti­ny­CA war hier­für frü­her ein hei­ßer Kan­di­dat – da ich (lei­der) recht viel unter Win­dows ar­bei­ten muß, habe ich mich schlie­ß­lich für XCA ent­schie­den: EIne prak­ti­sche keine GUI zum er­zeu­gen von Schlüs­seln, Zer­ti­fi­ka­ten und dem Be­ar­bei­ten von Zer­ti­fi­kats­an­trä­gen.

Hier­mit habe ich also meine selbst­si­gnier­te Root-CA sowie ge­ne­riert und hier­mit das Ser­ver­zer­ti­fi­kat si­gniert. Die zu­ge­hö­ri­ge Apa­che-Con­fig 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 grund­sätz­li­che Mög­lich­kei­ten, git über http zur Ver­fü­gung zu stel­len: Ent­we­der via Web­DAV oder über das seit git 1.7 vor­han­de­ne git http ba­ckend. Web­DAV hat eine Reihe von Nach­tei­len: Zum einen ist es re­la­tiv lang­sam, zum an­de­ren wer­den ser­ver­sei­tig keine Hooks aus­ge­führt – sehr är­ger­lich, wenn man bei­spiels­wei­se die In­te­gra­ti­on von git und Trac nut­zen will. Die man-Pa­ge des git http ba­ckends hat hier­zu reich­lich Kon­fi­gu­ra­ti­ons­bei­spie­le, was das Leben an die­ser Stel­le sehr an­ge­nehm mach­te. Die Kon­fi­gu­ra­ti­on sieht hier­für fol­gen­der­ma­ßen aus (bei mir lie­gen alle Git-Rep­si­to­ries unter http://gi­thost/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

Re­po-Brow­ser mit­tels git­web

Mit git­web läßt sich das Re­po­si­to­ry (und die His­to­ry) sehr ein­fach im Brow­ser an­zei­gen. In mei­nem Bei­spiel soll­te dies unter http://gi­thost/ ver­füg­bar 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

Si­che­rung mit Cli­ent-Zer­ti­fi­ka­ten

Mo­men­tan wäre die Seite noch voll­kom­men offen - je­der­mann könn­te über das git-In­ter­face lesen und schrei­ben. Für die Si­che­rung eines Web­ser­vers (u.a. für den Ein­satz von Cli­ent-Zer­ti­fi­ka­ten) bie­tet das SSL/TLS Strong En­cryp­ti­on Howto alle nö­ti­gen In­for­ma­tio­nen. Ich woll­te er­zwin­gen, daß nur Nut­zer mit einem Cli­ent­zer­ti­fi­kat, das di­rekt von mei­ner selbst­ge­ne­rier­ten CA si­gniert wurde, Zu­griff er­hal­ten. Der zu­ge­hö­ri­ge Ab­schnitt in der Kon­fi­gu­ra­ti­on sieht fol­gen­der­ma­ßen aus:

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

Damit ist der Zu­gang auf In­ha­ber eines gül­ti­gen Zer­ti­fi­kats be­schränkt. Möch­te man aber bei­spiels­wei­se den Zu­griff auf die ei­gent­li­chen git-Re­po­si­to­ries auf ei­ni­ge be­stimm­te Zer­ti­fi­ka­te ein­schrän­ken, so kann man dies fol­gen­der­ma­ßen er­rei­chen:

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

Damit kann jeder Zer­ti­fi­kats­in­ha­ber zwar den Quell­code via git­web brow­sen, der Schreib­zu­griff ist je­doch auf die Zer­ti­fi­ka­te be­schränkt, die in der Datei githost.​passwd an­ge­ge­ben sind. Diese hat fol­gen­des For­mat:

/CN=User 1/emai­l­Ad­dress=user.​1@​example.​com:xx­j31ZMTZz­kVA /CN=User 2/emai­l­Ad­dress=user.​2@​example.​com:xx­j31ZMTZz­kVA

Der „User­na­me“ eines Zer­ti­fi­kats kann mit dem Be­fehl

openssl x509 -noout -subject -in certificate.crt

er­mit­telt wer­den (in obi­gem Bei­spiel waren nur die Fel­der Com­mon Name und E-Mail Ad­dress ge­setzt). Der Pass­word-Hash hat nur eine Dum­my-Funk­ti­on, um das Da­tei­for­mat zu er­hal­ten – er be­steht aus dem (DES-ge­kryp­te­ten) Wort „pass­word“ :-) Es wird nie­mals für eine echte Pass­wort­ab­fra­ge be­nutzt.

Ein­rich­ten der Zer­ti­fi­ka­te im lo­ka­len git

Um git den Zu­griff zu er­lau­ben, mu­ß­ten nun noch das Zer­ti­fi­kat der CA sowie mein Cli­ent­zer­ti­fi­kat und der zu­ge­hö­ri­ge Schlüs­sel in git kon­fi­gu­riert wer­den. Dazu habe ich das selbst­si­gnier­te CA-Zer­ti­fi­kat und mein Cli­ent-Zer­ti­fi­kat als .crt-Da­tei und den Cli­ent-Schlüs­sel im .pem-For­mat ex­por­tiert. Nun mußte nur noch die ent­spre­chen­de git-Kon­fi­gu­ra­ti­on vor­ge­nom­men wer­den (die glo­bal-Op­ti­on ist je nach Be­darf zu nut­zen):

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

Trou­ble­shoo­ting

Falls es Pro­ble­me geben soll­te: Als sehr hilf­reich hat sich die Op­ti­on von git er­wie­sen, mit der die http-Hea­der aus der (in­tern ver­wen­de­ten) lib­curl aus­ge­ge­ben wer­den. Dies wird über eine Um­ge­bungs­va­ria­ble ge­steu­ert, bei­spiels­wei­se fol­gen­der­ma­ßen:

GIT_CURL_VERBOSE=1 git fetch

Beim Com­mit­ten einer gro­ßen Da­ten­men­ge warf mir git fol­gen­den Feh­ler ent­ge­gen:

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

Das Pro­blem tritt of­fen­bar nur bei git via http(s) auf; die Lö­sung (hier ent­deckt) ist, den Puf­fer (auf der Cli­ent-Sei­te) für http-Com­mits zu er­hö­hen (im Bei­spiel hier auf 100 MB):

git config http.postBuffer 104857600

Das war’s auch schon. Ei­gent­lich recht schmerz­frei - ich bin ge­spannt, wel­che Pro­ble­me der Pra­xis­be­trieb bringt :-)

Edit: In­zwi­schen habe ich auch das Ge­gen­stück, ein Howto für die Ein­rich­tung von lo­ka­len git-Clo­nes ge­schrie­ben.