In unserem vorherigen Artikel haben wir eine Problemstellung der Entwicklung eines Off-Site-Caches für TLS erläutert. Zum Downloaden von Firmware-Updates für IoT-Devices soll ein Cache bereitgestellt werden, der die TLS-Verbindung zum Content-Delivery-Network unterbricht, um die Inhalte zwischenzuspeichern und so die Last auf die Internetverbindung in der Off-Site-Location zu reduzieren. Dazu benötigt der Cache ein Wildcard-Zertifikat, dem die IoT-Devices vertrauen, das besonders schützenswert ist. Aus diesem Grund soll der Schlüssel für das Zertifikat auf einem Hardware-Security-Modul gespeichert werden. Die genaue Problemstellung und der Lösungsansatz wurden detailliert im letzten Artikel besprochen. In diesem Artikel soll nun die genaue technische Umsetzung erläutert werden.
Als Grundlage für das gesamte Setup dient ein VM-Ware-Image mit centOS 7. Für das Setup kann man dieses Tutorial verwenden.
Als normalen User für das Caching-System wird der User cache eingerichtet, das System heißt hier einfach host. Alle normalen Befehle werden also mit dem Prefix cache@host $ ausgeführt. Befehle, die als Administrator ausgeführt werden müssen, haben das Prefix root@host $. Der User cache ist in der Sudoers-Group, also kann er mit sudo -sroot werden.
Nach der Installation werden die Paketquellen und das System geupdated und schonmal einige für das Setup benötigte Pakete installiert:
Das erste Problem, das zu lösen ist, ist dass das Caching für die IoT-Devices vollständig transparent erfolgen soll. Hierfür soll die Domain des CDNs über DNS im Netzwerk der Off-Site-Location auf den Cache umgeleitet werden. Um dies zu gewährleisten, soll im Netzwerk der Off-Site-Location der DHCP-Server als Primary-DNS einen DNS-Server liefern, der von uns kontrolliert wird und auf unserer centOS-VM läuft, um die gewünschte Weiterleitung einzurichten. Hierzu verwenden wir dnsmasq. Außerdem installieren wir noch die bind-utils um dig zum Testen des DNS-Setups verwenden zu können.
Als nächstes müssen wir dnsmasq konfigurieren. Hierfür müssen wir die Datei /etc/dnsmasq.conf editieren.
Hier müssen wir die Zeile
einkommentieren (die Raute am Anfang der Zeile entfernen), um eine eigene Konfigurationsdatei in /etc/dnsmasq.d/ hinterlegen zu können, die von dnsmasq auch herangezogen wird.
Als nächstes fügen wir in /etc/dnsmasq.d eine Basiskonfiguration ein.
Neben Standardeinstellungen werden mit server die DNS-Server angegeben, die dnsmasq verwenden soll, wenn die angefragte Domain nicht umgeleitet werden soll. Bei den beiden IP-Adressen handelt es sich um die Adressen von quad9.
Als nächstes muss die eigentliche Weiterleitung konfiguriert werden. Hierfür schreiben wir in die Datei /etc/dnsmasq.d/1.overwriting.conf. Der Eintrag für die Umleitung sieht wie folgt aus: address=/<Hier die Domain, die umgeleitet werden soll>/<hier die IP-Adresse des Caches> In unserem Fall soll der Cache auf der selben centOS-VM laufen, wie dnsmasq, daher ist die IP-Adresse die Adresse der centOS-VM. Mit folgendem Befehl kann die Datei und der Eintrag erstellt werden:
Mit dig können wir testen, ob die Weiterleitung funktioniert:
@localhost sorgt hier dafür, dass dig den lokalen DNS-Server, also unser dnsmasq verwendet. Wollen wir aber, dass in der centOS-VM grundsätzlich dnsmasq als DNS-Server verwendet wird, können wir das in der /etc/resolv.conf eintragen.
Der Eintrag nameserver 127.0.0.1 sorgt dafür, dass in der gesamten centOS-VM nun grundsätzlich erstmal unser mit der Weiterleitung konfiguriertes dnsmasq als DNS-Server verwendet wird. Als nächstes muss jedoch auch dafür gesorgt werden, dass dnsmasq als DNS-Server auch für die IoT-Devices von außen erreicht werden kann. Dafür müssen wir die Firewall-Einstellungen der centOS-VM anpassen. Hierfür editieren wir die Datei /etc/firewalld/zones/public.xml.
Sie sollte folgenden Inhalt haben:
Für dnsmasq brauchen wir den Eintrag <service name="dns">. Die anderen Einträge sind für ssh, um auf die centOS-VM zu kommen, den nginx, der als Cache dient und DHCP.
Danach müssen wir die Firewall neustarten:
Nun setzen wir dnsmasq noch auf Autostart:
Jetzt können wir unseren Redirect nochmal mit dig testen, diesmal ohne @localhost, da ja der DNS-Server über die /etc/resolv.conf bereits auf localhost zeigt:
Wenn alles geklappt hat, steht an der Stelle <Hier steht jetzt hoffentlich die IP der centOS-VM> die IP der centOS-VM. Damit ist der DNS-Redirect fertig.
Da die Grundinstallation nun abgeschlossen ist, kümmern wir uns nun um die Anbindung des Hardware Security Moduls.
Als erstes müssen wir das USB Modul der VM verfügbar machen. Das genaue Vorgehen lässt sich aus der entsprechenden ESXI Dokumentation entnehmen. Ein einfacher Test erfolgt mit dem lsusb tool:
Es sollte hier ein Device von Yubico.com aufgelistet werden.
Damit eine Software mit dem HSM kommunizieren kann, benötigen wir ebenfalls das hsm2-sdk:
Wir installieren die entsprechenden Utilities und referenzieren die benötigten openssl engines.
Als weiteres nützliches Tool hat sich pkcs11-tool erwiesen, welches wir nun ebenfalls installieren:
Die Kommunikation mit dem USB Modul selbst erfolgt über den yubihsm-connector, dieser lässt sich als daemon betreiben, welchen wir nun als root user starten:
Da der Connector ein REST-basiertes Interface verwendet, können wir die ordnungsgemäße Funktion sehr einfach mit curl überprüfen:
Damit ein Client weiß, wie der Connector konfiguriert ist, wird eine Konfigurationsdatei benötigt. Diese liegt immer im Homedirectory des aktiven Benutzers. Für den User cache können wir diese wie folgt erstellen:
Nun können wir ebenfalls die Anbindung per openssl-engine testen, indem wir das Tool pkcs11-tool verwenden:
Um mehr Informationen über das Modul zu erhalten, benötigen wir einen PIN, welcher als Default auf 0001password gesetzt ist:
Die obige Ausgabe zeigt, dass bislang keine Keys auf dem Modul abgelegt worden sind. Sollten schon Keys auf dem Modul vorhanden sein, so ändert sich die Ausgabe z.B. zu:
Eine Administration des HSMs erfolgt über das Utility yubihsm-shell, welches eine interaktive Verbindung zu dem Modul aufbaut. Über dieses Tool lassen sich z.B. vorhandene Keys wieder löschen:
Die 1 ist hier die Session ID, 0x5787 ist die ObjectID vom Key (siehe vorherige Ausgabe) und asymmetric-key der object-type.
Weitere Beispiele finden sich in der Dokumentation von yubico, ebenfalls dort findet sich auch eine Übersicht über alle Objects.
Bevor wir nun mit der Einrichtung fortfahren, sollten wir den PIN ändern:
Damit openssl ebenfalls das HSM verwenden kann, muss eine openssl engine konfiguriert werden.
Bevor wir fortfahren, erstellen wir ein Backup der Originalkonfiguration:
Die neue openssl.cnf enthält nun die Konfiguration für die pkcs11 engine:
Ob die Konfiguration korrekt ist, lässt sich mit openssl testen:
Wir sollten nun ebenfalls in der Lage sein, einen Schlüssel auf dem Modul zu erstellen:
Mit openssl lässt sich nun auch ein certificate signing request (CSR) für den neuen Schlüssel erstellen:
Im produktiven Einsatz ist es natürlich nicht zu empfehlen, dass der connector zum einen mit root Rechten läuft, zum anderen manuell gestartet werden muss.
Deshalb bleibt als letzter Schritt das Erstellen einer Service Konfiguration, damit der connector automatisch nach dem Hochfahren der VM gestartet wird und zudem nur mit den Rechten eines normalen Benutzers läuft.
Als erstes legen wir hierfür einen neuen Benutzer an:
Damit dieser Benutzer auf das USB Device zugreifen kann, muss per udev Regel der Zugriff umgestellt werden. Eine udev Regel ist eine Art Script, welches auf bestimmte Events eines Devices (hier add und change) reagiert. Die Rule findet sich ebenfalls im Repository vom yubihsm-connector.
Damit die neue udev Regel aktiviert wird, muss das System einmal neu gestartet werden.
Danach erstellen wir eine Konfigurationsdatei /etc/yubihsm-connector.yaml:
Diese hat aktuell keine aktiven Einträge, muss aber vorhanden sein.
Weiterhin muss diese Konfigurationsdatei auch vom yubihsm-connector Benutzer gelesen werden können:
Der letzte Schritt ist das Anlegen eines systemd Services.
Die Konfigurationsdatei /etc/systemd/system/yubihsm-connector.service hat den Inhalt:
Nun aktualisieren wir die Daemons Konfiguration und starten des Service:
Damit der Service jedes Mal beim Systemstart ausgeführt wird, aktivieren wir den Autostart:
Nachdem nun das HSM angebunden ist, können wir den Cache aufsetzen.
Dafür müssen wir als erstes nginx installieren, den wir als Cache verwenden wollen.
Nachdem wir nginx installiert haben, müssen wir die Konfiguration so anpassen, dass sie unseren Anforderungen entspricht:
Die Datei sollte wie folgt aussehen:
Anschließend müssen zwei Verzeichnisse angelegt werden und dem nginx die Berechtigungen für diese Verzeichnisse erteilt werden:
Als nächstes erzeugen wir einen CSR mit dem HSM:
Dann signieren wir den CSR einfach selbst:
Das selbstsignierte Zertifikat kopieren wir nun in das dafür angelegte Verzeichnis:
Hätten wir kein selbstsigniertes Zertifikat, sondern ein von anderer Stelle signiertes, so bräuchten wir jetzt die Signaturkette. Diese muss in /etc/nginx/certs/serverchain.pem abgelegt werden. Da unser Zertifikat aber selbstsigniert ist, kopieren wir einfach unser Zertifikat stattdessen nach /etc/nginx/certs/serverchain.pem:
Nun gilt es, die URL für den engine-key vom HSM zu ermitteln:
Jetzt kennen wir die URL "pkcs11:model=YubiHSM;manufacturer=Yubico%20%28www.yubico.com%29;serial=07550837;token=YubiHSM;id=%c1%68;object=nginx-private-key;type=private". Diese können wir nun nehmen und damit unsere Konfiguration unseres Caches anpassen:
Diese sollte wie folgt aussehen:
Bei der Konfiguration ist wichtig, dass bei proxy-pass eine andere Domain angegeben wird, unter der die gleichen Inhalte verfügbar sind, wie unter der umgeleiteten Domain. Dies ist notwendig, damit der Cache die eigentlichen Inhalte herunterladen kann, ohne durch die Umleitung der Domain immer im Kreis zu laufen. In unserem Fall hatte unser Content-Delivery-Network ohnehin eine interne Domain <eigener teil>.cloudfront.net vergeben und wir hatten zusätzlich eine eigene Domain eingerichtet, die wir für die Umleitung verwenden. Somit konnten wir hier einfach die interne Domain des CDNs verwenden.
Als nächstes braucht nginx noch eine Konfiguration, um mit dem yubihsm-connector zusammenzuarbeiten:
Danach haben wir den nginx noch als Service konfiguriert:
Die Datei sollte so aussehen:
Anschließend laden wir die Konfiguration aller Dienste einmal neu:
Und sorgen dafür, dass nginx automatisch mit dem System startet:
Nun starten wir nginx nochmal neu:
Beim ersten Start haben wir dabei noch ein Problem mit SE-Linux. Um das zu ändern, schauen wir als erstes den Fehler im Log an:
Es gibt ein Tool audit2allow, dass aus diesen Fehlern automatisch eine Regel erzeugt:
Damit erzeugen wir nun eine solche Regel:
Das Ergebnis sollte in etwa so aussehen:
Dieses Modul muss nun wie folgt kompiliert werden:
Und zum Schluss muss das Modul noch aktiviert werden (was etwas dauern kann):
Noch einmal `nginx` neustarten, diesmal sollte alles klappen.
Um alles zu testen, brauchen wir eine Datei mit dem Zertifikat und seiner Signaturkette. Wenn wir weiter mit dem selbstsignierten Zertifikat arbeiten, reicht es, wenn wir nur das Zertifikat in diese Datei kopieren, ansonsten braucht es auch die Signaturkette. Dann können wir mit curl den gesamten Aufbau testen:
Der HTTP-Statuscode sollte 200 sein und die gecachte Datei sollte in testimage.bin liegen. Wenn testimage.bin löscht und den Befehl nochmals ausführt, sollte der Download diesmal aus dem Cache erfolgen.
Während der Testphase sind uns ein paar Probleme im Zusammenspiel mit dem YubiKey HSM und der VM begegnet.
Die Probleme und die verwendeten Lösungen wollen wir hier noch einmal abschließend schildern.
Hierzu haben wir die GRUB-Config in /etc/default/grub entsprechend angepasst:
Nachdem diese mit sudo grub2-mkconfig -o /boot/grub2/grub.cfg aktiviert worden ist, haben wir die VM neu gestartet.
Danach war das Verhalten etwas besser, aber wirklich verlässlich gelöst konnte das Problem dadurch nicht werden.
Wir haben dann ein kleines watchdog script erstellt, welches periodisch den Status überprüft und bei einem status != OK den USB Driver einmal neu lädt.
Das Script sieht so aus:
Wir lassen nun das Script alle 5 Minuten per Cron ausführen:
Wie oft das Script nun auf ein Problem gestoßen ist, lässt sich durch einen Blick in das Log /var/log/watchdog.log überprüfen.
Durch diesen Ansatz konnte der Fehler verlässlich behoben werden.
Das Problem selbst haben wir auch bereits bei Yubico gemeldet. Es steht noch aus, ob es ein Bug an der verwendeten libusb Bibliothek ist oder tatsächlich am VMWare USB Stack liegt.
Da unser Workaround bislang ohne Probleme funktioniert, arbeiten wir erst einmal damit.
Manchmal scheint die OpenSSL Engine nicht wie erwartet zu funktionieren. Dies führt dann sowohl beim Aufruf von openssl als auch vom pkcs11-tool zu den folgenden Fehlern:
Das Problem ist, dass die Engine die benötigte yubihsm Konfiguration nicht finden kann, wenn der Aufruf nicht im gleichen Verzeichnis ausgeführt wird, in dem sich auch die Konfigurationsdatei befindet.
Die Lösung ist das Setzen einer entsprechenden Umgebungsvariable:
Für den hier dokumentierten Use-Case haben die Autoren keine gute Dokumentation finden können.
Deshalb erfolgte hier eine Zusammenfassung.
Sind die Anfangsprobleme erst einmal umschifft, ist ein stabiler Betrieb aber möglich.
Insbesondere das HSM von Yubikey bietet eine kostengünstige und variable Möglichkeit, die sich auch für dezentrale Anwendungsfälle verwenden lässt.