Virtualisierung mit KVM

Nachdem der Trend immer mehr zur private Cloud geht, stellt sich natürlich die Frage, wie man so etwas auch sicher realisieren kann. Viele haben einen kleinen Server im Keller oder Abstellraum stehen, der sich prima für die eigenen Cloudexperimente eignen würde. Aber möchte man wirklich den Internetzugriff auf dieses System erlauben?

Zudem stellt sich natürlich die Frage, ob man der Sicherheit des Webservers und der Applikation so weit vertraut, dass man ihr wirklich die auf dem selben Server liegenden Steuerunterlagen oder die Videosammlung anvertrauen möchte? Einige mögen jetzt einwenden, dass eine Changeroot-Umgebung doch Schutz bietet. Aber das tut sie leider nicht 1. Somit gibt es aus meiner Sicht nur eine Lösung, um es einem Angreifer schwer zu machen: Man virtualisiert den Server.

Doch auch die Virtualisierung ist keine absolut sichere Bank. Man muss also auch auf dem Gastsystem für ausreichend Sicherheit sorgen. Jedoch ist es schon erheblich schwerer aus einer VM auszubrechen (auch mit root-Rechten), als aus einer Changeroot-Umgebung. Zusätzlich kann man innerhalb der VM die Sicherheitseinstellungen sehr restriktiv wählen, ohne das man sich mit diesen Einstellungen dann auch auf dem Heimserver herumschlagen müsste.

KVM, libvirt und Debian

Zumindest auf den ersten Blick wächst bei libvirt, KVM und Debian Squeeze zusammen, was nie so richtig zusammen gehört zu haben scheint. Die in Squeeze enthaltene KVM Version funktioniert nur leidlich mit der enthaltenen libvirt. Die Dokumentation 2 ist zwar hilfreich, aber - wie man es gewohnt ist - irgendwo zwischen veraltet und zu neu (d. h. die Features sind noch nicht implementiert). Also ist viel probieren, tüfteln und schrauben nötig um alles zum Laufen zu bekommen. Um anderen Interessierten dieses Drama zu ersparen, möchte ich im Folgenden einmal einige Tipps zusammenstellen.

Wichtig: Dies hier liest sich zwar wie eine “Schritt für Schritt”-Anleitung, soll jedoch keine sein. Einfach nur alles aus diesem Posting heraus in eine Konsole zu kopieren wird eher nicht funktionieren. Wie immer gilt die Regel: Erst verstehen, dann nachmachen.

Einzelteile

## KVM KVM ist quasi Qemu mit Kernelnachbrenner. Durch ein Kernelmodul kann KVM sehr bequem die Virtualisierungsfeatures moderner Prozessoren nutzen. Hierdurch fällt der Virtualisierungsoverhead kleiner aus und die Performance steigt. Zusätzlich kennt KVM (genauso wie Qemu) einige paravirtualisierte Gerätetreiber3, welche die Nutzung von Netzwerk, Festplatten und anderem Getier deutlich vereinfachen.

libvirt

Libvirt ist eine Bibliothek welche angetreten ist eine Abstraktionsschicht zwischen den unterschiedlichen Virtualisierungsmöglichkeiten unter Linux und ihren Nutzern (Applikationen oder der User) einzuziehen. Leider klappt das nicht immer reibungslos, dafür sind die Virtualisierungsansätze wohl doch zu unterschiedlich. Jedoch bringt libvirt für das Vorhaben Virtualisierung einige sehr angenehme Features mit. Hierzu gehört das Herunterfahren der VMs bei einem Shutdown des Hostsystems und das automatische Starten der VMs während des Systemstarts des Hosts. Außerdem erlaubt die Virtualisierungsshell virsh das komfortable Management der VMs.

Debian Squeeze

Ich beschreibe das Vorgehen hier für Debian Squeeze. Für Debian Derivate wie Ubuntu ist das Vorgehen daher wahrscheinlich sehr ähnlich, bei anderen Distributionen können größere Anpassungen notwendig sein. Im Prinzip sollte es aber unter jedem Linux so funktionieren.

Vorbereiten des Hosts

Zurück aus der Zukunft

Leider sind die in Debian Squeeze enthaltenen Versionen von KVM und libvirt schon etwas angestaubt. Einige Dinge funktionieren mit diesen Versionen nicht oder nicht zuverlässig. Daher ist es notwendig einige Pakete aus der Zukunft zu benutzen. Hierfür gibt es bequemer Weise das Backports-Repository von Debian. Das Repository sollte man nach dieser Anleitung einrichten. Ist man damit fertig, stehen neuere Paketversionen auf Wunsch4 zur Verfügung.

Installation der Pakete

Benötigt werden die Pakete qemu-kvm und libvirt-bin. Natürlich müssen auch die nötigen Abhängigkeiten installiert werden. ~~~~ bash sudo apt-get -t squeeze-backports install qemu-kvm libvirt-bin ~~~~

Hook me up

Als nächstes muss die Netzwerkverbindung für die VMs vorbereitet werden. Am einfachsten hängt man alle VMs an einen virtuellen Switch in Form eines Bridge-Devices und verbindet dieses mit dem Server. Entgegen vieler im Internet verbreiteter HowTos rate ich dringend davon ab, einfach das Netzwerkinterface eth0 in die Bridge einzubinden. Hierdurch erhalten zwar alle VMs Internetzugang, es ist aber nicht mehr möglich die VMs durch die Firewall des Hosts abzuschirmen. Aus diesem Grund binde ich in dieser Anleitung die Bridge über Routing und die Firewall des Hosts ein.

Netzwerkdiagramm für die Integration der virtuellen Maschinen.

Um die Firewallkonfiguration werde ich mich in einem späteren Artikel noch kümmern. Für’s Erste können wir aufgrund mangelnden IP-Forwardings nur vom Host aus auf die VMs zugreifen. Wer schon eine funktionierende Firewall und IP-Forwarding konfiguriert hat,kann die VMs natürlich auch gleich aus dem internen Netzwerk heraus ansprechen.

Bevor es an die Konfiguration des Bridge-Devices geht, muss noch der Adressbereich für die VMs festgelegt werden. Ich verwende hier 192.168.100.0/24. Natürlich klappt auch jedes andere private Subnetz, welches intern noch nicht in Verwendung ist.

Das neue Bridge-Interface, welches als virtueller Switch dient, heißt br0. Unter Debian ist in der Datei /etc/network/interfaces die Definition des Interfaces hinzuzufügen:

# Bridge als virtueller Switch für die VMs
auto br0
iface br0 inet static
  address 192.168.100.1
  netmask 255.255.255.0
  bridge_ports none
  bridge_stp off
  bridge_maxwait 0

Dieser Eintrag aktiviert die Bridge br0 automatisch beim Hochfahren der VM. Wer seinen Server jetzt nicht neu starten möchte, kann die Bridge auch manuell über sudo ipup br0 an den Start bringen. Nun sollten in der Ausgabe von ip a unter anderem folgende Zeilen auftauchen:

4: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN
    link/ether fe:54:00:26:6a:b1 brd ff:ff:ff:ff:ff:ff
    inet 192.168.100.1/24 brd 192.168.100.255 scope global br0
    inet6 fe40::72cc:6bff:fb70:6a11/64 scope link
       valid_lft forever preferred_lft forever

Wenn alles korrekt funktioniert hat, dann hat der ipup-Befehl auch gleich eine Route auf dieses Interface eingerichtet. Mit ip route lässt sich dies leicht überprüfen:

192.168.100.0/24 dev br0  proto kernel  scope link  src 192.168.100.1

Somit sind also die Systeme, welche an dieser Bridge hängen, vom Host aus erreichbar.

Zuhörer

Ein wichtiger Hinweis für alle, die z. B. DNS oder DHCP aus den VMs nutzen möchten: Hat man die hierfür notwendigen Dienste aus Sicherheitsgründen an spezifische Interfaces gebunden, dann muss man nun auch br0 zu den erlaubten Interfaces hinzufügen.

Kernel-Modul

Damit KVM auf die Virtualisierungsfunktionen der CPU zugreifen kann muss noch das passende Kernelmodul geladen werden. Für Intel-Prozessoren heißt dieses kvm-intel und für AMD-CPUs5 kvm-amd. Zuerst sollte man das entsprechende Modul auf der Kommandozeile über sudo modprobe kvm-intel bzw. sudo modprobe kvm-amd laden und mit dmesg kontrollieren, ob alles geklappt hat. Die Zeile kvm: Nested Virtualization enabled ist hierbei ein gutes Zeichen. Wurde das Modul erfolgreich geladen, kann man es in die Datei /etc/modules eintragen und damit dafür sorgen, dass es bei jedem Systemstart automatisch geladen wird.

Reinen Tisch machen

Ist auf dem System nur die Bridge br0 als virtueller Switch definiert und kein reales Ethernet-Interface mit der Bridge verbunden, dann ist auch das automatisch installierte Modul ebtables überflüssig. Natürlich kann es trotzdem beim Systemstart geladen werden, das Kernel-Modul belegt jedoch unnötig Speicher und das Startscript verzögert den Systemstart. Wer also kein weiteres Bridge-Interface nutzt, kann (zugunsten von iptables) auf ebtables verzichten. Der Start von ebtables lässt sich einfach über sudo update-rc.d ebtables disable abschalten.

Gaststeuerung

Die Gast-VMs sollen natürlich bei einem Shutdown des Hosts nicht einfach abgeschossen werden. Viel mehr soll das Hostsystem die VMs geordnet herunterfahren und später wieder starten. Natürlich können die VMs auch gesichert und später wieder restauriert werden. Aus Gründen der Stabilität bevorzuge ich jedoch einen simplen Shutdown der VM über ACPI. Damit libvirt bei einem Systemshutdown die VMs ebenfalls herunterfährt, muss die Konfiguration in /etc/default/libvirt-guests angepasst werden. Hier ist die Zeile ON_SHUTDOWN=suspend durch ON_SHUTDOWN=shutdown zu ersetzen.

Der automatische Start der VMs erfolgt über libvirt-bin. Der libvirt-Dienst aktiviert die, mit dem Autostart-Flag versehenen, VMs automatisch beim Systemstart. Aus diesem Grund darf auch in der Konfigurationsdatei /etc/default/libvirt-guests die Option ON_BOOT nicht aktiviert werden. Um sicherzustellen, dass die notwendigen Init-Scripte auch wirklich ausgeführt werden, sollte man sie nochmals explizit aktivieren:

update-rc.d libvirt-bin enable
update-rc.d libvirt-guests enable

Wie weiter?

Im zweiten Teil “Firewalling und NAT für die private Cloud” geht es um die Definition der notwendigen Firewallregeln um der VM Internetzugriff zu erlauben und auf die VM zugreifen zu können. Anschließend wird im dritten Teil Debian in der VM installiert.

  1. Changeroot ist kein Sicherheitsfeature. Wie man aus einem Changeroot-Gefängnis entkommt, kann man hier und hier nachlesen. 

  2. Zwingt mich nicht, hier über die Qualität der Dokumentation im OpenSource-Umfeld zu schimpfen. 

  3. Stichwort virtio

  4. Das Repository muss bei apt-get install immer mit -t squeeze-backports ausgewählt werden. 

  5. Wer hätte das gedacht.