transparent verschlüsselte VMs mit Libvirt

Vorwort

Diese Anleitung soll einen kurzen Überblick ermöglichen, wie 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 (transparent).
Somit ist keine VNC- / Spike- / serielle Verbindung beim booten, oder gar ein Dropbear Server von nötigen um das Gastsystem zu booten.

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.

# 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
UUID=<UUID des Libvirt Secrets>

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 einbinden

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

<domain type='kvm'>
<!-- INHALT AUF DISK CONFIG REDUZIERT! -->
    <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>