Safer netboot: Difference between revisions
m (→Webserver) |
|||
(43 intermediate revisions by the same user not shown) | |||
Line 8: | Line 8: | ||
Das heißt alleinig dadurch, dass das "Transportprotokoll", mit dem die Daten zum Client übertragen werden, geändert wird, werden beide Probeleme gelöst. Aber diese Änderung hat auch Änderung in der Infrastrutur zur Folge. Diese Änderungen werden nachfolgend beschrieben. |
Das heißt alleinig dadurch, dass das "Transportprotokoll", mit dem die Daten zum Client übertragen werden, geändert wird, werden beide Probeleme gelöst. Aber diese Änderung hat auch Änderung in der Infrastrutur zur Folge. Diese Änderungen werden nachfolgend beschrieben. |
||
== |
== Neues PXE == |
||
Da das gewöhnliche PXE-ROM |
Da das gewöhnliche PXE-ROM standardmäßig kein HTTP unterstützt, wird hier eine Alternative gebraucht. Hier bietet sich iPXE an. Auf der Projektwebseite sagen die Autoren selbst: ”iPXE is the leading open source network boot firmware." iPXE unterstützt eine Vielzahl an Protokollen wie HTTP(S), iSCSI, FCoE, AoE, wireless und mehr. Weiter ist der Bootvorgang scriptable. Das heißt, man kann iPXE schon vorher sagen, welche Files von wo geladen werden sollen. Das entbindet von der Notwendigkeit, diese Addressen über DHCP zu verteilen. Von großem Vorteil ist, dass iPXE-ROMs auch für physische Hardware gebaut werden können, die auch auf die Chips dieser Hardware geflasht werden kann. |
||
Es bietet auch die Möglichkeit des Chainloadings und eines |
Es bietet auch die Möglichkeit des Chainloadings und eines Bootprompts, um den Netboot manuell vor der Maschine zu bedienen. |
||
== |
== Eigene Public Key Infrastruktur == |
||
=== Zertifikate === |
=== Zertifikate === |
||
HTTP soll über TLS abgesichert werden. Dafür sind Zertifikate für die |
HTTP soll über TLS abgesichert werden. Dafür sind Zertifikate für die Server- und die Clientseite zur nötig, um eine gegenseitige Authentifizierung zu erreichen. iPXE unterstützt exklusiv [[https://ipxe.org/crypto RSA-Zertifikate]]. Für unser Vorhaben erstellen wir mit OpenSSL eine eigene (Root)CA, die sowohl das Zertifikat des HTTPS-Boot-Servers als auch die Clientzertifikate für die Clienten, die von diesem Server Material zum Booten herunterladen sollen, signiert. Wir lassen uns von https://ipxe.org/crypto inspirieren: |
||
<source lang=bash highlight="7,10,13,16,19"> |
|||
### preparation |
|||
echo 01 > ca.srl |
|||
touch ca.idx |
|||
mkdir signed |
|||
### generating the CA: |
|||
RootCA: |
|||
<source lang=bash highlight="1"> |
|||
openssl req -x509 -newkey rsa:2048 -out ca.crt -keyout ca.key -days 1000 |
openssl req -x509 -newkey rsa:2048 -out ca.crt -keyout ca.key -days 1000 |
||
### generating request for the server key (without password protection): |
|||
openssl req -config server.conf -nodes -newkey rsa:2048 -keyout server.key -out server.req |
|||
### signing server cert |
|||
openssl ca -config ca.conf -extensions v3_ext_server -in server.req -out server.crt |
|||
### generating request for the client key (without password protection): |
|||
openssl req -config client.conf -nodes -newkey rsa:2048 -keyout client.key -out client.req |
|||
### signing client cert |
|||
openssl ca -config ca.conf -extensions v3_ext_client -in client.req -out client.crt |
|||
</source> |
</source> |
||
Dabei finden die folgenden drei OpenSSL-Konfigurationsdateien <code>ca.conf</code>, <code>server.conf</code> und <code>client.conf</code> Verwendung: |
|||
Server: |
|||
<source lang=bash highlight="1,2"> |
|||
openssl req -newkey rsa -keyout server.key -out server.req |
|||
openssl ca -config ca.cnf -in server.req -out server.crt |
|||
</source> |
|||
Client: |
|||
<source lang=bash highlight="1,2"> |
|||
openssl req -newkey rsa -keyout client.key -out client.req |
|||
openssl ca -config ca.cnf -in client.req -out client.crt |
|||
</source> |
|||
#<code>ca.conf</code> enthält spezifische Abschnitte für <code>v3</code>-Erweiterungen. Für den Server wird hier die IP-Adresse mit in das Zertifikat als <code>subjectAltName</code> mit aufgenommen, was wünschenswert ist, das im Bootrom diese hinterlegt ist und die Auflösung eines DNS-Namens im Bootprozess nicht erforderlich ist. Für den Server wird die <code lang=ini>extendedKeyUsage = clientAuth</code> definiert:<source lang="ini"> |
|||
Die <code>ca.cnf</code> trägt diesen Inhalt: |
|||
<source lang="ini"> |
|||
[ ca ] |
[ ca ] |
||
default_ca = ca_default |
default_ca = ca_default |
||
copy_extensions = copy |
|||
[ ca_default ] |
[ ca_default ] |
||
certificate = ca.crt |
certificate = ca.crt |
||
Line 48: | Line 55: | ||
policy = policy_anything |
policy = policy_anything |
||
preserve = yes |
preserve = yes |
||
default_days = |
default_days = 356 |
||
unique_subject = no |
unique_subject = no |
||
Line 59: | Line 66: | ||
commonName = optional |
commonName = optional |
||
emailAddress = optional |
emailAddress = optional |
||
[ |
[ v3_ext_server ] |
||
extendedKeyUsage = serverAuth |
|||
basicConstraints = critical,CA:true |
|||
subjectAltName = @alt_names |
|||
keyUsage = critical,cRLSign,keyCertSign |
|||
[ alt_names ] |
|||
DNS.1 = wolftux |
|||
[ codesigning ] |
|||
DNS.2 = wolftux.sar.informatik.hu-berlin.de |
|||
keyUsage = digitalSignature |
|||
IP.1 = 192.168.5.22 |
|||
extendedKeyUsage = codeSigning |
|||
[ v3_ext_client ] |
|||
extendedKeyUsage = clientAuth |
|||
</source> |
</source> |
||
#<code>server.conf</code> definiert den <code>distinguished_name</code> des Boot-Servers.<source lang="ini"> |
|||
[ req ] |
|||
distinguished_name = req_distinguished_name |
|||
prompt = no |
|||
[ req_distinguished_name ] |
|||
C = DE |
|||
ST = Berlin |
|||
L = Berlin |
|||
O = HUB |
|||
OU = Informatik |
|||
CN = netBootServer |
|||
</source> |
|||
#<code>client.conf</code>definiert den <code>distinguished_name</code> des Boot-Clients. Hier könnte man in den <code>CN</code> die Hardwareadresse der Netzwerkkarte aufnehmen, so dass der Boot-Server abhängig von der mittels des Client-Zertifikats (und des Besitzes des zugehörigen privaten Schlüssels) nachgewiesenen Identität verschiedene Kernel oder Initrds ausliefern kann.<source lang="ini"> |
|||
[ req ] |
|||
distinguished_name = req_distinguished_name |
|||
prompt = no |
|||
[ req_distinguished_name ] |
|||
C = DE |
|||
ST = Berlin |
|||
L = Berlin |
|||
O = HUB |
|||
OU = Informatik |
|||
CN = netBootClient_HWADDR |
|||
</source> |
|||
Die <span id="gen_certs">'''generierten Zertifikate'''</span> [[netboot_ca.crt.txt|<code>ca.crt</code>]], [[netboot_server.crt.txt|<code>server.crt</code>]] und [[netboot_client.crt.txt|<code>client.crt</code>]] können mit <code lang=bash>openssl x509 -in name.crt -noout -text</code> betrachtet werden. |
|||
=== Webserver === |
=== Webserver === |
||
Da der Webserver HTTPS-Anfragen entgegen nehmen soll, bedarf dieser ein kleines an Konfiguration: |
Da der Webserver HTTPS-Anfragen entgegen nehmen soll, bedarf dieser ein kleines an Konfiguration: |
||
<span id="apache.conf"> |
|||
<source lang="apache"> |
<source lang="apache"> |
||
<VirtualHost *:443> |
<VirtualHost *:443> |
||
Line 80: | Line 119: | ||
SSLVerifyDepth 1 # lass Client mit Zertifikaten bis Tiefe 1 unterhalb |
SSLVerifyDepth 1 # lass Client mit Zertifikaten bis Tiefe 1 unterhalb |
||
SSLCACertificateFile /etc/apache2/ca.crt # dieser CA zu |
SSLCACertificateFile /etc/apache2/ca.crt # dieser CA zu |
||
#SSLCARevocationFile /etc/apache2/ca.crl # Prüfung auf Widerruf von Zertifikaten (für produktiven Einsatz) |
|||
</VirtualHost> |
</VirtualHost> |
||
</source> |
</source> |
||
</span> |
|||
Weiter müssen die Files für den nächsten Bootstage des Clients bereit liegen. |
Weiter müssen die Files für den nächsten Bootstage des Clients bereit liegen. |
||
== iPXE-Image == |
|||
Die Images werden aus den Quellen direkt kompiliert. Anpassungen werden über die Kommandozeile beim Kompilieren mitgeteilt. Für HTTPS ist auch eine Anpassung in <code> |
Die Images werden aus den Quellen direkt kompiliert. Anpassungen werden über die Kommandozeile beim Kompilieren mitgeteilt. Für HTTPS ist auch eine Anpassung in <code>src/config/general.h</code> nötig. Wir schalten die TFTP-Unterstützung aus und die HTTPS-Unterstützung ein: |
||
<source lang=diff> |
|||
diff --git a/src/config/general.h b/src/config/general.h |
|||
index 3c14a2cd..a755e7a1 100644 |
|||
--- a/src/config/general.h |
|||
+++ b/src/config/general.h |
|||
@@ -52,9 +52,9 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); |
|||
* |
|||
*/ |
|||
-#define DOWNLOAD_PROTO_TFTP /* Trivial File Transfer Protocol */ |
|||
+#undef DOWNLOAD_PROTO_TFTP /* Trivial File Transfer Protocol */ |
|||
#define DOWNLOAD_PROTO_HTTP /* Hypertext Transfer Protocol */ |
|||
-#undef DOWNLOAD_PROTO_HTTPS /* Secure Hypertext Transfer Protocol */ |
|||
+#define DOWNLOAD_PROTO_HTTPS /* Secure Hypertext Transfer Protocol */ |
|||
#undef DOWNLOAD_PROTO_FTP /* File Transfer Protocol */ |
|||
#undef DOWNLOAD_PROTO_SLAM /* Scalable Local Area Multicast */ |
|||
#undef DOWNLOAD_PROTO_NFS /* Network File System Protocol */ |
|||
</source> |
|||
Die Optionen, die benötigt werden sind, diese: |
|||
* script (”EMBED=”) |
* script (”EMBED=”) |
||
* Zertifikate (”CERT=”) |
* Zertifikate (”CERT=”) |
||
Line 95: | Line 157: | ||
#!ipxe # Shebang |
#!ipxe # Shebang |
||
dhcp # Netzwerkkonfiguration über DHCP bekommen |
dhcp # Netzwerkkonfiguration über DHCP bekommen |
||
kernel https://192.168.5.22/ |
kernel https://192.168.5.22/vmlinuz # lade Kernel über HTTPS von URL |
||
initrd https://192.168.5.22/initrd # lade initrd über HTTPS von URL |
initrd https://192.168.5.22/initrd # lade initrd über HTTPS von URL |
||
boot # Bootvorgang |
boot # Bootvorgang |
||
Line 105: | Line 167: | ||
Der Typ der Karte (VendorID und ProduktID) kann mittels lspci -nn ermittelt werden. |
Der Typ der Karte (VendorID und ProduktID) kann mittels lspci -nn ermittelt werden. |
||
=== ROM flashen === |
|||
Um das Image auf physische Hardware zu bringen, muss der ROM-Speicher der Netzwerkkarte geflasht werden. Das kann mit den openSource Werkzeug flashrom getan werden. Es bietet sich an, dies unter einem Livesystem (wir haben [https://grml.org/ Grml] verwendet) zu tun. Dabei stellte sich heraus, dass Root-Rechte (wie zu erwarten) aber zusätzlich auch beim Booten der Kernelparameter <code> iomem=relaxed </code> erforderlich sind. |
Um das Image auf physische Hardware zu bringen, muss der ROM-Speicher der Netzwerkkarte geflasht werden. Das kann mit den openSource Werkzeug flashrom getan werden. Es bietet sich an, dies unter einem Livesystem (wir haben [https://grml.org/ Grml] verwendet) zu tun. Dabei stellte sich heraus, dass Root-Rechte (wie zu erwarten) aber zusätzlich auch beim Booten der Kernelparameter <code> iomem=relaxed </code> erforderlich sind. |
||
Line 159: | Line 221: | ||
Damit kann man nun über HTTPS mit Clientauthetisierung booten. |
Damit kann man nun über HTTPS mit Clientauthetisierung booten. |
||
==Testen der Lösung== |
== Testen der Lösung == |
||
=== Funktionalität === |
|||
Im weiteren wird nun neu gestartet und die Netzwerkarte als erstes Boot-Device im Bios eingestellt. Es funktioniert scheinbar wie beabsichtigt: |
Im weiteren wird nun neu gestartet und die Netzwerkarte als erstes Boot-Device im Bios eingestellt. Es funktioniert scheinbar wie beabsichtigt: |
||
[[File:netboot_screen.jpeg|Monitor zeigt iPXE Download von kernel und initrd von HTTPS-Server]] |
[[File:netboot_screen.jpeg|Monitor zeigt iPXE Download von kernel und initrd von HTTPS-Server]] |
||
Line 165: | Line 228: | ||
Der Kernel und die Initrd werden von dem Webserver geladen und gebootet, wie auch das Log-File des WWW-Servers zeigt: |
Der Kernel und die Initrd werden von dem Webserver geladen und gebootet, wie auch das Log-File des WWW-Servers zeigt: |
||
<source lang=bash highlight=1> |
<source lang=bash highlight=1> |
||
tail -n 2 /var/log/apache2/access_log_nb |
|||
192.168.5.101 - - [18/Oct/2019:12:43:54 +0200] "GET /vmlinuz HTTP/1.1" 200 7352432 |
192.168.5.101 - - [18/Oct/2019:12:43:54 +0200] "GET /vmlinuz HTTP/1.1" 200 7352432 |
||
192.168.5.101 - - [18/Oct/2019:12:43:54 +0200] "GET /initrd HTTP/1.1" 200 10822844 |
192.168.5.101 - - [18/Oct/2019:12:43:54 +0200] "GET /initrd HTTP/1.1" 200 10822844 |
||
Line 174: | Line 237: | ||
und überzeugen uns, dass eine ausreichend stark gesicherte TLS-Verbindung <code>Cipher Suite: TLS_RSA_WITH_AES_128_CBC_SHA256 (0x003c)</code> ausgehandelt wurde und verwendet wird. |
und überzeugen uns, dass eine ausreichend stark gesicherte TLS-Verbindung <code>Cipher Suite: TLS_RSA_WITH_AES_128_CBC_SHA256 (0x003c)</code> ausgehandelt wurde und verwendet wird. |
||
Damit ist im Sinne eines Proof of Concept gezeigt, dass der von uns gewählte Ansatz geeignet ist, das Booten aus dem Netzwerk abzusichern. |
|||
=== Bemerkungen, Probleme=== |
|||
* iPXE unterstützt als stärkste <code>Cipher Suite: TLS_RSA_WITH_AES_256_CBC_SHA256 (0x003d)</code> und bietet diese zur Aushandlung im ClientHello auch an. Der Apache sollte so konfiguriert werden, dass diese auch gewählt wird. "forward secure" Cipher Suites sind nicht möglich. Dies ist für unser Szenario aber hinnehmbar. |
|||
* Es ist zumindest überlegenswert, 3072 Bit oder gar 4096 Bit RSA-Schlüssel zu verwenden, um eine dauerhaft sicherere Authentifizierungsmethode zu verwenden, da das Flashen relativ langwierig ist. Entsprechend könnten auch die Laufzeiten der CA sowie der im ROM enthaltenen Clientzertifikate erhöht werden (orientiert an: BSI TR-02102 Kryptographische Verfahren: Empfehlungen und Schlüssellängen). |
|||
* Wenn das System produktiv eingesetzt werden soll, ist es aus unser Sicht unbedingt kompromittiere oder nicht mehr genutzte Zertifikate revoziert werden und die CRL auch vom [[Safer_netboot#apache.conf|Webserver (bislang auskommentiert)]] geprüft werden. |
|||
* Man sollte sich bewusst sein, dass der '''private Schlüssel''' zum Clientzertifikat in der Firmware auf der Netzkarte gespeichert ist. Wie wir beim erstellen gesehen haben, kann man das ROM von dort auch wieder auslesen, wenn man Zugriff mit Root-Rechten auf die Netzwerkkarte hat. Daraus ergeben sich die folgenden Anforderungen, um den '''Schlüssel zu schützen''': |
|||
*# Angreifer dürfen keinen physischen Zugriff zu der Netzwerkkarte bekommen! |
|||
*# Angreifer dürfen nicht Root-Rechten im gebooteten Betriebssystem bekommen! |
|||
*# Angreifern darf es nicht möglich sein, ein andres OS zu booten! |
|||
* Die Anforderung 1. kann zumindest halbwegs mit abgeschlossenen Gehäusen in Poolräumen, oder Onboard-Netzwerkkarten, oder bei Servern mit schlicht und einfach nicht zugänglichen Räumen umgesetzt werden. Die Anforderung 2. setzt 3. voraus und ist in den meisten Fällen ohnehin zu erfüllen, um einen sicheren Rechnerbetrieb zu gewährleisten. Anforderung 3. kann über das setzen der Bootorder, Abschalten externer Laufwerke zum Booten mit dem BIOS-Passwort abgesichert werden. |
|||
=== Weitere Ideen === |
|||
* Um z.B. Poolrechner mit individuellen Konfigurationen, privaten Schlüsseln, oder Scripten aus dem Netz zu booten, kann benutzt werden, dass mehrere INITRD-Einträge in iPXE benutzt werden können. Man könnte also etwa durch den Webserver für alle Poolrechner KERNEL und INITRD gemeinsam vorhalten und die individuellen (kleinen) Zusätze in eine extra INITRDX auslagern, die dann der Webserver statisch vorhält oder dynamisch erzeugt und gemäß der durch das jeweilige Clientzertifikat bewiesenen Identität des bootenden Rechners ausgeliefert wird. <source lang=bash> |
|||
#!ipxe # Shebang |
|||
dhcp # Netzwerkkonfiguration über DHCP bekommen |
|||
kernel https://192.168.5.22/vmlinuz # lade Kernel über HTTPS von URL |
|||
initrd https://192.168.5.22/initrd # lade initrd über HTTPS von URL |
|||
# lade zusätzliche Datei, blende diese unter dem Namen /sbin/init mit den Rechten 755 ein. |
|||
initrd https://192.168.5.22/initrdx /sbin/init mode=755 |
|||
boot # Bootvorgang |
|||
</source> Das sollte gemäß [[https://ipxe.org/cmd/imgfetch| iPXE Dokumentation initrd]] dazu führen, dass das Default-Dateisystem in der INITRD in iPXE mit den Zusätzen in INITRDX überlagert wird. |
|||
* Auch das beidseitig authentisierte Nachladen von iPXE-Skripten und anzeigen von Bootmenüs und anschließende Booten von anderen Systemen (KERNEL+INITRD) könnte ausgetestet werden. |
|||
* Zusätzlich zur Authentisierung mit Clientzertifikaten scheint iPXE auch eine "Basic Auth" auf Basis von Passworten gegenüber dem Webserver zu unterstützen. So wäre es denkbar, beim Booten die Wahl zwischen verschiedenen Systemen zwar anzubieten, das Defaultsystem könnte automatisch nach einem kurzen Timout gebootet werden. Wird jedoch ein anderer Eintrag gewählt, der z.B. nur für Administratoren zugänglich sein, so könnte dieser dann zum Download der Kernels+INITRD oder weiterer iPXE-Skripte zusätzlich ein Passwort erfordern. |
|||
* Beim CHAIN-BOOT ist zu beachten, dass nach dem Booten eines anderen Kernels die der Firmware der Netzwerkkarte hinterlegten Zertifikat und der private Schlüssel zum Clientzertifikat nicht mehr benutzt werden können. |
|||
* Nicht benötigte Boot-Mechanismen im iPXE sollten im Konfigurationsfile ggf. abgeschaltet werden <code>/src/config/general.h</code>, damit das erzeugte ROM nicht größer als nötig wird und keine unnötige Angriffsfläche geboten wird. |
|||
* Auch nachgeladene iPXE-Skripte sollten nicht gestatten, in iPXE als Shell eigene Kommandos einzugeben (außer, der Zugriff erforderte bereits eine stärkere Authentisierung, wie das Administratorpasswort). |
Latest revision as of 12:34, 15 November 2019
Wenn eine Vielzahl von Clientrechnern vorhanden sind, die es zum Beipiel neu zu installieren gilt, dann bietet sich die Installation über Netboot - vorrangig über PXE - an. In der einfachen Umsetzung besteht die Infrastruktur, die nötig für den Netboot ist, aus einem DHCP-Server, der die nötige IP-Konfiguration bereitstellt, und den TFTP-Server, der die Files für den nächsten Bootstage bereit stellt. Wenn ein Client nun über sein PXE-Boot-ROM, welches auf der Netzwerkkarte oder direkt im BIOS, bei internen Netzwerkarten, gespeichert ist, bootet, sucht er nach einer gültigen Konfiguration für das Netzwerk. Daraufhin lädt er vom vom DHCP-Server angekündigten TFTP-Server nötige Komponenten für den nächsten Boot-Stage. Dies sind zum Beispiel Kernel und initrd. Das Problem an dieser Stelle ist das folgende: Die Kommunikation zwichen den Servern und dem Client, insbesondere zwischen TFTP-Server und Client sind unverschlüsselt. Das schreit praktisch schon nach einem Man-in-the-Middle-Angriff. Außerdem gibt es keine Restriktionen in Bezug darauf, welche Clients berechtigt sind, Daten von dem TFTP-Server herrunter zu laden. Das ist dann kritisch, wenn zum Beispiel in der initrd Schlüssel gespeichert werden, um geschützte Netzlaufwerke zu mounten, die es zu schützen gilt. Im Weiteren wird ein Konzept betrachtet, mit dem diesem Problem begegnet werden soll.
Netboot neu überdacht
Um beide Probleme zu beseitigen, ist eine mögliche Antwort HTTPS. In HTTPS sind bereits passende Lösungen eingebaut: Mit der asymmetrischen Krytographie wird das Verschüsselungsproblem gelöst, woduch keine Man-in-the-Middle-Angriffe mehr möglich sind; gleichzeitg werden in HTTPS auch clientseitig Authentifizierung unterstützt. Das heißt alleinig dadurch, dass das "Transportprotokoll", mit dem die Daten zum Client übertragen werden, geändert wird, werden beide Probeleme gelöst. Aber diese Änderung hat auch Änderung in der Infrastrutur zur Folge. Diese Änderungen werden nachfolgend beschrieben.
Neues PXE
Da das gewöhnliche PXE-ROM standardmäßig kein HTTP unterstützt, wird hier eine Alternative gebraucht. Hier bietet sich iPXE an. Auf der Projektwebseite sagen die Autoren selbst: ”iPXE is the leading open source network boot firmware." iPXE unterstützt eine Vielzahl an Protokollen wie HTTP(S), iSCSI, FCoE, AoE, wireless und mehr. Weiter ist der Bootvorgang scriptable. Das heißt, man kann iPXE schon vorher sagen, welche Files von wo geladen werden sollen. Das entbindet von der Notwendigkeit, diese Addressen über DHCP zu verteilen. Von großem Vorteil ist, dass iPXE-ROMs auch für physische Hardware gebaut werden können, die auch auf die Chips dieser Hardware geflasht werden kann. Es bietet auch die Möglichkeit des Chainloadings und eines Bootprompts, um den Netboot manuell vor der Maschine zu bedienen.
Eigene Public Key Infrastruktur
Zertifikate
HTTP soll über TLS abgesichert werden. Dafür sind Zertifikate für die Server- und die Clientseite zur nötig, um eine gegenseitige Authentifizierung zu erreichen. iPXE unterstützt exklusiv [RSA-Zertifikate]. Für unser Vorhaben erstellen wir mit OpenSSL eine eigene (Root)CA, die sowohl das Zertifikat des HTTPS-Boot-Servers als auch die Clientzertifikate für die Clienten, die von diesem Server Material zum Booten herunterladen sollen, signiert. Wir lassen uns von https://ipxe.org/crypto inspirieren:
### preparation
echo 01 > ca.srl
touch ca.idx
mkdir signed
### generating the CA:
openssl req -x509 -newkey rsa:2048 -out ca.crt -keyout ca.key -days 1000
### generating request for the server key (without password protection):
openssl req -config server.conf -nodes -newkey rsa:2048 -keyout server.key -out server.req
### signing server cert
openssl ca -config ca.conf -extensions v3_ext_server -in server.req -out server.crt
### generating request for the client key (without password protection):
openssl req -config client.conf -nodes -newkey rsa:2048 -keyout client.key -out client.req
### signing client cert
openssl ca -config ca.conf -extensions v3_ext_client -in client.req -out client.crt
Dabei finden die folgenden drei OpenSSL-Konfigurationsdateien ca.conf
, server.conf
und client.conf
Verwendung:
ca.conf
enthält spezifische Abschnitte fürv3
-Erweiterungen. Für den Server wird hier die IP-Adresse mit in das Zertifikat alssubjectAltName
mit aufgenommen, was wünschenswert ist, das im Bootrom diese hinterlegt ist und die Auflösung eines DNS-Namens im Bootprozess nicht erforderlich ist. Für den Server wird dieextendedKeyUsage = clientAuth
definiert:[ ca ] default_ca = ca_default copy_extensions = copy [ ca_default ] certificate = ca.crt private_key = ca.key serial = ca.srl database = ca.idx new_certs_dir = signed default_md = default policy = policy_anything preserve = yes default_days = 356 unique_subject = no [ policy_anything ] countryName = optional stateOrProvinceName = optional localityName = optional organizationName = optional organizationalUnitName = optional commonName = optional emailAddress = optional [ v3_ext_server ] extendedKeyUsage = serverAuth subjectAltName = @alt_names [ alt_names ] DNS.1 = wolftux DNS.2 = wolftux.sar.informatik.hu-berlin.de IP.1 = 192.168.5.22 [ v3_ext_client ] extendedKeyUsage = clientAuth
server.conf
definiert dendistinguished_name
des Boot-Servers.[ req ] distinguished_name = req_distinguished_name prompt = no [ req_distinguished_name ] C = DE ST = Berlin L = Berlin O = HUB OU = Informatik CN = netBootServer
client.conf
definiert dendistinguished_name
des Boot-Clients. Hier könnte man in denCN
die Hardwareadresse der Netzwerkkarte aufnehmen, so dass der Boot-Server abhängig von der mittels des Client-Zertifikats (und des Besitzes des zugehörigen privaten Schlüssels) nachgewiesenen Identität verschiedene Kernel oder Initrds ausliefern kann.[ req ] distinguished_name = req_distinguished_name prompt = no [ req_distinguished_name ] C = DE ST = Berlin L = Berlin O = HUB OU = Informatik CN = netBootClient_HWADDR
Die generierten Zertifikate ca.crt
, server.crt
und client.crt
können mit openssl x509 -in name.crt -noout -text
betrachtet werden.
Webserver
Da der Webserver HTTPS-Anfragen entgegen nehmen soll, bedarf dieser ein kleines an Konfiguration:
<VirtualHost *:443>
SSLEngine on # SSL an
SSLCertificateFile /etc/apache2/server.crt # Serverzertifikat
SSLCertificateKeyFile /etc/apache2/server.key # Serverprivatekey
SSLVerifyClient require # verifiziere Clientzertifikate
SSLVerifyDepth 1 # lass Client mit Zertifikaten bis Tiefe 1 unterhalb
SSLCACertificateFile /etc/apache2/ca.crt # dieser CA zu
#SSLCARevocationFile /etc/apache2/ca.crl # Prüfung auf Widerruf von Zertifikaten (für produktiven Einsatz)
</VirtualHost>
Weiter müssen die Files für den nächsten Bootstage des Clients bereit liegen.
iPXE-Image
Die Images werden aus den Quellen direkt kompiliert. Anpassungen werden über die Kommandozeile beim Kompilieren mitgeteilt. Für HTTPS ist auch eine Anpassung in src/config/general.h
nötig. Wir schalten die TFTP-Unterstützung aus und die HTTPS-Unterstützung ein:
diff --git a/src/config/general.h b/src/config/general.h
index 3c14a2cd..a755e7a1 100644
--- a/src/config/general.h
+++ b/src/config/general.h
@@ -52,9 +52,9 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
*
*/
-#define DOWNLOAD_PROTO_TFTP /* Trivial File Transfer Protocol */
+#undef DOWNLOAD_PROTO_TFTP /* Trivial File Transfer Protocol */
#define DOWNLOAD_PROTO_HTTP /* Hypertext Transfer Protocol */
-#undef DOWNLOAD_PROTO_HTTPS /* Secure Hypertext Transfer Protocol */
+#define DOWNLOAD_PROTO_HTTPS /* Secure Hypertext Transfer Protocol */
#undef DOWNLOAD_PROTO_FTP /* File Transfer Protocol */
#undef DOWNLOAD_PROTO_SLAM /* Scalable Local Area Multicast */
#undef DOWNLOAD_PROTO_NFS /* Network File System Protocol */
Die Optionen, die benötigt werden sind, diese:
* script (”EMBED=”)
* Zertifikate (”CERT=”)
* Vertrauen (”TRUST=”)
* privaten SChlüssel (”PRIV=”)
Das beigefügt Script beihnhaltet:
#!ipxe # Shebang
dhcp # Netzwerkkonfiguration über DHCP bekommen
kernel https://192.168.5.22/vmlinuz # lade Kernel über HTTPS von URL
initrd https://192.168.5.22/initrd # lade initrd über HTTPS von URL
boot # Bootvorgang
Dann kann das ROM-Image gebaut werden. Mit folgender Zeile wird ein Boot-ROM-Image für eine Intelnetzwerkkarte des Typs 107c gebaut:
make bin/8086107c.rom EMBED=boot.ipxe TRUST=ca.crt CERT=ca.crt,client.crt PRIVKEY=client.key
Der Typ der Karte (VendorID und ProduktID) kann mittels lspci -nn ermittelt werden.
ROM flashen
Um das Image auf physische Hardware zu bringen, muss der ROM-Speicher der Netzwerkkarte geflasht werden. Das kann mit den openSource Werkzeug flashrom getan werden. Es bietet sich an, dies unter einem Livesystem (wir haben Grml verwendet) zu tun. Dabei stellte sich heraus, dass Root-Rechte (wie zu erwarten) aber zusätzlich auch beim Booten der Kernelparameter iomem=relaxed
erforderlich sind.
Wenn mit
lspci -nn | grep Ethernet
05:04.0 Ethernet controller [0200]: Intel Corporation 82541PI Gigabit Ethernet Controller [8086:107c] (rev 05)
herrausgefunden wurde, an welcher pci-Adresse (hier 05:04.0
) die Netzwerkkarte zu finden ist und welcher Programmer (in unserem Beispiel nicintel_spi
)verwendet werden muss (letzteres verrät das flashrom Wiki im Abschnitt "supported hardware" beim Suchen nach 8086:107c
), kann man damit loslegen, die "Flasbarkeit" zu testen:
flashrom -p nicintel_spi:pci=05:04.0
flashrom v0.9.9-r1954 on Linux 4.19.0-1-grml-amd64 (x86_64)
flashrom is free software, get the source code at https://flashrom.org
Calibrating delay loop... OK.
Found Atmel flash chip "AT25F512" (64 kB, SPI) on nicintel_spi.
Found Atmel flash chip "AT25F1024(A)" (128 kB, SPI) on nicintel_spi.
Multiple flash chip definitions match the detected chip(s): "AT25F512", "AT25F1024(A)"
Please specify which chip definition to use with the -c <chipname> option.
Auf der verwendeten Netzwerkkarte stehen offenbar zwei Chips zum flashen zur Auswahl, da unser iPXE-Image größer als 64kB ist, entscheiden wir uns für den weiten Chip und erstellen sicherheitshalber ein Backup (was natürlich anschließend extern des Live-Syystems GRML persistent gespeichert werden sollte):
flashrom -p nicintel_spi:pci=05:04.0 -c "AT25F1024(A)" -r backup"AT25F1024(A)".rom
flashrom v0.9.9-r1954 on Linux 4.19.0-1-grml-amd64 (x86_64)
flashrom is free software, get the source code at https://flashrom.org
Calibrating delay loop... OK.
Found Atmel flash chip "AT25F1024(A)" (128 kB, SPI) on nicintel_spi.
Reading flash... done.
flashrom -p nicintel_spi:pci=05:04.0 -c "AT25F1024(A)" -r 7,28s user 0,00s system 99% cpu 7,285 total
Die Option -c
gibt an, welcher Chip verwendet werden soll, wenn die Netzwerkkarte bzw. flashrom beim Probing mehrere Chips entdeckt.
Bevor das Image geflasht werden kann, muss das Image auf die Größe des Chips (hier 128kB) gepaddet werden. Das kann mit dem folgenden Kommando erledigt werden:
(cat bin/8086107c.rom; tr '\0' '\377' < /dev/zero) | dd bs=1 count=128k of=netBootCA/8086107c.rom_padded_128k
Dann kann das gepaddete Image geflasht werden:
flashrom -p nicintel_spi:pci=05:04.0 -c "AT25F1024(A)" -w 8086107c.rom_padded_128k
flashrom v0.9.9-r1954 on Linux 4.19.0-1-grml-amd64 (x86_64)
flashrom is free software, get the source code at https://flashrom.org
Calibrating delay loop... OK.
Found Atmel flash chip "AT25F1024(A)" (128 kB, SPI) on nicintel_spi.
Reading old flash chip contents... done.
Erasing and writing flash chip... Erase/write done.
Verifying flash... VERIFIED.
flashrom -p nicintel_spi:pci=05:04.0 -c "AT25F1024(A)" -w 25,76s user 0,00s system 99% cpu 25,765 total
Damit kann man nun über HTTPS mit Clientauthetisierung booten.
Testen der Lösung
Funktionalität
Im weiteren wird nun neu gestartet und die Netzwerkarte als erstes Boot-Device im Bios eingestellt. Es funktioniert scheinbar wie beabsichtigt:
Der Kernel und die Initrd werden von dem Webserver geladen und gebootet, wie auch das Log-File des WWW-Servers zeigt:
tail -n 2 /var/log/apache2/access_log_nb
192.168.5.101 - - [18/Oct/2019:12:43:54 +0200] "GET /vmlinuz HTTP/1.1" 200 7352432
192.168.5.101 - - [18/Oct/2019:12:43:54 +0200] "GET /initrd HTTP/1.1" 200 10822844
Wir schauen uns aber um sicher zu sein, dass die Verbindung auch tatsächlich TLS-verschlüsselt, beidseitig authentisiert und somit vertraulich und authentisch ist, das Ergebnis noch mit Wireshark an:
und überzeugen uns, dass eine ausreichend stark gesicherte TLS-Verbindung Cipher Suite: TLS_RSA_WITH_AES_128_CBC_SHA256 (0x003c)
ausgehandelt wurde und verwendet wird.
Damit ist im Sinne eines Proof of Concept gezeigt, dass der von uns gewählte Ansatz geeignet ist, das Booten aus dem Netzwerk abzusichern.
Bemerkungen, Probleme
- iPXE unterstützt als stärkste
Cipher Suite: TLS_RSA_WITH_AES_256_CBC_SHA256 (0x003d)
und bietet diese zur Aushandlung im ClientHello auch an. Der Apache sollte so konfiguriert werden, dass diese auch gewählt wird. "forward secure" Cipher Suites sind nicht möglich. Dies ist für unser Szenario aber hinnehmbar. - Es ist zumindest überlegenswert, 3072 Bit oder gar 4096 Bit RSA-Schlüssel zu verwenden, um eine dauerhaft sicherere Authentifizierungsmethode zu verwenden, da das Flashen relativ langwierig ist. Entsprechend könnten auch die Laufzeiten der CA sowie der im ROM enthaltenen Clientzertifikate erhöht werden (orientiert an: BSI TR-02102 Kryptographische Verfahren: Empfehlungen und Schlüssellängen).
- Wenn das System produktiv eingesetzt werden soll, ist es aus unser Sicht unbedingt kompromittiere oder nicht mehr genutzte Zertifikate revoziert werden und die CRL auch vom Webserver (bislang auskommentiert) geprüft werden.
- Man sollte sich bewusst sein, dass der private Schlüssel zum Clientzertifikat in der Firmware auf der Netzkarte gespeichert ist. Wie wir beim erstellen gesehen haben, kann man das ROM von dort auch wieder auslesen, wenn man Zugriff mit Root-Rechten auf die Netzwerkkarte hat. Daraus ergeben sich die folgenden Anforderungen, um den Schlüssel zu schützen:
- Angreifer dürfen keinen physischen Zugriff zu der Netzwerkkarte bekommen!
- Angreifer dürfen nicht Root-Rechten im gebooteten Betriebssystem bekommen!
- Angreifern darf es nicht möglich sein, ein andres OS zu booten!
- Die Anforderung 1. kann zumindest halbwegs mit abgeschlossenen Gehäusen in Poolräumen, oder Onboard-Netzwerkkarten, oder bei Servern mit schlicht und einfach nicht zugänglichen Räumen umgesetzt werden. Die Anforderung 2. setzt 3. voraus und ist in den meisten Fällen ohnehin zu erfüllen, um einen sicheren Rechnerbetrieb zu gewährleisten. Anforderung 3. kann über das setzen der Bootorder, Abschalten externer Laufwerke zum Booten mit dem BIOS-Passwort abgesichert werden.
Weitere Ideen
- Um z.B. Poolrechner mit individuellen Konfigurationen, privaten Schlüsseln, oder Scripten aus dem Netz zu booten, kann benutzt werden, dass mehrere INITRD-Einträge in iPXE benutzt werden können. Man könnte also etwa durch den Webserver für alle Poolrechner KERNEL und INITRD gemeinsam vorhalten und die individuellen (kleinen) Zusätze in eine extra INITRDX auslagern, die dann der Webserver statisch vorhält oder dynamisch erzeugt und gemäß der durch das jeweilige Clientzertifikat bewiesenen Identität des bootenden Rechners ausgeliefert wird. Das sollte gemäß [iPXE Dokumentation initrd] dazu führen, dass das Default-Dateisystem in der INITRD in iPXE mit den Zusätzen in INITRDX überlagert wird.
#!ipxe # Shebang dhcp # Netzwerkkonfiguration über DHCP bekommen kernel https://192.168.5.22/vmlinuz # lade Kernel über HTTPS von URL initrd https://192.168.5.22/initrd # lade initrd über HTTPS von URL # lade zusätzliche Datei, blende diese unter dem Namen /sbin/init mit den Rechten 755 ein. initrd https://192.168.5.22/initrdx /sbin/init mode=755 boot # Bootvorgang
- Auch das beidseitig authentisierte Nachladen von iPXE-Skripten und anzeigen von Bootmenüs und anschließende Booten von anderen Systemen (KERNEL+INITRD) könnte ausgetestet werden.
- Zusätzlich zur Authentisierung mit Clientzertifikaten scheint iPXE auch eine "Basic Auth" auf Basis von Passworten gegenüber dem Webserver zu unterstützen. So wäre es denkbar, beim Booten die Wahl zwischen verschiedenen Systemen zwar anzubieten, das Defaultsystem könnte automatisch nach einem kurzen Timout gebootet werden. Wird jedoch ein anderer Eintrag gewählt, der z.B. nur für Administratoren zugänglich sein, so könnte dieser dann zum Download der Kernels+INITRD oder weiterer iPXE-Skripte zusätzlich ein Passwort erfordern.
- Beim CHAIN-BOOT ist zu beachten, dass nach dem Booten eines anderen Kernels die der Firmware der Netzwerkkarte hinterlegten Zertifikat und der private Schlüssel zum Clientzertifikat nicht mehr benutzt werden können.
- Nicht benötigte Boot-Mechanismen im iPXE sollten im Konfigurationsfile ggf. abgeschaltet werden
/src/config/general.h
, damit das erzeugte ROM nicht größer als nötig wird und keine unnötige Angriffsfläche geboten wird. - Auch nachgeladene iPXE-Skripte sollten nicht gestatten, in iPXE als Shell eigene Kommandos einzugeben (außer, der Zugriff erforderte bereits eine stärkere Authentisierung, wie das Administratorpasswort).