If you don’t know what the libvirt is, then the official web page can clarify some things:

The libvirt project is a toolkit to manage virtualization platforms. It is used by many applications and support multiple hypervisors like KVM, QEMU, Xen, VMWare and others.

In this post I will show you how to interact with libvirt using Python and the KVM hypervisor, but first things first.

What do you need to start?

If you are in Ubuntu like me, you need to install the libvirt-dev package. Using apt is easy:

$ sudo apt-get install libvirt-dev

Also, you will need Python and pip installed in your system. We will create a new virtual environment using Python 3.7. For this purpose I use Conda. The next command creates a virtual environment called libvirt

$ conda create --name libvirt python=3.7

When done, you need to activate this environment using conda activate libvirt. Now, we will proceed to install the libvirt-python package version 5.7.0 using pip (at this time, this is the latest release).

(libvirt) $ pip install libvirt-python==5.7.0

If all is ok, then you are ready to go.

Connect to the KVM hypervisor driver

As I commented previously, we’ll use the KVM hypervisor. As this hypervisor is QEMU compatible, the driver will be qemu:///system. If you want to see other options, see this.

Note that this is a privileged URI.

To open a connection to the RPC server exposed by the libvirt daemon, it must be running. You can check it’s state using systemctl status libvirtd. In case that the daemon is not available, you will need to start it using systemctl start libvirtd.

For default, this daemon will only be listening for connection on a local UNIX domain socket. As it is only accessible on the local machine, it’s unencrypted.

There are two types of sockets:

  • Full management capabilities, /var/run/libvirt/libvirt-sock.
  • Read only operations, /var/run/libvirt/libvirt-sock-ro.

We will open the full read-write socket.

>>> import libvirt
>>> conn = libvirt.open('qemu:///system')
>>> conn.getVersion()
3001000

If you want to use the read only socket, then you need to specify it when open() method is called, or use the short way, calling openReadOnly():

>>> conn = libvirt.open('qemu:///system?socket=/var/run/libvirt/libvirt-sock-ro')
>>> # Or call, conn = libvirt.openReadOnly('qemu:///system')
>>> conn.getVersion()
3001000

When no hostname is provided in the URI, by default it uses unix transport (same reduntant URI is qemu+unix:///system?socket=...).

These two methods to create a connection are deprecated in favour of the method openAuth(driver, credentials, flags). Apart from the driver URI, it takes two more arguments, one with a Python list with the client credentials, and a flags parameter to allows the application request a read only or other type of connection.

I will show you how to configure libvirt with authentication using SASL. There are several points to note:

  • SASL doesn’t require real accounts on the server. It uses its own database to store names and passwords.
  • Connections using SASL are encrypted. The current data encryption mechanism in the new versions of libvirt is GSSAPI, but we will use TLS. Since we will use SASL on top of TLS, we can deactivate session encryption to avoid overhead.

Lets go. First, we need to edit the file /etc/default/libvirtd and add this option to start listening on TCP: libvirtd_opts="-l"

You can use -l or --listen

Next, if you restart the libvirtd using systemctl restart libvirtd, it will fail. Why?. Well, by default when this option is enabled, it is necesary to set up a CA and issue server certificates.

We need to generate server certificates. For this purpose we will use openssl.

$ # First we create a random passphrase that is written to the passphrase.txt file
$ echo `openssl rand -base64 8` > passphrase.txt
$ # Generate CA certificates
$ openssl genrsa -out cakey.pem -passout file:passphrase.txt 2048
$ openssl req -new -x509 -key cakey.pem -out cacert.pem -passin file:passphrase.txt
$ # Generate new CSR
$ openssl genrsa -out serverkey.pem 2048
$ openssl req -new -key serverkey.pem -out server.csr
$ # Now we create a certificate using the CA
$ openssl x509 -req -in server.csr -CA cacert.pem -CAkey cakey.pem -CAcreateserial -out servercert.pem

Now, we need to copy these files to the corresponding folder:

  • The cacert.pem certificate will be copied to the /etc/pki/CA/ folder.
  • The servercert.pem certificate will be copied to the /etc/pki/libvirt/ folder.
  • The serverkey.pem private key will be copied to the /etc/pki/libvirt/private/ folder.

Remember that server certificates must be readable to QEMU processes. Adjust read access to those files.

When done, you need to enable TLS in the /etc/libvirt/libvirtd.conf file. By default, secure TLS connections are enabled by default, but you can add listen_tls = 1 to make configuration more readable. Restart libvirtd. Done.

To test this configuration, you need to create a new certificate for your client, and call one command using TLS transport in the hypervisor driver connection. We will use virsh for this test:

$ sudo virsh -c qemu+tls://localhost/system list --all
Id   Name   State
--------------------

The last step is to configure SASL. We need to add to the file /etc/libvirt/libvirtd.conf this two lines:

  • auth_tcp = "sasl" to enable SASL for tcp connections.
  • auth_tls = "sasl" to enable SASL for TLS connections.

Now we need to configure SASL users. Install the package sasl2-bin, from ubuntu you can:

$ sudo apt-get install sasl2-bin

Add a new user and restart the libvirtd. If you try to execute the last command to list domains in the host, the result will be:

$ sudo virsh -c qemu+tls://localhost/system list --all
error: failed to connect to the hypervisor
error: authentication failed: authentication failed

Create the file auth.conf in /etc/libvirt, and put the user and password created in the last step using saslpasswd2.

[credentials-sasl]
authname=
password=

[auth-libvirt-localhost]
credentials=sasl

Remeber restart libvirtd.

We go to write a Python script to test if all works correctly.

import libvirt

SASL_USER = "YOUR_USER"
SASL_PASS = "YOUR_PASSWORD"

def request_cred(credentials, user_data):
  for credential in credentials:
    if credential[0] == libvirt.VIR_CRED_AUTHNAME:
      credential[4] = SASL_USER
    elif credential[0] == libvirt.VIR_CRED_PASSPHRASE:
      credential[4] = SASL_PASS
  return 0

auth = [[libvirt.VIR_CRED_AUTHNAME, libvirt.VIR_CRED_PASSPHRASE], request_cred, None]
conn = libvirt.openAuth('qemu+tls://localhost/system', auth, 0)
print(conn.getVersion())
conn.close()

If you execute the script, then:

(libvirt) $ python virt.py
3001000

All works fine!.

Release resources

The conn object is of type libvirt.virConnect. All connections should be closed using the close() method:

>>> conn.close()
0
>>> conn.getVersion()
libvirt: Domain Config error : invalid connection pointer in virConnectGetVersion
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/javi/miniconda3/envs/libvirt/lib/python3.7/site-packages/libvirt.py", line 4016, in getVersion
    if ret == -1: raise libvirtError ('virConnectGetVersion() failed', conn=self)
libvirt.libvirtError: invalid connection pointer in virConnectGetVersion

As you can see, after closing the connection, no method calls are allowed.

Using this connection object you can define new virtual machines, list host domains, filter by name etc, but I will explain it in a new blog posts.