Teil 1: PKI und Zertifikate – ein paar Grundlagen
Teil 2: IP-Whitelist durch Client-Zertifikate ersetzen
Teil 3: Client Certificates Webapp
Die 90er haben angerufen – sie wollen ihre IP-Whitelists zurück!
Wer kennt das nicht: man hat eine Plattform, auf die die Entwickler zugreifen müssen und noch ein paar externe Dienstleister – aber niemand sonst.
Und wie das so ist: der eine kann kein Basic-Auth, der andere kein oauth und der dritte schickt Credentials gerne mal im Klartext rum.
Also denkt sich der zuständige Entwickler und/oder Manager: Ok, dann scheiß darauf – dann packen wir da halt ein IP-Whitelisting hin und hoffen, dass sich da nicht all zu oft was ändert!
Die Änderung
Das funktioniert dann meistens solange, bis ein Dienstleister kommt, der keine feste IP hat – z.B. weil der seine Systeme inzwischen in der Cloud hostet und dort dynamisch die Cluster hoch und runter fährt, die damit immer eine neue IP bekommen.
Oder wahlweise, bis ein Entwickler aus dem Homeoffice auf die Systeme zugreifen muss, und feststellt, dass das auch mit Basic-auth nix wird, weil der ganze Kram gar nicht darauf ausgelegt ist – weil im Office hat das ja immer funktioniert!
Und dann fangen die Entwickler an, zumindest einmal nachzufragen „Gibt es nicht eine Möglichkeit, dass ich unabhängig von meiner IP auf die Systeme zugreifen kann? Und ohne dass ich ständig meine Credentials eingeben muss!? Aber für Außenstehende soll das weiterhin nicht erreichbar sein!“
Nun, die Antwort ist einfach: Ja! Gibt es!
Die Umsetzung ist allerdings nicht ganz so einfach. Wer wissen möchte, warum dem so ist, wirft einen Blick in den Artikel „PKI und Zertifikate – ein paar Grundlagen“!
Entsprechend soll hier auf die Details bzgl. CA und Zertifikate nicht noch einmal eingegangen werden.
Das Backend vorbereiten
Wir betrachten im Folgenden die Integration in nginx – einfach, weil der bei uns zum Einsatz kommt. Geht aber auch alles mit dem Apache.
Prinzipiell braucht es dafür auch gar nicht mal so viel. Das kritische ist nur, dass es diverse Einschränkungen gibt, denen man sich bewusst sein muss.
Einschränkungen:
Die Client-Certificates sind immer auf server
-Ebene gültig, nicht auf location
-Ebene. Das heißt, es lässt sich nur für die komplette (sub)-Domain aktivieren, oder nicht – nur einzelne Pfade damit auszustatten geht nicht!
Da aber API-Calls generell über eine andere Subdomain gehen sollten, als reguläre User-Requests, sollte das kein all zu großes Problem darstellen!
Ärgerlicher ist da schon das folgende:
Das ganze lässt sich nicht (bzw. nicht sinnvoll) mit anderen Verifikationsmechanismen kombinieren! Natürlich ist es möglich, zusätzlich zu den Client-Certificates auch noch eine IP-Whitelist zu schalten (was aber total unsinnig ist). Aber es ist nicht möglich den nginx dazu zu bringen „wenn kein valides Client-Cert vorliegt, mache einen Fallback auf die IP-Whitelist“ – zumindest nicht mit vertretbarem Aufwand.
Nachdem wir das nun wissen, können wir uns dran machen, den ausgewählten server
-Eintrag zu ergänzen:
ssl_client_certificate ssl/ca-chain.cert.pem; ssl_crl ../clientside.crl; ssl_verify_client on; ssl_verify_depth 2;
Mehr ist an der Stelle tatsächlich nicht nötig.
Trotzdem noch ein paar Hinweise, die euch einigen Ärger ersparen sollten:
Wie schon zu sehen, muss das angegebene Client-Cert eine Chain aus Root-Zertifikat und Intermediate-Zertifikat sein! Die ssl_verify_depth
muss dementsprechend auf „2“ hoch gesetzt werden.
Wenn ihr nur das Intermediate-Zertifikat angebt, oder trotz Chain die ssl_verify_depth
nicht entsprechend hoch setzt, dann werdet ihr, statt einem funktionierenden Setup, nur Fehler bekommen, dass der Client nicht verifiziert werden konnte.
Desweiteren fällt auf, dass die „Certificate Revocation List“ (ssl_crl
) als Datei referenziert werden muss. Das ist praktisch und ärgerlich zugleich!
Ärgerlich, weil wir das ja dynamisch von der URL abrufen wollen, auf der wir es bereit stellen (weil es ja auch nur eine recht kurze Gültigkeitsdauer hat). Aber auch praktisch – weil wenn die URL mal nicht erreichbar ist, dann führt das nicht gleich zu einem Ausfall unserer API oder Fehlern im nginx.
Es bedeutet aber auch, dass wir noch einen Cronjob einrichten müssen, der regelmäßig die aktuelle CRL zieht, und an der referenzierten Stelle ablegt …und anschließend einen reload
auf den nginx macht, damit dieser auch mitbekommt, dass die Datei sich geändert hat!
ssl_verify_client
kann neben „on“ und „off“ auch noch „optional“ annehmen. Das ist dann sinnvoll, wenn man den Client nicht hart abweisen möchte, sondern später eine Fallunterscheidung haben will. Mehr Details stehen in der nginx Dokumentation
Und ein Punkt ist auch noch wichtig zu erwähnen: das ganze funktioniert nur mit SSL! Ihr solltet eure (sub)-Domain also schon mit einem regulären SSL-Zertifikat für https ausgestattet haben.
Ich hatte erst überlegt, dass unter den Punkt „Einschränkungen“ zu packen. Aber wir haben Jahr 7 AS (After Snowden)! Wenn deine API ohne SSL im Netz steht, dann solltest du über einen Berufswechsel nachdenken! Und keine Ausreden: dank Letsencrypt kann jeder ein kostenloses Zertifikat bekommen!
Zugriff mit dem Client
Wie können wir denn jetzt mit dem Client darauf zugreifen?
Nun, dass kommt vor allem darauf an, was wir dafür benutzen. In jedem Fall gehe ich an diesem Punkt aber davon aus, dass bereits ein entsprechendes Client-Zertifikat vorliegt!
Wenn nicht, empfehle ich noch einmal den oben verlinkten Artikel bezüglich der Grundlagen von Zertifikaten.
Wie sich das ganze eleganter Verwalten lässt und es den Benutzern so einfach wie möglich macht, an ein Zertifikat zu gelangen, dazu kommen wir in einem späteren Artikel noch zu sprechen!
Mit curl
bietet es sich an, einfach folgendes zu tun:
curl "https://mein.zugriffs.punkt" --cert "mein.zertifikat.crt.pem" --key "mein.privater.key.pem"
Versuche, es mit einem PKCS#12 Zertifikat zu Wege zu bringen, waren – zumindest bei mir – regelmäßig zum Scheitern verurteilt!
Aber wer es versuchen möchte:
curl "https://mein.zugriffs.punkt" --cert "mein.zertifikat.pfx" --cert-type P12
Es wird sich aber dennoch lohnen, ein entsprechendes PKCS#12 Zertifikat aus dem vorhandenen „X.509“-Zertifikat zu erstellen. Und zwar aus dem einfachen Grund, weil der Webbrowser das so möchte.
Zumindest weigert sich Chromium beharrlich, ein „X.509“-Zertifikat zu importieren, mit dem Hinweis, dass der zugehörige private Schlüssel fehle.
Zum Erstellen eines PKCS#12-Zertifikat wird, neben dem (Intermediate)CA-Zertifikat, openssl
benötigt.
Ist beides vorhanden, kann mit folgendem Befehl ein entsprechendes Zertifikat erstellt werden:
openssl pkcs12 -export -out user-cert.pfx -inkey /path/to/user.key.pem -in /path/to/user.crt.pem -certfile /path/to/ca.crt.pem
Ich muss wohl nicht extra betonen, dass die Pfade natürlich entsprechend angepasst werden müssen.
Im Browser gibt es dann eine Einstellung zum „Zertifikate verwalten“ – im Chromium findet man die am einfachsten über die Suche:
Dort kann dann das pfx-File einfach importiert werden.
Fazit
Die Hürden für Client-Zertifikate sind natürlich höher, als einfach ein paar IPs in eine Liste einzutragen und den Webserver durch zu starten.
Aber bei genauer Betrachtung, ist es nicht das Zertifikats-Thema, dass einem hier Steine in den Weg legt. Sondern der immer beliebter werdende „MVP-Gedanke/-Anspruch“ führt bei allen Beteiligten zu einer Geisteshaltung, die förmlich dazu auffordert in einem Desaster zu enden.
Hauptsache schnell an den Start kommen – Sicherheit ist dabei nur im Weg! Es wird dann vorher behauptet, dass das nach dem Start noch nachgebessert wird, aber wenn es soweit ist, bleibt dafür keine Zeit, weil schon das nächste MVP an den Start gebracht werden muss.
Client-Zertifikate zwingen dazu, sich zu überlegen, wer eigentlich alles Zugriff erhalten soll. Da die Zertifikate auch ein Ablaufdatum haben, zwingen sie dazu, sich von Zeit zu Zeit Gedanken zu machen, ob all die Clients auch weiterhin Zugriff benötigen. Zum Beispiel wenn eine Person das Unternehmen verlässt.
Die wenigsten Unternehmen haben Prozesse – geschweige denn automatisierte Prozesse – um systematisch alle Berechtigungen zu entfernen. Im Falle von Zertifikaten erledigt sich das Problem zumindest nach einiger Zeit von selbst.
Das setzt aber auch voraus, dass man den Leuten keine Wahl lässt! Denn sonst wird die Antwort immer sein: „Ne, dafür haben wir keine Zeit. Trag‘ mal die folgenden 100 IP-Ranges in die Whitelist ein, damit wir hier voran kommen!“
Dann bleibt nur noch das told-you-so-Karma, wenn das Desaster eingetreten ist 🙁
Kommentare von Martin Drößler