verschlüsselte VM Images mit Libvirt und LUKS (transparent)

verschlüsselte VM Images mit Libvirt und LUKS (transparent)
Photo by Towfiqu barbhuiya / Unsplash

Diese Anleitung soll einen kurzen Überblick ermöglichen, wie man mittels Libvirt und LUKS Images virtueller Maschinen verschlüsseln kann.
Hierbei findet die Verschlüsselung nicht in der virtuellen Maschine selbst statt, sondern wird außerhalb angewendet, sodass das Gastsystem nichts von der Verschlüsselung mitbekommt.

Somit ist keine VNC- / Spike- / serielle Verbindung beim booten, oder gar ein Dropbear Server von nötigen um das Gastsystem zu booten.

Ich gehe hierbei davon aus, dass der Leser grundlegend weiß, was LUKS ist und wie das Verfahren für das Öffnen / Schließen des Containers funktioniert...

Libvirt vorbereiten

Secret anlegen

Zuerst muss ein Kennwort (bzw. ein Keyfile) generiert werden, was zum Öffnen des LUKS-Containers genutzt wird. Jedes Image hat zwar in seiner Struktur einen individuellen Schlüssel mit dem die symmetrische Verschlüsselung realisiert wird, allerdings muss eben jener mittels Keyfile geöffnet werden.

Libvirt verwaltet diese Schlüssel in sogenannten Secrets, welches wir folgenderweise anlegen:

# UUID generieren und in Variable UUID speichern
UUID=$(uuidgen)

# Anlegen der Datei mysecret.xml mit Nutzung der UUID Variablen
cat <<EOF > mysecret.xml
<secret ephemeral='no' private='yes'>
   <description>my luks secret</description>
   <uuid>${UUID}</uuid>
</secret>


virsh secret-define --file mysecret.xml

# 128 Zeichen langes Kennwort generieren und in Base64 speichern
# die Nutzung von -base64 statt -hex sorgt dafür, dass der Key binäre Daten enthält. Hier kann man selbst entscheiden ob der Schlüsel lesbar sein soll ;). Ich empfehle die Konstellation mit -hex | base64 zu nutzen.

PASSWORD=$(openssl rand -hex 128 | base64)

# Passphrase / Key setzen
virsh secret-set-value "${UUID}" "${PASSWORD}"

# Secrets auflisten, hier sollte das Secret angezeigt werden!
virsh secret-list

Volume anlegen

Wenn das Secret in Libvirt existiert, können wir dieses nun für Images nutzen. Hierbei bitte darauf achten, dass die UUID unten angegeben werden muss. Diese entspricht eben jener die wir gerade vorher angelegt haben.

Außerdem können Parameter der Verschlüsselung hier wie gewünscht angepasst werden.
Aber nun zum eigentlichen Anlegen:

V_NAME=my_volume
# in GB, ohne Einheit (siehe <capacity>)
V_SIZE=100
V_PATH="/srv/images/${V_NAME}"


cat <<EOF > myvolume.xml
<volume>
  <name>${V_NAME}</name>
  <capacity unit='G'>${V_SIZE}</capacity>
  <target>
    <path>${V_PATH}</path>
    <format type='raw'/>
    <encryption format='luks'>
       <secret type='passphrase' uuid='${UUID}'/>
       <cipher name='aes' size='256' mode='xts' hash='sha512'/>
       <ivgen name='plain64' hash='sha512'/>
    </encryption>
  </target>
</volume>

# Volume anlegen, bitte falls ein anderer Pool gewünscht ist, entsprechend anpassen
virsh vol-create --pool default --file myvolume.xml

Libvirt erzeugt danach mittels QEMU-IMG ein Image, welches außen mittels LUKS gesichert ist und im inneren mit RAW-Format verwendet wird, nicht dass man plötzlich wunderbare QCOW2 Funktionen erwartet ;)

Image in VM nutzen

In der XML Defintion der virtuellen Maschine müssen entsprechende Parameter gesetzt sein, damit das Image angezogen werden kann. Deshalb hier ein Beispiel aus dem Kontext heraus gezogen, mit aufgelösten Variablen.

# File:

<domain type='kvm'>
....
....
    <disk type='file' device='disk'>
      <!-- cache deaktivieren und io=native bringen Geschwindigkeit -->
      <driver name='qemu' type='raw' cache='none' io='native'/>
      <!-- PFAD wie in der my_volume.xml! -->
      <source file='/srv/images/my_volume'/>
      <backingStore/>
      <target dev='vda' bus='virtio'/>
      <encryption format='luks'>
        <!-- unbedingt die UUID mit der Secret-UUID beibehalten, darüber wird der Schlüssel zum öffnen des LUKS Containers gefunden -->
        <secret type='passphrase' uuid='16cd518f-35ee-4a55-acc3-376bb0c9152e'/>
      </encryption>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
    </disk>

....
....
</domain>

Beim starten der VM wird das Image geöffnet und transparent der VM bereitgestellt. Danach kann ein Betriebssystem installiert werden.