Avatar of Felix Schumacher
Avatar of Philipp Haußleiter

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.

Einrichten des Grundsystems

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 [email protected] $ ausgef√ľhrt. Befehle, die als Administrator ausgef√ľhrt werden m√ľssen, haben das Prefix [email protected] $. Der User cache ist in der Sudoers-Group, also kann er mit sudo -s root werden. Nach der Installation werden die Paketquellen und das System geupdated und schonmal einige f√ľr das Setup ben√∂tigte Pakete installiert:

[email protected]$ sudo yum install epel-release -y
[email protected]$ sudo yum update -y
[email protected]$ sudo yum install nano usbutils openssl-pkcs11 gnutls-utils policycoreutils-python -y

Hiernach empfiehlt sich ein Neustart der VM.

Umleitung der Geräte im Netzwerk der Edge-Location

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.

[email protected]$ sudo yum install dnsmasq bind-utils -y

Als n√§chstes m√ľssen wir dnsmasq konfigurieren. Hierf√ľr m√ľssen wir die Datei /etc/dnsmasq.conf editieren.

[email protected]$ sudo vi /etc/dnsmasq.conf

Hier m√ľssen wir die Zeile

#conf-dir=/etc/dnsmasq.d/,*.conf

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.

domain-needed
bogus-priv
no-hosts
keep-in-foreground
no-resolv
expand-hosts
#quad9 dns servers
server=9.9.9.9 
server=149.112.112.112

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:

[email protected]$ sudo sh -c 'echo "address=/ <Hier die Domain, die umgeleitet werden soll> /$(hostname -I)" > /etc/dnsmasq.d/1.overwriting.conf'

Mit dig können wir testen, ob die Weiterleitung funktioniert:

[email protected]$ dig @localhost <Hier die Domain, die umgeleitet werden soll>

@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.

[email protected]$ sudo cat /etc/resolv.conf
 [sudo] password for cache:
 # Generated by NetworkManager
 # nameserver 192.168.2.1
 nameserver 127.0.0.1

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.

[email protected]$ sudo vim /etc/firewalld/zones/public.xml

Sie sollte folgenden Inhalt haben:

<?xml version="1.0" encoding="utf-8"?>
 <zone>
 	<short>Public</short>
 	<description>For use in public areas. You do not trust the other computers on networks to not harm your computer. Only selected incoming connections are accepted.</description>
 	<service name="dns"/>
 	<service name="ssh"/>
 	<service name="https"/>
 	<service name="dhcpv6-client"/>
 </zone>

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:

[email protected]$ sudo service firewalld restart

Nun setzen wir dnsmasq noch auf Autostart:

[email protected]$ sudo chkconfig dnsmasq on

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:

[email protected]$ dig <Hier die Domain, die umgeleitet werden soll>

 ; <<>> DiG 9.9.4-RedHat-9.9.4-73.el7_6 <<>> @localhost 
 ; (2 servers found)
 ;; global options: +cmd
 ;; Got answer:
 ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 63252
 ;; flags: qr aa rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

 ;; QUESTION SECTION:
 ;<Hier die Domain, die umgeleitet werden soll>. IN A

 ;; ANSWER SECTION:
 <Hier die Domain, die umgeleitet werden soll>. 0	IN A <Hier steht jetzt hoffentlich die IP der centOS-VM>

 ;; Query time: 0 msec
 ;; SERVER: ::1#53(::1)
 ;; WHEN: Mo Mai 13 12:05:45 CEST 2019
 ;; MSG SIZE  rcvd: 94

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.

Anbinden des HSMs

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:

[email protected]$ lsusb
 Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
 Bus 002 Device 004: ID 1050:0030 Yubico.com
 Bus 002 Device 003: ID 0e0f:0002 VMware, Inc. Virtual USB Hub
 Bus 002 Device 002: ID 0e0f:0003 VMware, Inc. Virtual Mouse
 Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub

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:

[email protected]$ curl wget https://developers.yubico.com/YubiHSM2/Releases/yubihsm2-sdk-2019-12-centos7-amd64.tar.gz --output /tmp/yubihsm2-sdk-2019-12-centos7-amd64.tar.gz
[email protected]$ cd /tmp/
[email protected]$ tar xfz yubihsm2-sdk-2019-03-centos7-amd64.tar.gz
[email protected]$ chmod +x bin/*

Wir installieren die entsprechenden Utilities und referenzieren die benötigten openssl engines.

[email protected]$ sudo cp -R bin/* /usr/bin/
[email protected]$ sudo cp -Rf lib/* /usr/lib64/
[email protected]$ sudo ln -s /usr/lib64/engines-1.1/pkcs11.so /usr/lib64/openssl/engines/libpkcs11.so

Als weiteres n√ľtzliches Tool hat sich pkcs11-tool erwiesen, welches wir nun ebenfalls installieren:

[email protected]$ sudo yum install opensc -y

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:

[email protected]$ sudo -s
[email protected]$ yubihsm-connector -d

Da der Connector ein REST-basiertes Interface verwendet, k√∂nnen wir die ordnungsgem√§√üe Funktion sehr einfach mit curl √ľberpr√ľfen:

[email protected]$ curl localhost:12345/connector/status
 status=OK
 serial=*
 version=2.2.0
 pid=23705
 address=localhost
 port=12345

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:

[email protected]$ echo 'connector = http://127.0.0.1:12345' > /home/cache/yubihsm_pkcs11.conf

Nun können wir ebenfalls die Anbindung per openssl-engine testen, indem wir das Tool pkcs11-tool verwenden:

[email protected]$ pkcs11-tool --module /usr/lib64/pkcs11/yubihsm_pkcs11.so  -L -O
 Available slots:
 Slot 0 (0x0): YubiHSM Connector localhost
   (empty)
 No slot with a token was found.

Um mehr Informationen √ľber das Modul zu erhalten, ben√∂tigen wir einen PIN, welcher als Default auf 0001password gesetzt ist:

[email protected]$ pkcs11-tool --module /usr/lib64/pkcs11/yubihsm_pkcs11.so --pin 0001password  -L -O
 Available slots:
 Slot 0 (0x0): YubiHSM Connector localhost
   token label        : YubiHSM
   token manufacturer : Yubico (www.yubico.com)
   token model        : YubiHSM
   token flags        : login required, rng, token initialized, PIN initialized
   hardware version   : 2.101
   firmware version   : 2.101
   serial num         : 07550837
 No slot with a token was found.

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:

[email protected]$ pkcs11-tool --module /usr/lib64/pkcs11/yubihsm_pkcs11.so --pin 0001password  -L -O
 Available slots:
 Slot 0 (0x0): YubiHSM Connector localhost
   token label        : YubiHSM
   token manufacturer : Yubico (www.yubico.com)
   token model        : YubiHSM
   token flags        : login required, rng, token initialized, PIN initialized
   hardware version   : 2.101
   firmware version   : 2.101
   serial num         : 07550837
 Using slot 0 with a present token (0x0)
 Private Key Object; RSA
   label:      nginxkey
   ID:         5787
   Usage:      decrypt, sign
 Public Key Object; RSA 2048 bits
   label:      nginxkey
   ID:         5787
   Usage:      encrypt, verify
 Private Key Object; RSA
   label:      nginxkey2
   ID:         b670
   Usage:      sign
 Public Key Object; RSA 2048 bits
   label:      nginxkey2
   ID:         b670
   Usage:      verify

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:

[email protected]$ yubihsm-shell
 Using default connector URL: http://127.0.0.1:12345
yubihsm> connect
 Session keepalive set up to run every 15 seconds
yubihsm> session open 1 password
 Created session 1
yubihsm> delete 1 0x5787 asymmetric-key

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:

yubihsm> change authkey 0 1 <your-password>
 Changed Authentication key 0x0001

Einrichten der OpenSSL Engine

Damit openssl ebenfalls das HSM verwenden kann, muss eine openssl engine konfiguriert werden. Bevor wir fortfahren, erstellen wir ein Backup der Originalkonfiguration:

[email protected]$ sudo mv /etc/pki/tls/openssl.cnf /etc/pki/tls/openssl.cnf.bak

Die neue openssl.cnf enth√§lt nun die Konfiguration f√ľr die pkcs11 engine:

openssl_conf = openssl_init

[openssl_init]
engines = engine_section

[engine_section]
pkcs11 = pkcs11_section

[pkcs11_section]
engine_id = pkcs11
dynamic_path = /usr/lib64/engines-1.1/pkcs11.so
MODULE_PATH = /usr/lib64/pkcs11/yubihsm_pkcs11.so
PIN = "0001<b style="color:red">&lt;your-passwordl&gt;</b>"
init = 0

[req]
distinguished_name = req_dn
string_mask = utf8only
utf8 = yes

[req_dn]
commonName = ssc

Ob die Konfiguration korrekt ist, lässt sich mit openssl testen:

[email protected]$ openssl engine -t -c pkcs11
 (pkcs11) pkcs11 engine
 [RSA, rsaEncryption]
     [ available ]

Wir sollten nun ebenfalls in der Lage sein, einen Schl√ľssel auf dem Modul zu erstellen:

[email protected]$ pkcs11-tool --module /usr/lib64/pkcs11/yubihsm_pkcs11.so \
    --login \
    --pin 0001<your password> \
    --keypairgen \
    --label "nginx-private-key" \
    --key-type rsa:2048 \
    --usage-sign
 Using slot 0 with a present token (0x0)
 Key pair generated:
 Private Key Object; RSA
 label:      nginx-private-key
 ID:         c168
 Usage:      sign
 Public Key Object; RSA 2048 bits
 label:      nginx-private-key
 ID:         c168
 Usage:      verify

Mit openssl l√§sst sich nun auch ein certificate signing request (CSR) f√ľr den neuen Schl√ľssel erstellen:

[email protected]$ openssl req -new \
    -subj '/CN=nginx-private-key/' \
    -sha256 \
    -engine pkcs11 -keyform engine -key 0:c168 -out /tmp/csr.pem
  engine "pkcs11" set.

[email protected]$ cat /tmp/selfsigned.pem
 -----BEGIN CERTIFICATE-----
 MIICtDCCAZwCCQDBL+/A9ykCbjANBgkqhkiG9w0BAQsFADAcMRowGAYDVQQDDBFu
 …
 Q1UTUBPIdzGeO39ycfopeWIdr4xXQ6a1Llq4zctAbnvlVgI2bdkrkoL6NSXksnTZ
 5kQx2XBcZFL1m2bwmNhNe6jqEVqqWeGj
 -----END CERTIFICATE-----

Den yubihsm-connector als Service einrichten

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:

[email protected]$ sudo useradd yubihsm-connector -m

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.

[email protected]$ sudo cat /etc/udev/rules.d/70-yubihsm-connector.rules

 ACTION!="add|change", GOTO="yubihsm_connector_end"

 #Yubico YubiHSM2
 SUBSYSTEM=="usb", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0030", OWNER="yubihsm-connector"

 LABEL="yubihsm_connector_end"

Damit die neue udev Regel aktiviert wird, muss das System einmal neu gestartet werden.

Danach erstellen wir eine Konfigurationsdatei /etc/yubihsm-connector.yaml:

# Certificate (X.509)
#cert: ""
#
# Certificate key
#key: ""
#
# Listening address. Defaults to "127.0.0.1:12345".
#listen: "127.0.0.1:12345"
#
# Device serial in case of multiple devices
#serial: ""
#
# Log to syslog/eventlog. Defaults to "false".
#syslog: "false"

Diese hat aktuell keine aktiven Einträge, muss aber vorhanden sein. Weiterhin muss diese Konfigurationsdatei auch vom yubihsm-connector Benutzer gelesen werden können:

[email protected]$ sudo chown -R yubihsm-connector:yubihsm-connector /etc/yubihsm-connector.yaml

Der letzte Schritt ist das Anlegen eines systemd Services. Die Konfigurationsdatei /etc/systemd/system/yubihsm-connector.service hat den Inhalt:

[Unit]
Description=YubiHSM connector
Documentation=https://developers.yubico.com/YubiHSM2/Component_Reference/yubihsm-connector/
After=network-online.target
Wants=network-online.target systemd-networkd-wait-online.service

[Service]
Restart=on-abnormal

; User and group the process will run as.
User=yubihsm-connector
Group=yubihsm-connector

ExecStart=/bin/yubihsm-connector -c /etc/yubihsm-connector.yaml

; Use private /tmp and /var/tmp, which are discarded after caddy stops.
PrivateTmp=true
; Hide /home, /root, and /run/user. Nobody will steal your SSH-keys.
ProtectHome=true
; Make /usr, /boot, /etc and possibly some more folders read-only.
ProtectSystem=full

[Install]
WantedBy=multi-user.target

Nun aktualisieren wir die Daemons Konfiguration und starten des Service:

[email protected]$ sudo systemctl daemon-reload
[email protected]$ sudo service yubihsm-connector restart

Damit der Service jedes Mal beim Systemstart ausgef√ľhrt wird, aktivieren wir den Autostart:

[email protected]$ sudo chkconfig yubihsm-connector on

 Note: Forwarding request to 'systemctl enable yubihsm-connector.service'.
 Created symlink from /etc/systemd/system/multi-user.target.wants/yubihsm-connector.service to /etc/systemd/system/yubihsm-connector.service.

Damit ist das Setup vom HSM abgeschlossen.

Aufsetzen des Caches

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.

[email protected]$ sudo yum install nginx

Nachdem wir nginx installiert haben, m√ľssen wir die Konfiguration so anpassen, dass sie unseren Anforderungen entspricht:

[email protected]$ sudo vim /etc/nginx/nginx.conf

Die Datei sollte wie folgt aussehen:

ssl_engine pkcs11;
user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    proxy_cache_path /var/lib/nginx/cache levels=1:2  keys_zone=STATIC:10m  inactive=24h  max_size=100g;

    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    &#x0023; tcp_nopush     on;

    keepalive_timeout  65;

    &#x0023; gzip  on;

    include /etc/nginx/conf.d/*.conf;
}

Anschlie√üend m√ľssen zwei Verzeichnisse angelegt werden und dem nginx die Berechtigungen f√ľr diese Verzeichnisse erteilt werden:

[email protected]$ sudo mkdir -p  /etc/nginx/certs /var/lib/nginx/cache
[email protected]$ sudo chown -R nginx:nginx /var/lib/nginx/cache

Als nächstes erzeugen wir einen CSR mit dem HSM:

[email protected]$ openssl req -new -subj '/CN=<hier der Common Name f√ľr das eigene Zertifikat>/' -sha256 -engine pkcs11 -keyform engine -key <hier die eigene Key-ID eintragen, z.B. 0:c168> -out /tmp/csr.pem

Dann signieren wir den CSR einfach selbst:

[email protected]$ openssl x509 -engine pkcs11 -signkey <hier die eigene Key-ID eintragen, z.B. 0:c168> -keyform engine -in /tmp/csr.pem -out /tmp/cert.pem

Das selbstsignierte Zertifikat kopieren wir nun in das daf√ľr angelegte Verzeichnis:

[email protected]$ sudo cp cert.pem /etc/nginx/certs/cert.pem

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:

[email protected]$ sudo cp cert.pem /etc/nginx/certs/serverchain.pem

Nun gilt es, die URL f√ľr den engine-key vom HSM zu ermitteln:

[email protected]$ p11tool --provider /usr/lib64/pkcs11/yubihsm_pkcs11.so --list-privkeys --login
 Token 'YubiHSM' with URL 'pkcs11:model=YubiHSM;manufacturer=Yubico%20%28www.yubico.com%29;serial=07550837;token=YubiHSM' requires user PIN
 Enter PIN:
 Object 0:
     URL: pkcs11:model=YubiHSM;manufacturer=Yubico%20%28www.yubico.com%29;serial=07550837;token=YubiHSM;id=%c1%68;object=nginx-private-key;type=private
     Type: Private key
     Label: nginx-private-key
     Flags: CKA_PRIVATE; CKA_SENSITIVE;
     ID: c1:68

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:

[email protected]$ vim /etc/nginx/conf.d/000-default.conf

Diese sollte wie folgt aussehen:

server {

    listen 443;
    server_name <Hier die Domain, die umgeleitet werden soll>;

    ssl_certificate             /etc/nginx/certs/cert.pem;
    #das hier ist die Engine-URL:
    ssl_certificate_key         "engine:pkcs11:pkcs11:model=YubiHSM;manufacturer=Yubico%20%28www.yubico.com%29;serial=07550837;token=YubiHSM;id=%c1%68;object=nginx-private-key;type=private";
    ssl_stapling                on;
    ssl_stapling_verify         on;
    ssl_trusted_certificate     /etc/nginx/certs/serverchain.pem;

    ssl on;
    ssl_session_cache           builtin:1000  shared:SSL:10m;
    ssl_protocols               SSLv3 TLSv1.1 TLSv1.2;
    ssl_ciphers                 ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384;
    ssl_prefer_server_ciphers   on;
    ssl_session_cache           shared:SSL:10m;
    ssl_verify_client           off; 

    access_log                  /var/log/nginx/cache_access.log;
    error_log                   /var/log/nginx/cache_error.log debug;

    location / {

        proxy_set_header        Host $host;
        proxy_set_header        X-Real-IP $remote_addr;
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header        X-Forwarded-Proto $scheme;

        proxy_ssl_server_name   on;
        proxy_ssl_name          <Hier die Domain, die umgeleitet werden soll>;
        proxy_pass              <Hier eine Domain, die zusätzlich auf das CDN zeigt>;
        proxy_read_timeout      90;       

        proxy_buffering         on;
        proxy_cache             STATIC;
        proxy_cache_valid       200  10d;

    }
}

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:

[email protected]$ sudo echo 'connector = http://127.0.0.1:12345' > /var/lib/nginx/yubihsm_pkcs11.conf
[email protected]$ sudo chown -R nginx:nginx /var/lib/nginx/yubihsm_pkcs11.conf

Danach haben wir den nginx noch als Service konfiguriert:

[email protected]$ sudo nano /usr/lib/systemd/system/nginx.service

Die Datei sollte so aussehen:

[Unit]
Description=The nginx HTTP and reverse proxy server
After=network.target remote-fs.target nss-lookup.target

[Service]
Type=forking
PIDFile=/run/nginx.pid
# nginx will fail to start if /run/nginx.pid already exists but has the wrong
# SELinux context. This might happen when running `nginx -t` from the cmdline.
# https://bugzilla.redhat.com/show_bug.cgi?id=1268621

Environment="YUBIHSM_PKCS11_CONF=/var/lib/nginx/yubihsm_pkcs11.conf"
ExecStartPre=/usr/bin/rm -f /run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t
ExecStart=/usr/sbin/nginx
ExecReload=/bin/kill -s HUP $MAINPID
KillSignal=SIGQUIT
TimeoutStopSec=5
KillMode=process
PrivateTmp=true

[Install]
WantedBy=multi-user.target

Anschließend laden wir die Konfiguration aller Dienste einmal neu:

[email protected]$ sudo systemctl daemon-reload

Und sorgen daf√ľr, dass nginx automatisch mit dem System startet:

[email protected]$ sudo chkconfig nginx on
 Note: Forwarding request to 'systemctl enable nginx.service'.
 Created symlink from /etc/systemd/system/multi-user.target.wants/nginx.service to /usr/lib/systemd/system/nginx.service.

Nun starten wir nginx nochmal neu:

[email protected]$ sudo service nginx restart
 Redirecting to /bin/systemctl restart nginx.service

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:

[email protected]$ sudo grep nginx /var/log/audit/audit.log | grep denied
 type=AVC msg=audit(1556899011.196:782): avc:  denied  { name_connect } for  pid=10095 comm="nginx" dest=12345 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:unreserved_port_t:s0 tclass=tcp_socket permissive=0
 type=AVC msg=audit(1556899552.351:818): avc:  denied  { read } for  pid=10592 comm="nginx" name="cache" dev="dm-0" ino=25749899 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:default_t:s0 tclass=dir permissive=0

Es gibt ein Tool audit2allow, dass aus diesen Fehlern automatisch eine Regel erzeugt:

[email protected]$ sudo yum install audit2allow

Damit erzeugen wir nun eine solche Regel:

[email protected]$ sudo grep nginx /var/log/audit/audit.log | grep denied | audit2allow -m nginx > nginx

Das Ergebnis sollte in etwa so aussehen:

[email protected]$ sudo cat nginx

  module nginx 1.0;

  require {
      type httpd_t;
      type default_t;
      type unreserved_port_t;
      class tcp_socket name_connect;
      class dir read;
  }

  #============= httpd_t ==============

  #!!!! WARNING: 'default_t' is a base type.
  allow httpd_t default_t:dir read;

  #!!!! This avc is allowed in the current policy
  allow httpd_t unreserved_port_t:tcp_socket name_connect;
Dieses Modul muss nun wie folgt kompiliert werden:
[email protected]$ sudo grep nginx /var/log/audit/audit.log | audit2allow -M nginx
Und zum Schluss muss das Modul noch aktiviert werden (was etwas dauern kann):
[email protected]$ sudo semodule -i nginx.pp
Noch einmal `nginx` neustarten, diesmal sollte alles klappen.
[email protected]$ sudo service nginx restart

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:

[email protected]$ curl -vvv --cacert <Hier eine Datei mit dem Zertifikat und, wenn vorhanden der Zertifikatskette angeben>.pem <Hier die Domain, die umgeleitet werden soll><und hier ein Pfad auf eine Datei, die es zu cachen gilt> > testimage.bin

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.

Problembehandlung beim Anbinden des HSMs

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.

Problem: Die Verbindung zum USB Device geht verloren

Manchmal kann es passieren, dass der yubihsm-connector die Verbindung zum HSM Device verliert. Eine Statusabfrage liefert dann die folgende Ausgabe:

[email protected]$ curl localhost:12345/connector/status
 status=NO_DEVICE
 serial=*
 version=2.2.0
 pid=6677
 address=localhost
 port=12345

Im Debug Log lassen sich dann die folgenden Einträge lesen:

DEBU[0000] preflight complete                            cert= config= key= pid=6658 seccomp=false serial= syslog=false version=2.2.0
DEBU[0000] takeoff                                       TLS=false listen="localhost:12345" pid=6658
DEBU[0003] reopening usb context                         Correlation-ID=d3a27afe-67b7-69ad-4b43-852b71978459 why="status request"
DEBU[0003] usb context not yet open                      Correlation-ID=d3a27afe-67b7-69ad-4b43-852b71978459
WARN[0005] status failed to open usb device              X-Request-ID=d3a27afe-67b7-69ad-4b43-852b71978459 error="device not found"
INFO[0005] handled request                               Content-Length=0 Content-Type= Method=GET RemoteAddr="127.0.0.1:45628" StatusCode=200 URI=/connector/status User-Agent=curl/7.29.0 X-Real-IP=127.0.0.1 X-Request-ID=d3a27afe-67b7-69ad-4b43-852b71978459 latency=1.629251502s
DEBU[0007] reopening usb context                         Correlation-ID=acaa699f-bd22-25ae-4cfe-6d1030559047 why="status request"

Per lsusb Befehl ist das Device aber immer noch auffindbar.

Ein erster Versuch hier war, dass wir die USB Autosuspend Funktion des Linux Kernels deaktiviert haben.

Hierzu haben wir die GRUB-Config in /etc/default/grub entsprechend angepasst:

GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="crashkernel=auto rd.lvm.lv=centos/root rd.lvm.lv=centos/swap rhgb quiet usbcore.autosuspend=-1"
GRUB_DISABLE_RECOVERY="true"

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:

#!/bin/bash

STATUS=$(curl -s localhost:12345/connector/status | grep status=)

if [ $STATUS != "status=OK" ]; then
    for i in /sys/bus/pci/drivers/[uoex]hci_hcd/*:*; do
      [ -e "$i" ] || continue
      echo "${i##*/}" > "${i%/*}/unbind"
      echo "${i##*/}" > "${i%/*}/bind"
    done
fi

Wir lassen nun das Script alle 5 Minuten per Cron ausf√ľhren:

[email protected]$ crontab -e

*/5 * * * * /root/watchdog.sh >> /var/log/watchdog.log 2>&1

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.

Problem: Die OpenSSL Engine funktioniert nicht

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:

[email protected]$ openssl x509 -engine pkcs11 -signkey 0:c168 -keyform engine -in /tmp/csr.pem -out /tmp/cert2.pem
 Unable to load module /usr/lib64/pkcs11/yubihsm_pkcs11.so
 can't use that engine
 140625983563664:error:82065006:PKCS#11 module:pkcs11_check_token:Function failed:p11_load.c:92:
 140625983563664:error:260B806D:engine routines:ENGINE_TABLE_REGISTER:init failed:eng_table.c:175:
 Getting Private key
 no engine specified
 unable to load Private key

[email protected]$ pkcs11-tool --module /usr/lib64/pkcs11/yubihsm_pkcs11.so --pin 0001password  -L -O
 error: PKCS11 function C_Initialize failed: rv = CKR_FUNCTION_FAILED (0x6)
 Aborting.

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:

[email protected]$ export YUBIHSM_PKCS11_CONF=/home/cache/yubihsm_pkcs11.conf

Fazit

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.

Foto von Adi Goldstein auf Unsplash.

TAGS