KVM/Qemu VMs with a multi-screen Spice console – VIII – VM and user specific restrictions for remote-viewer connections – iptables and sudo

The Spice console allows users to access the graphical desktop of a Qemu based virtual machines [VM]. The performance with both data encryption and data compression is excellent, audio is no problem and the required data transfer rates to client-applications as "remote-viewer" are within reasonable limits for a (switched) LAN. In virtualization scenarios where you can organize tasks according to a scheme "one user per VM" Spice is actually an attractive tool - and even professional virtualization environments a "Proxmoxx" and "oVirt" make use of it.

In this series we look at basic setups in self-administered Intranet environments. So far we had some ups and downs regarding the tool remote-viewer. If you want to use it in a desktop virtualization environment it is an almost perfect tool. We also found it to be a very convenient and efficient remote client-tool in an Intranet when we combined it with SSH and internal data compression of the Spice protocol. See:

KVM/Qemu VMs with a multi-screen Spice console – V – remote access via remote-viewer, a network port and a SSH-tunnel
KVM/Qemu VMs with a multi-screen Spice console – IV – remote access via SSH, remote-viewer and a Unix socket
KVM/Qemu VMs with a multi-screen Spice console – III – local access with remote-viewer via a Unix socket

SSH gave us all options we needed to take care of various security issues in remote access scenarios. The KVM/Qemu server could control the interaction of remote-users with VMs by applying user-specific SSH restrictions to port-forwarding. We could establish rules to bind the Spice access to a specific VM to a specific user.

Regarding the last point the combination of remote-viewer, TLS and SASL came as a solid disappointment. TLS worked perfectly. But we had problems with SASL; see:

KVM/Qemu VMs with a multi-screen Spice console – VII – remote-viewer, qemu and SASL authentication
KVM/Qemu VMs with a multi-screen Spice console – VI – remote access with remote-viewer and TLS encryption

We got SASL authentication working in two different ways. The frustrating result of our efforts, however, was that we could not confine the access to the Spice-console of a specific VM to exactly one user.

In the end we found that it might even be better to use TLS and a VM-specific Spice password set in the VM's XML-domain file. The problem then still is that users could share the password for a specific VM. So, we still could not ensure a scenario "one VM - one UID, only".

In this article we, therefore, look at measures on remote client-systems to allow a TLS connection to a specific VM on the KVM/Qemu host for exactly one user. We use two different Linux tools - namely iptables and sudo - to achieve this. The recipes given below are interesting in themselves and can be applied to other scenarios where the admin wants to restrict outgoing TCP-connection on a user specific level.

Why do we care about user-specific control for Spice at all?

Assume a situation in a small Linux oriented office where you run a bunch of VMs for special purposes on a central KVM-server.
You provide e.g. a VM (VM1) with Windows 10 ( 🙁 ) for book-keeping with a program your tax advisor understands. A user "Charlie" with a defined UID should use the VM's Spice console, but basically only he (and his boss in the evening hours).
You have a second VM (VM2) with a Web-server for development and test purposes. Only user "Maggie" shall have access to the Spice console of VM2 on Mondays and Tuesdays and a user "Anne" on Wednesdays and Thursdays and no one else.
And then there is yet another VM (VM3) which only user "Ralph" shall access via Spice to create documents of a confidential analysis for a governmental organization.
There are multiple Linux client PCs available in the office; any of the user can use and login to any free Linux workstations. None of the user has root rights on the clients and the server.

We have thus defined a scenario of the type

One VM + Spice console    < = >    one user or a group of selected users.

Can you cover such a situation with remote-viewer, TLS and server-based SASL, only?
The answer of my last article was: NO. At least not by simple means. (By the way: It is not the fault of SASL. Remote-viewer, simply and unfortunately, does not provide any system-based data for the realm or for the remote-system which SASL could evaluate. The user has too much freedom ...)

Do we need measures on the client-systems?

It is interesting that even Opensuse's "Virtualization Guide" writes about some required measures on the client (restricting access to specific client-certificates for selected users) when discussing the security for libvirt and vnc as tools to access the graphical desktop of a VM. We have not come to libvirt-based tools in this series, yet, but this is already some indication that restrictions of desktop access to KVM/Qemu based VMs may sometimes require measures on the client. So, let us turn to the client-systems ... Of course, we assume that such clients have a Linux OS, in my case a Opensuse Leap 15.2.

Schematic drawing

The following schematic drawing reflects the situation we want to control:
We want to make it impossible user "mybro" to access the Spice console of a VM1. Only "myself" should be able to use VM1's Spice console via a TLS connection. The other way round: Only user "mybro" shall be allowed to connect to the the Spice console of VM2 with remote-viewer. Cross-connections are forbidden; each user gets access to one defined VM only.

We use the same systems as in the last articles: A KVM/Qemu server host "MySRV" (IP: 192.168.2.2) with a Leap 15.2 OS on it, a test-VM1 "debianx" with a Kali-OS on it and a client-system "MyLAP" (a laptop with a Leap 15.2 OS; IP: 192.168.2.22). On MyLap we have a user "myself" and a user "mybro". VM1 ("debianx") gets the (TLS-) port 20002 on the server, and VM2 ("leaptest") the (TLS-) port 20004.

We shall perform the experiments in this article with a Qemu set up without SASL, but each VM configured for an individual password. (As we already know it will only means a small difference in the form of the authentication dialog. We will not be asked for a username.) The scenario can easily be changed to SASL authentication.

Strategies to allow Spice access to a VM's desktop only for a specific user

There are three major strategies we can follow:

Strategy 1: We could think about a specific TLS client certificate for a connection to a VM and to make it accessible only to the user in question. Such a strategy would require that client certificates are supported by Qemu and Spice.

Strategy 2: We could use iptables and add user-specific rules for outgoing data to certain TLS-ports on the server. As user-related rules can only be set for the "output chain" we cannot set any such user-specific filters for our problem on the server.

Strategy 3: We take the choice of the target port on the server away from the user as well as any free access to the remote-viewer command namely by changing file permissions, setting up special sudo rules and/or enforcing him/her to run a shell script which starts remote-viewer with a pre-defined target port for him/her.

The strategies (if working) can be combined. Even if the firewall of strategy 2 failed the user would only get access to a specific VM by restrictions of strategy 3. But all these things are a bit complicated and they all depend on the user not being able to get root rights, the firewall being set up at every system-startup and the sudo rules being tight. This is crucial; our KVM/Qemu server with TLS and SASL could only block connections from certain IPs, but not really users.

We could extend strategy 3 to a network namespace - but it won't help too much as we then would again need to apply routing and related firewall rules. Or to sudo rules. We could also think about using different networks for different VMs and thus a port-IP-VM-relation - but we would still depend on user-specific firewall rules on the clients.

Strategy 1: What about Spice and TLS client certificates?

Here I come with bad news:
If you activate the option "default_tls_x509_verify = 1" in "/etc/livirt/qemu.conf" it is simply ignored. One can guess it from a lack of required parameters in the qemu-command created by virt-manager. Even if and when you provide client certificates in the required directories on the server and the client. In contrast to VNC-settings there is no option like "spice_tls_x509_verify = 1" available. If you set it by yourself, it is silently ignored.

So, client-certificates will be of no help with Spice and remote-viewer. The situation my be different for the libvirt-dependent "virt-viewer" tool. But this is the topic of a future blog post.

Strategy 2: netfilter/iptables to the rescue

I assume that you have some tool in place on your Linux systems to configure your own iptables-rules. Professionals may write their own scripts. Independent of your interface to netfilter/iptables, it is obvious that working with firewall rules in a productive environment is a risky business. Therefore:

Disclaimer: I take no responsibility whatever for the consequences of the approach describes below and its application to your computers. The iptables-rules have to be tested carefully before making them productive and their setup on Linux hosts must be supervised by an expert.

In my network environments I still cling to the tool "fwbuilder" because it gives you a good graphical overview for most purposes and it allows for more complex configurations than e.g. frontends to firewalld or ufw. So, let me show you how to set up a basic set of user-specific iptables rules with fwbuilder and then have a closer look at the contents of the created statements of the firewall script.

fwbuilder allows you to set up "users" with a defined UID in the object catalog for "services":

This may seem strange; but the meaning is the following:

  1. Using a "user" as a "service" will trigger the creation of a rule for the OUTPUT chain which matches the user of outgoing connection packets against an owner with a defined UID and then triggers a reaction.
  2. To cover the reaction in fwbuilder we "branch off" into a specific rule-set for the user. On the iptables level this corresponds to a user-defined rule-chain as a reaction target.
  3. The filter-rules for data packets in the user-defined chain themselves then lead to certain final reactions in the sense of "allow" or "deny".

In pseudo-code: IF a user matches an UID THEN apply a set of filter rules with their own final reactions.

In Fwbuilder a set of filter-rules is usually put into an object called "policy". The following image shows some rules of the main "policy" on "MyLap":

You see that rules 4 and 5 include users "myself" and "mybro"; the rules branch off to policies "Spice_VM_1" and "Spice_VM_2", respectively.

Rule 6 blocks a whole bunch of TCP-ports used for VMs on the KVM/Qemu server for any other user than "myself" and "mybro".

Policy "Spice_VM_1" contains one rule, only:

This rule specifies a certain interface and the IP of the target host "mysrv". We allow access to port 20002 - corresponding to the TLS port defined for our test VM "debianx" (VM1). Something analogous holds for our second user "mybro". He gets access to another virtual machine "VM2" which we have bound to port 20004 on the KVM-server for external TLS connections.

Now, let us look at the iptables statements generated by fwbuilder:


    # ================ Table 'filter', automatic rules
    # accept established sessions
    $IPTABLES -A INPUT   -m state --state ESTABLISHED,RELATED -j ACCEPT 
    $IPTABLES -A OUTPUT  -m state --state ESTABLISHED,RELATED -j ACCEPT 
    $IPTABLES -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT 
    # backup ssh access from a admin host 
    $IPTABLES -A INPUT  -p tcp -m tcp  -s 192.168.44.12/255.255.255.255  --dport 22  -m state --state NEW,ESTABLISHED -j  ACCEPT 
    $IPTABLES -A OUTPUT  -p tcp -m tcp  -d 192.168.44.12/255.255.255.255  --sport 22  -m state --state ESTABLISHED,RELATED -j ACCEPT


    # ================ Table 'filter', rule set Spice_VM_1
    # 
    # Rule Spice_VM_1 0 (eth0)
    # 
    echo "Rule Spice_VM_1 0 (eth0)"
    # 
    $IPTABLES -N Spice_VM_1
    $IPTABLES -N Cid40091X30615.0
    $IPTABLES -A Spice_VM_1 -o eth0  -p tcp -m tcp  -d 192.168.2.2   --dport 20002  -m state --state NEW  -j Cid40091X30615.0
    $IPTABLES -A Cid40091X30615.0  -s 192.168.2.22   -j ACCEPT
    $IPTABLES -A Cid40091X30615.0  -s 192.168.2.22   -j ACCEPT
    
    # ================ Table 'filter', rule set Spice_VM_2
    # 
    # Rule Spice_VM_2 0 (eth0)
    # 
    echo "Rule Spice_VM_2 0 (eth0)"
    # 
    $IPTABLES -N Spice_VM_2
    $IPTABLES -N Cid40661X30615.0
    $IPTABLES -A Spice_VM_2 -o eth0  -p tcp -m tcp  -d 192.168.2.2   --dport 20004  -m state --state NEW  -j Cid40661X30615.0
    $IPTABLES -A Cid40661X30615.0  -s 192.168.2.22   -j ACCEPT
    $IPTABLES -A Cid40661X30615.0  -s 192.168.2.22   -j ACCEPT
    
    # ================ Table 'filter', rule set Main_Policy
    # 
    # Rule Main_Policy 0 (eth0)
    # 
    echo "Rule Main_Policy 0 (eth0)"
    # 
    # Antispoofing
    $IPTABLES -N In_Main_Policy_0
    $IPTABLES -A INPUT -i eth0   -s 192.168.2.22   -j In_Main_Policy_0
    $IPTABLES -A INPUT -i eth0   -s 192.168.4.22   -j In_Main_Policy_0
    $IPTABLES -A FORWARD -i eth0   -s 192.168.2.22   -j In_Main_Policy_0
    $IPTABLES -A FORWARD -i eth0   -s 192.168.4.22   -j In_Main_Policy_0
    $IPTABLES -A In_Main_Policy_0  -j LOG  --log-level info --log-prefix "RULE 0 -- DENY "
    $IPTABLES -A In_Main_Policy_0  -j DROP
    # 
    # Rule Main_Policy 1 (lo)
    # 
    echo "Rule Main_Policy 1 (lo)"
    # 
    $IPTABLES -A INPUT -i lo   -m state --state NEW  -j ACCEPT
    $IPTABLES -A OUTPUT -o lo   -m state --state NEW  -j ACCEPT
    # 
    # Rule Main_Policy 2 (global)
    # 
    echo "Rule Main_Policy 2 (global)"
    # 
    #  ICMP, DNS, DHCP 
    $IPTABLES -A OUTPUT -p icmp  -m icmp  --icmp-type 3  -m state --state NEW  -j ACCEPT
    $IPTABLES -A OUTPUT -p icmp  -m icmp  --icmp-type 0/0   -m state --state NEW  -j ACCEPT
    $IPTABLES -A OUTPUT -p icmp  -m icmp  --icmp-type 8/0   -m state --state NEW  -j ACCEPT
    $IPTABLES -A OUTPUT -p icmp  -m icmp  --icmp-type 11/0   -m state --state NEW  -j ACCEPT
    $IPTABLES -A OUTPUT -p icmp  -m icmp  --icmp-type 11/1   -m state --state NEW  -j ACCEPT
    $IPTABLES -A OUTPUT -p tcp -m tcp  --dport 53  -m state --state NEW  -j ACCEPT
    $IPTABLES -A OUTPUT -p udp -m udp  -m multiport  --dports 68,67,53  -m state --state NEW  -j ACCEPT
    # 
    # Rule Main_Policy 3 (global)
    # 
    echo "Rule Main_Policy 3 (global)"
    # 
    $IPTABLES -A OUTPUT -p udp -m udp  --dport 123  -m state --state NEW  -j ACCEPT
    # 
    # Rule Main_Policy 4 (global)
    # 
    echo "Rule Main_Policy 4 (global)"
    # 
    $IPTABLES -A OUTPUT -m owner --uid-owner 1021  -j Spice_VM_1
    # 
    # Rule Main_Policy 5 (global)
    # 
    echo "Rule Main_Policy 5 (global)"
    # 
    $IPTABLES -A OUTPUT -m owner --uid-owner 1022  -j Spice_VM_2
    # 
    # Rule Main_Policy 6 (global)
    # 
    echo "Rule Main_Policy 6 (global)"
    # 
    $IPTABLES -N Out_Main_Policy_6
    $IPTABLES -A OUTPUT -p tcp -m tcp  --dport 20001:20010  -j Out_Main_Policy_6
    $IPTABLES -A Out_Main_Policy_6  -j LOG  --log-level info --log-prefix "RULE 6 -- DENY "
    $IPTABLES -A Out_Main_Policy_6  -j DROP
    # 
...
...

 
These are simple rules which the experienced reader will have no difficulty to interpret. (The reader also sees that I have multiple IPs on eth0, but this does not affect our present topic).

I leave the test to you. You should see that user "myself" can access VM1 (debianx) whilst user "mybro" and any other users cannot. User "mybro" instead can access VM2 on the server.

Important note:

Do not forget to write a small systemd service which starts your firewall automatically during the startup of your client-system.

I have written about his topic somewhere else in this blog already. believe me its easy.

Strategy 3: Using sudo

Working with "sudo" and manipulating the file "/etc/sudoers" is somewhat risky. Therefore:

Disclaimer: I take no responsibility whatever for the consequences of the sudo approach describe below and its application to your computers. The sudoer rules have to be tested carefully before the are used in a production environment and their setup must be supervised by an expert.

The settings below work for an Opensuse Leap system - partially due to the default settings there.

A standard problem with sudoer rules for graphical applications is the handling of the access to the X11 display if you need to start programs as another user (e.g. root). (Things may be even worse with Wayland; but I have no experience with it). To keep things simple it is a worthwhile investment to think a bit about the precise nature of your sudo-objective.

In our case we want enforce a user-specific usage of the command remote-viewer. More precisely:

  1. We want to disallow the usage of remote-viewer for most users. And even selected users shall not be able to call or invoke remote-viewer directly and freely.
  2. We want to enforce user-specific arguments to the command "remote-viewer", if executed by certain selected users.
  3. If possible we do not want to run any remote-tool with root-rights at all.
  4. We want to keep our user-specific firewall rules in place and not to create new ones.
  5. X11-display access shall be possible.

The answer to this challenge is a bit tricky. It first looks like you need to write a separate script (accessible to root) which evaluates UIDs or SUD_UIDs and then calls remote-viewer with appropriate arguments.

But you will then be confronted with problems to access the X11-display of the user issuing the script. In addition rule 3 above forces you to start the required remote-viewer command in the end as a specific user via "sudo -u USER" or "su -c '...' USER". This in turn forces you to allow "USER" to execute the remote-viewer command anyway according to a specific sudo (!) rule for USER. Therefore, he/she must be able to read and execute the remote-viewer command. But then he/she could execute it outside freely without sudo - and again use any arguments he/she likes. It took me a while to find a way out.

The right approach is to find a rule working for the user in question, first, before you turn to a script as a mediator. On the other side: The user must NOT be allowed to use "remote-viewer" freely - neither by user or group access rights. This seeming contradiction is solved by the following steps on our client-system; we describe the rules below for user "myself". You must execute most of the commands as root:

  • Step 1: Create a special group, e.g. named "spicegrp". Do NOT make user "myself" a member of this group!
  • Step 2: Change the ownership and access rights of "/usr/bin/remote-viewer" according to
    • chown root.spicegrp /usr/bin/remote-viewer
    • chmod 750 /usr/bin/remote-viewer
  • Step 3: Check that remote-viewer can no longer be executed by user "myself".
  • Step 4: Start editing the file "/etc/sudoers" with visudo.
  • Step 5: Add the following lines and turn some existing lines into comments:
     
    ....
    Defaults env_reset
    Defaults env_keep = "LANG LC_ADDRESS LC_CTYPE LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE LC_ATIME LC_ALL LANGUAGE LINGUAS XDG_SESSION_COOKIE"
    
    Defaults:myself env_keep += "DISPLAY"
    
    #Defaults targetpw   # ask for the password of the target user i.e. root
    #ALL   ALL=(ALL) ALL
    
    myself ALL=(myself:spicegrp) /usr/bin/remote-viewer -- spice\://mysrv.anraconc.de?tls-port=20002
    # myself ALL=(myself:spicegrp) NOPASSWD: /usr/bin/remote-viewer -- spice\://mysrv.anraconc.de?tls-port=20002
    # mybro ALL=(mybro:spicegrp) NOPASSWD: /usr/bin/remote-viewer -- spice\://mysrv.anraconc.de?tls-port=20004
    
    

This is all we basically need for the user myself. An extension to user "mybro" should be clear. See the commented line for mybro.

The sudoer statements - also the commented ones - deserve some short explanation:
We reset the environment, but we keep some language settings for sudoer users. More important: We keep the "DISPLAY" variable of the environment of sudoer user "myself". Meaning, it will be available when commands are executed with his/her UID. Hereby, we avoid major X11 trouble. The two commented lines in the middle correspond to the the objective that sudoer users should provide their own passwords instead of the root password to execute the commands in the prescribed form.

And then comes some sudo vodoo:

We only define a rule for the user "myself": We allow him/her to execute the command remote-viewer with his own UID, but with a different group. This gives him access to "/usr/bin/remote-viewer" under "sudo" conditions ( only !). But, in no way else on a shell's command line, as he/her no longer has access rights to "/usr/bin/remote-viewer" and is no member of "spicegrp"!

The other part of the magic is that the sudoer-mechanism checks the precise form by which the user in question (here: myself) executes the command. It compares the command exactly to the form given in the rule line including the command's arguments!

myself ALL=(myself:spicegrp) /usr/bin/remote-viewer -- spice\://mysrv.anraconc.de?tls-port=20002

The command and the arguments to it are together handled as one string for comparison!
Thus we have a user "myself" who can only use remote-viewer with "sudo" and he/she is forced to provide a specific argument. And when he issues the command the firewall rules defined in the first part of this post should open doors to the VM as the execution is done with his/her UID!

What will the required sudo command for a successful access to the VM1 (debianx) at TLS-port 20002 on server MySRV look like:

sudo -u myself -g spicegrp remote-viewer -- spice://mysrv.anraconc.de?tls-port=20002

You took notice of the argument for the group?

Test for a sudoer user

Let us test our theory (sorry for the German system messages, tried to translate them):

myself@mylap:~> remote-viewer -- spice://mysrv.anraconc.de?tls-port=20002
-bash: /usr/bin/remote-viewer: Keine Berechtigung
myself@mylap:~> # Keine Berechtigung = No access right
myself@mylap:~> # Now with sudo - but wrong port 20004
myself@mylap:~> sudo -u myself -g spicegrp remote-viewer -- spice://mysrv.anraconc.de?tls-port=20004
[sudo] Passwort für myself: 
Leider darf der Benutzer myself »/usr/bin/remote-viewer -- spice://mysrv.anraconc.de?tls-port=20004« als myself:spicegrp auf mylap.anraconc.de nicht ausführen.
myself@mylap:~> # Translation: Sorry, but user myself is not allowed to execute »/usr/bin/remote-viewer -- spice://beta.rux.anraconb.de?tls-port=20004« as myself:spicegrp on mylap.anraconc.de.
myself@mylap:~> # Now with sudo - but the right port 20002
myself@mylap:~> sudo -u myself -g spicegrp remote-viewer -- spice://mysrv.anraconc.de?tls-port=20002

And now we get the familiar dialog to authenticate :

Our firewall obviously has let us through. And - after filling in the VM-specific password (we work without SASL here; see above), we get:

Good!
I leave it to the reader extend the whole thing to two users and to test all combinations out.

Making things a bit easier for the users

An authorized user has to type a lot of things to make the sudo command work. One thing, you can think about, is to not require a password for the sudo command. (Personally, I would not do this; a password is a last security barrier in case of configuration mistakes. But, in principle it is possible to work without a password in this specific case - after you have checked out and tested all security implications). The entry in the "/etc/sudoers" file then would look like

....
myself ALL=(myself:spicegrp) NOPASSWD: /usr/bin/remote-viewer -- spice\://mysrv.anraconc.de?tls-port=20002
...

Another reduction of typing work comes through a script which readers can read and execute, but not change. The script could make all the relevant decisions for a user. A very simplified version would in my test scenario contain statements like:

#!/bin/bash
host="myserv.anraconc.de"
myself_port="20002"
mybro_port="20004"
Myself_UID="1021"
Mybro_UID="1022"
if  test $SUDO_UID
then
        if test  $UID -ne $SUDO_UID
        then
                echo "error - you are not allowed to run this command as another user"
                exit
        fi
fi
if  test $UID -eq $Myself_UID
then
        echo "Hello Ralph"
        msg="You are entering the Spice console of VM \"debianx\" - happy working"
        echo $msg
        sudo -u "#$UID" -g spicegrp /usr/bin/remote-viewer -- spice://$host?tls-port=$myself_port
elif  test $UID -eq $Mybro_UID
then
        echo "Hello Brother"
        msg4="You are entering the Spice console of VM \"leaptest\" - happy working"
        echo $msg4
        sudo -u "#$UID" -g spicegrp /usr/bin/remote-viewer -- spice://$host?tls-port=$mybro_port
else
        echo "Sorry, you are not allowed to access VMs"
fi

Note that the script would ask for a password before executing the sudo statement enclosed - if you had defined a password request in the sudoer file.
If you absolutely wanted to obfuscate the information contained in this script you could again use the trick with sudo and the special group spicegrp. You would the add lines

myself ALL=(myself:spicegrp) /usr/bin/rviewer
myself ALL=(mybro:spicegrp) /usr/bin/rviewer

to the "/etc/sudoers"-file

Important note:

You must not forget to check the fie permission of the file "/usr/bin/remote-viewer" after SW-updates or upgrades of your system.

I would recommend to start a small scheduled job or a service to check the rights settings frequently.

Conclusion

Remote-viewer connections to VMs cannot be controlled on a user level by the KVM/Qemu server if we just used TLS and SASL. We can set up a VM specific password. But external connections to a VM specific TLS port can only be blocked for external systems and IPs on the server.
However, on Linux client-systems iptables helps us to allow access to the Spice console of a specific VM for a selected user, only. This can be achieved by setting up user specific iptables rules on client-systems. This post has shown you how to create such rules for the OUTPUT chain with fwbuilder. We must set up a systemd service to implement these rules automatically at system (and network) startup.

To restrict users on Linux client systems even more we applied the sudo mechanism in a very specific way: we enforced the usage of certain arguments to the remote-viewer command for specific users. I think the method I discussed is safe; if you find a caveat please send me a mail.

Both strategies can and should be applied in Intranets where we want to provide remote-viewer and the Spice consoles of Qemu VMs as real working instruments.

KVM/Qemu VMs with a multi-screen Spice console – II – local access with remote-viewer via a network port

In my first article in this series on local and remote connections to the Spice console of a KVM/Qemu virtual machine [VM] I have given you an overview over some major options. See the central drawing in

KVM/qemu with the graphical multi-screen Spice console – I – Overview over local and remote access methods

In this post we explore a part of the drawing's left side - namely a local connection with the remote-viewer client over a network-port and the "lo"-device. Local means that we access the Spice console with a Spice client in a desktop session on the KVM/Qemu host.

You can find some basic information on "remote-viewer" here.

Note: "remote-viewer" supports VNC, too, but we ignore this ability in this series.

A local scenario with remote-viewer on a KVM/Qemu host corresponds to desktop virtualization - though done with a remote tool. Such an approach can become a bit improper, because setting up ports for potential remote network connections always has security implications. This is one of the reasons why I actually want to discuss two different local access methods which can be realized with remote-viewer:

  • Method 1: Access via a network port (TCP socket). This standard method is well documented on the Internet both regarding the VM's setup (e.g. with virt-manager) and the usage of remote-viewer (see the man pages). The network based scenario is also depicted in my drawing.
  • Method 2: Access via a local Unix socket. This method is not documented so well - neither regarding the VM's setup (e.g. with virt-manager) and not at all by the man pages for remote-viewer.

You may complain now that I never indicated anything in the above drawing about a pure Unix socket! Well, at the time of writing the first article I did not even know that remote-viewer worked over a pure Unix socket and how one can configure the VM for a Unix socket. I will discuss the more common "Method 1" in this present article first. "Method 2" will be the subject of the next post - and then I will provide an update of the drawing. Talking about sockets: When you think a bit about it - Unix sockets must already be an ingredient of the local interaction of libvirt-tools with qemu, too, ....

Below I will discuss a set up for "Method 1" which is totally insecure. But it can be realized in a very simple way and is directly supported by virt-manager for the VM setup. Thus, it will give us a first working example without major efforts for creating TLS certificates. But, you are/were warned:

Disclaimer:
The test-configurations I discuss in this and the next articles of this series may lead to major security risks. Such configurations should NOT be used in productive multi-user and/or network environments without carefully crafted protection methods (outside the qemu-configuration) - as local firewall protection, system policies, etc. I take no responsibility whatever for an improper usage of the ideas presented in this article series.

Assumptions for test scenarios

  • The KVM/Qemu host in this article series is an Opensuse Leap 15.2 host. Most of the prescriptions discussed may work with some minor modifications also on other Linux distributions. But you must look up documentations and manuals ...
  • I assume that you already have set up a KVM/Qemu based test-VM with virsh or virt-manager. Let us call the VM "debianx". Actually, it is a Kali system in my case. (Most of the discussed measures will work on guests with a Debian OS, too.)
  • On our Opensuse KVM host we find a XML-definition file "debianx.xml" for a libvirt domain (i.e. a VM) below the directory "/etc/libvirt/qemu/", i.e. "/etc/libvirt/qemu/debianx.xml". It has a typical XML structure and can be edited directly if required.
  • The KVM host itself has a FQDN of "MySRV.mydomain.de". (MySRV is partiall writtenin big letters to remind you that you have to replace it by your own domain.
  • The user on the KVM host, who invokes remote-viewer to access the VM, has a name "uvma". We will later add him to a group "libvirt" which gets access rights to libvirt tools. We shall also work with a different user "uvmb" who is a member of the group "users", only. Both users work on a graphical desktop on the KVM/Qemu host (!) - let us say a KDE desktop. These users open one or more Spice client windows on the host's desktop to display graphical desktops of the KVM/Qemu guest VMs - as if the windows were screens of the VM.

We must carefully distinguish between the (KDE) desktop used by "uvma" on the KVM host and the desktop of the guest VM:

The Spice client windows on the KVM host's (or later on independent remote systems) desktop present full graphical desktops (Gnome, XFCE, KDE ...) of the VM and allow for interactive working with the VM. The desktop used on the host can, of course, be of a different type than the one used on the guest.

I will use this first practical article also

  • to have a brief look at two basic configuration files for libvirtd and qemu settings,
  • to discuss some basic setup options for a Qemu VM,
  • to check the multi-screen ability of remote-viewer
  • and to prove the "one-seat" situation for the Spice console.

I restrict my hints regarding the guest configuration to Kali/Debian or Opensuse Leap guest systems on the VM. I provide images for a Kali guest system, only. I do not have time for more ...

Make backups

Before you start any experiments you should make a backup of the directories "/etc/libvirt" and "/etc/pki" of your host - and in doubt also of the disk-files (or real partitions) of your existing VMs. You should also experiment with a non-productive test VM, only.

Basic security considerations

(In-) Security is always a bit relative. Let us consider briefly, what we intend to do with respect to "Method 1":

With the first method we define a network port for unencrypted connections without authentication.

In principle the port can be used by anyone having access to your network or anyone present on your PC. Such a configuration is therefore only reasonable for desktop virtualization

  • if you really are alone on your PC
  • AND if you have a local firewall preventing access to the port from any other system AND if there is a local policy restricting access to the Qemu-hypervisor or Spice-clients.

Otherwise it opens a much too big attack surface. Remember: Anyone being able to use the network port can kick you off your Spice console session seat, remotely, locally and also when you are already logged into the guest OS. And:

All data transfer over any kind of network device (lo, ethernet, virtual devices on the host, ...) occurs unencrypted. Other users on your machine (legal users, e.g. active via ssh, or attackers) may with some privilege escalation be able to read it.

Access to the Spice console itself can, however, be restricted to users who know a password. At the end of this article we will add such a simple password to our configuration to get at least some basic protection against kicking you out of a Spice console session.

Preparational step 1: Settings in "/etc/libvirt/qemu.conf" and the integration of Spice with Qemu

As mentioned in the last article, Spice is fully integrated with the Qemu-emulator. So, it will not surprise you that we can modify some general properties of connections to a VM's Spice console by parameter settings in a configuration file for Qemu. The relevant file in the libvirt-dominated virtualization environment of Opensuse Leap 15.2 is

"/etc/libvirt/qemu.conf".

Readers of my last article may ask now: Why do we need to configure the Qemu-driver of libvirt at all, when we access the Spice console with remote-viewer directly via Qemu ? I.e., without passing a libvirt-layer? As depicted in the drawing?

Answer: Probably you and me start KVM/Qemu based VMs through "virsh" or "virt-manager", i.e. libvirt components. Then libvirt drivers which are responsible to start the VMs according to its setting profile must know what we expect from the Qemu emulator. If you worked directly with the qemu-emulator (by calling "/usr/bin/qemu-x86_64") you would have to specify a lot of options to control the outcome! Some sections below you will find an example. Libvirt-programs only automate this process.

"qemu.conf" allows for general default settings of a variety of qemu-parameters. Some of them can, however, be overwritten by VM-specific settings; see the next section. When you scroll through the file you come to a major section which contains Spice parameters. For an easy beginning of our experiments we set some selected parameters to the following values:

 
#spice_listen = "0.0.0.0" # just leave it as it is 
# .... 
spice_tls = 0  # This corresponds to a deactivation of TLS and a INSECURE configuration!
spice_tls_x509_cert_dir = "/etc/pki/libvirt-spice"
# .... 
# just leave the next settings as it is - it creates a socket at  "/var/lib/libvirt/qemu/domain-1-debianx/monitor.sock"
#spice_auto_unix_socket = 1  
spice_sasl = 0

Our first setting deactivates TLS. This is reasonable as long a we have not created valid TLS server certificates and encryption keys. But we can already define a default directory into which we later place such a certificate and key files. We also deactivate any SASL authentication support for the time being. (Actually, even if you left it at the default value of "1" it would not help you much - as on Opensuse systems a required file "/etc/sasl2/qemu.conf" is missing, yet. As standard SASL encryption methods are insecure today, SASL activation makes sense in combination with TLS only.

General security precautions in "/etc/libvirt/qemu.conf"
Talking about security: On an Opensuse system with activated "apparmor" you could/should set the following parameters in "/etc/libvirt/qemu.conf":

 
security_driver = "apparmor"
security_default_confined = 1

to confine VMs.

Note: If "apparmor" is deactivated for some reason you will after these settings not be able to use virt-manager or virsh. You can check the status of apparmor via "rcapparmor status" or systemctl status apparmor".
(Off topic: Those interested in process separation should also have a look at cgroup-settings for the VMs. I would not change the basic settings, if you do not know exactly what you are doing. But folks trying to experiment with "virgl3D" may need to add "/dev/dri/renderD128" to the device list for ACLs.)

Preparational step A: A brief look a "/etc/libvirt/libvirtd.conf" - allow a selected standard user to use virt-manger or virsh

As we are within the folder "/etc/libvirt", we take the chance to very briefly look at another configuration file, which will become more interesting in later articles:

/etc/libvirt/libvirtd.conf

Among other things this file contains a variety of parameters which control the access to libvirt tools via Unix sockets or TCP-sockets - with and without TLS and/or some form of authentication. This is of no direct importance for what we presently are doing with remote-viewer - as we access Spice console directly via qemu, i.e. without a libvirt-layer. However, for convenience reasons you should nevertheless be able to use virt-manager or virsh. If you do not want to work as root all the time you, i.e. he normal user "uvma", need(s) special access rights to access Unix sockets which are automatically opened by libvirtd. How can this be achieved on an Opensuse Leap system?

You can configure a special user-group (e.g. a group named "libvirt") to get access to virt-manager. You can do this in a section named "UNIX socket access controls". The instructions in Opensuse's documentation on virtualization (see: Connections and authorization and section 9.1.1.1 therein) will help you with this. Afterwards you must of course add your selected and trusted user (here: "uvma") to the defined group.

Note: The libvirtd-process is nevertheless run by the user "root" and not with the rights of the standard user (here "uvma"). The group just allows for a kind of sudo execution. Note also that the central sockets by which libvirt-clients connect to the libvirtd-daemon are created by systemd.

By the way - who is the user for qemu-processes on an Opensuse Leap system?
On an Opensuse there is a special user "qemu" which is used to run the Qemu-emulator processes for VMs. We also find a related group "qemu". User and group have special rights regarding aes-keys for potential credential encryption related to a VM. The user and its group also define the access right to special Unix sockets generated directly by the Qemu emulator and not by libvirtd. We shall request such a Unix socket from Qemu in the next article.

On other operative systems you may find a different pre-configured group for libvirt access rights. In addition the standard qemu-user may have a different name. For Debian and derivatives you find related information at the following links:
whats-the-purpose-of-kvm-libvirt-and-libvirt-qemu-groups-in-linux,
install-configure-kvm-debian-10-buster,
libvirt_qemu_kvm_debian .

Preparational step B: Restart libvirtd or reload its configuration after changes to qemu.conf, livirtd.conf or changes to XML-definition files for VMs

Changes of the file "/etc/libvirt/qemu.conf" or "/etc/libvirt/libvird.conf" require that the libvirtd daemon is restarted or forced to reload its configuration. The same is true for any changes of XML-definition files for VMs located in "/etc/lbvirt/qemu/". (We will change such files later on). According to the man page sending a SIGHUP signal to the daemon will enforce a reload of the configuration. You can indirectly achieve this by the commands

rclibvirtd reload [on Opensuse systems)
or
systemctl reload libvirtd [any system with systemd]

But: Any libvirt or qemu changes will NOT affect already running VMs. You have to stop them, then restart libvirtd, reload the daemon's configuration and restart the VMs again.

Preparational step C: Configure the VM to use a Spice-console - and some other devices

Ok, let us prepare our VM for "Method 1". As we are in a libvirt environment we could use virt-manager to perform the required Spice configuration (see the image in the first article) . But we can also directly edit the XML configuration file "/etc/libvirt/qemu/debianx.xml" - assuming that you already have a working configuration for your VM. You need to be root to edit the files directly. See the restrictive access rights of the domain definition files

-rw------- 1 root root   4758 Mar  8 13:33 debianx.xml

By the way: A detailed description of possible parameter settings is provided at https://libvirt.org/formatdomain.html. Search for Spice and qxl .....

Regarding "Method 1" I use the following settings. The listing below only gives you an excerpt of the XML domain file, but it contains the particularly relevant settings for "graphics" (can be looked upon as a display device) and "video" (an be interpreted as a kind of virtual graphics card):

Excerpts from a libvirt domain definition XML file

   
   <interface type='network'>
      <mac address='52:54:00:93:38:4c'/>
      <source network='os'/>
      <model type='virtio'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
    </interface>
   ...
   <graphics type='spice' port='20001' autoport='no' listen='0.0.0.0' keymap='de' >    
      <listen type='address' address='0.0.0.0'/>
      <image compression='off'/>
    </graphics>
   ... 
    <sound model='ich6'>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
    </sound>
    ...
    <video>
      <model type='qxl' ram='262144' vram='65536' vram64='2097152' vgamem='65536' heads='4' primary='yes'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
    </video>
...

Parameters which affect general qemu-related properties (as e.g. the "listen"-settings) will overwrite the settings in "qemu.conf". The choice of the PCI slot numbers may be different for your guest.

You see that I defined a network (!) port for the VM by which we can access the Spice console data stream directly. I have changed this port from the default "5900" to "20001".

You should be aware of the fact that this sets a real network port on the KVM host - i.e. a network stack is invoked when accessing the Spice console. As indicated above we can avoid the superfluous network operations by directly working with a Unix socket (Method 2), but this is the topic of the next forthcoming article.

Regarding network port definitions for a Spice console three rules apply:

  • Each of the VMs defined requires its own individual port !
  • Firewalls between the client and the VM have to be opened for this port. This includes local firewalls on the VM, on the KVM host (with rules that may apply to virtual bridges, on the client system and on any network components in between.
  • A TLS port has later to be configured in addition if you deactivated the "autoport" functionality - as we have done in the example above. I shall describe how to set the TLS port in a forthcoming article.)

The individual port definitions already imply the provision of a URI with port specification when starting remote-viewer (see below).

Let me briefly comment on the QXL-settings:

The QXL configuration shown above allows for 4 so called "heads" (or connectors) of the virtual graphics device. These heads support up to 4 virtual screens, if requested by the Spice-client. Note that the situation is different from real hardware consoles:
The (Spice) client (here remote-viewer) defines the number of screens attached to the VM's console by a number of Spice client windows opened on the user's desktop on the KVM host. The dimensions of these Spice client windows determine the dimensional capabilities of the virtual "screens".

How the virtual screen resolution is handled afterwards may depend on the user's own desktop resolution (on the KVM host or a remote system) AND the display settings of the VM's guest system's own tools for screen management. Today these are typically tools integrated into the guest's graphical desktop (KDE, Gnome, XFCE) itself. Present Gnome and KDE5 desktops on the VM's guest Linux system automatically adapt to the virtual display dimensions - i.e. the Spice window dimensions. But a XFCE desktop on the guest may behave differently; see below.

A short note also on the network settings for our test VM:
A virtual machine will normally be associated with a certain type of virtual network. At least if you want to use it for the provision of services or other network or Internet dependent tasks. You can configure virtual networks with virt-manager (menu "edit" >> "connection details"); but you must be root to do this:

In my case I prefer a separate virtual network for which we need routing on the KVM host. This allows us to configure restrictive firewall rules - e.g, with netfilter on the host. It even allows for the setup of virtual VLANs - a topic which I have extensively covered in other article series in this blog. And when required we just can stop routing on the host.

Guest requirements
Note that the QXL-configuration must also be supported on the VM guest itself: You need a qxl-video-driver there - and it is useful to also have the so called spice-vdagent active (see the my referenced series about a qxl-setup in the last article). On the guest system this requires

  • the installation of the "spice-vdagent" package and the activation of the "spice-vdagent.service" (both on Debian/Kali and Opensuse guest systems) ,
  • the installation of the packet "xserver-xorg-video-qxl" on a Debian/Kali-guest or "xf86-video-qxl" on an Opensuse guest

Present Debian, Kali or Opensuse operative system on the KVM/Qemu guest VM should afterward automatically recognize the QXL-video-device and load the correct driver (via systemd/udev).
Regarding the (virtual) network device on the VM itself you must use the guest system's tools for the configuration of NICs and of routes.

Preparational step D: Starting the VM - and "Where do I find logs for my VM"?

I assume that you as user "uvma" are logged in to a graphical desktop session on your KVM host "MySRV". If you are not yet a member of the libvirt-privileged group, you may need to use sudo for the next step. On a terminal window you then enter "virt-manager &", select our VM "debianx" and boot it.

If you look at the process list of your KVM host you may find something like:

qemu     17662  5.5  6.8 15477660 4486180 ?    Sl   18:11   2:29 /usr/bin/qemu-system-x86_64 -machine accel=kvm -name guest=debianx,debug-threads=on -S -object secret,id=masterKey0,format=raw,file=/var/lib/libvirt/qemu/domain-1-debianx/master-key.aes -machine pc-i440fx-2.9,accel=kvm,usb=off,vmport=off,dump-guest-core=off -cpu Skylake-Client -m 8192 -overcommit mem-lock=off -smp 3,sockets=3,cores=1,threads=1 -uuid 789498d2-e025-4f8e-b255-5a3ac0f9c965 -no-user-config -nodefaults -chardev socket,id=charmonitor,fd=33,server,nowait -mon chardev=charmonitor,id=monitor,mode=control -rtc base=utc,driftfix=slew -global kvm-pit.lost_tick_policy=delay -no-hpet -no-shutdown -global PIIX4_PM.disable_s3=1 -global PIIX4_PM.disable_s4=1 -boot strict=on -device ich9-usb-ehci1,id=usb,bus=pci.0,addr=0x5.0x7 -device ich9-usb-uhci1,masterbus=usb.0,firstport=0,bus=pci.0,multifunction=on,addr=0x5 -device ich9-usb-uhci2,masterbus=usb.0,firstport=2,bus=pci.0,addr=0x5.0x1 -device ich9-usb-uhci3,masterbus=usb.0,firstport=4,bus=pci.0,addr=0x5.0x2 -device virtio-serial-pci,id=virtio-serial0,bus=pci.0,addr=0x6 -blockdev {"driver":"file","filename":"/virt/debs/debianx.qcow2","node-name":"libvirt-2-storage","cache":{"direct":true,"no-flush":false},"auto-read-only":true,"discard":"unmap"} -blockdev {"node-name":"libvirt-2-format","read-only":false,"cache":{"direct":true,"no-flush":false},"driver":"qcow2","file":"libvirt-2-storage","backing":null} -device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x7,drive=libvirt-2-format,id=virtio-disk0,bootindex=1,write-cache=on -device ide-cd,bus=ide.0,unit=0,id=ide0-0-0 -netdev tap,fd=35,id=hostnet0,vhost=on,vhostfd=36 -device virtio-net-pci,netdev=hostnet0,id=net0,mac=52:54:00:93:38:4c,bus=pci.0,addr=0x3 -chardev pty,id=charserial0 -device isa-serial,chardev=charserial0,id=serial0 -chardev socket,id=charchannel0,fd=37,server,nowait -device virtserialport,bus=virtio-serial0.0,nr=1,chardev=charchannel0,id=channel0,name=org.qemu.guest_agent.0 -chardev spicevmc,id=charchannel1,name=vdagent -device virtserialport,bus=virtio-serial0.0,nr=2,chardev=charchannel1,id=channel1,name=com.redhat.spice.0 -device usb-tablet,id=input0,bus=usb.0,port=1 -spice port=20001,addr=0.0.0.0,disable-ticketing,plaintext-channel=default,image-compression=off,seamless-migration=on -k de -device qxl-vga,id=video0,ram_size=268435456,vram_size=67108864,vram64_size_mb=2048,vgamem_mb=64,max_outputs=4,bus=pci.0,addr=0x2 -device intel-hda,id=sound0,bus=pci.0,addr=0x4 -device hda-duplex,id=sound0-codec0,bus=sound0.0,cad=0 -chardev spicevmc,id=charredir0,name=usbredir -device usb-redir,chardev=charredir0,id=redir0,bus=usb.0,port=2 -chardev spicevmc,id=charredir1,name=usbredir -device usb-redir,chardev=charredir1,id=redir1,bus=usb.0,port=3 -device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x8 -object rng-random,id=objrng0,filename=/dev/urandom -device virtio-rng-pci,rng=objrng0,id=rng0,bus=pci.0,addr=0x9 -sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny -msg timestamp=on

 
You see that the user "qemu" started the qemu-process by calling "/usr/bin/qemu-system-x86_64" and adding a bunch of parameters. You may identify some parameters which stem from the settings in libvirt's XML definition file for our special the VM (or "domain").

Afterwards, have a look at connections with netstat on the host; you should see that something (qemu) listens on the defined Spice port of the VM:

MySRV:/etc/libvirt/qemu # netstat -a -n | grep 20001
tcp        0      0 0.0.0.0:20001           0.0.0.0:*               LISTEN
...

Regarding logs of the VM for error analysis: You will find them in the directory " /var/log/libvirt/qemu" (on an Opensuse Leap system).

Method 1: Local access to the Spice console with remote-viewer

Now, we are prepared to test a local access to the Spice console with remote-viewer. (As we work via a network port you may still need to open the Spice port on a local firewall - it depends of how you configured your firewall.) The format of the URI we have to specify for remote-viewer is

spice://FQDN_OR_IP:PORT_NR

with

  • FQDN_OR_IP: You have to present a host address which can be resolved by DNS or a directly an IP-address.
  • PORT_NR: The port for the Spice service - here "20001".

For our setup this translates to

spice://localhost:20001

and thus the command

remote-viewer spice://localhost:20001

As user "uvma" we enter this command in another terminal window of our graphical desktop on the KVM host :

uvma@MySrv:~> remote-viewer spice://localhost:20001

(remote-viewer:22866): GSpice-WARNING **: 12:43:58.150: Warning no automount-inhibiting implementation available

Just ignore the warning. Then you should get a window with a graphical display of some login-screen on the VM. Details depend of course on the configuration of your VM's operative system. In my case, I get the "gdm3"-login-screen of my virtualized Kali system:

The desktop-manager may be a different one on your system. After a login to a Gnome desktop I use remote-viewer's menu to open a 2nd screen:

and get two resizeable screen-windows :

As expected! When we change the dimensions the desktop of the VM adapts to the new virtual screen sizes. Just try it out.

Will this work with a KDE desktop on the Kali guest, too? Yes, it does. KDE5 and Gnome3 can very well coexist on a Kali, Debian or Opensuse Leap15.2 (guest) system! For a Kali system you find information how to install multiple graphical desktop environments e.g. at the following links:
install-kali-linux,
kde-environment-configuring-in-kali,
kali switching-desktop-environments.

The next image shows the transparency effect of a moving terminal window on a KDE desktop of the same Kali VM - again with 2 Spice screens requested by remote-viewer:

Such effects, which can also be configured on Gnome, require an active compositor on the guest's desktop. At least when working locally on a KVM host I never got any problems with the performance of QXL/Spice and remote-viewer with an active compositor on the guest's Gnome, KDE, and XFCE-desktops (without OpenGL acceleration). This is a bit different on a remote system and using remote-viewer over a LAN connection; see the next articles.

KDE and Gnome desktops on the VM automatically adapt to the sizes of the virtual screens. XFCE does not ... XFCE is the present default desktop on Kali; so I installed after a distribution upgrade. Here some remote-viewer hints for XFCE fans on a VM with Kali/Debian and XFCE:

  • Set the number of screens in remote-viewer to 1. After installing XFCE with " sudo apt update && sudo apt install -y kali-desktop-xfce" and a reboot we can choose "XFCE-Session" on a button of the gdm3-screen (after having entered the username). It will the boot into a XFCE session.
  • Then with the setting tool you can configure fixed resolutions for the screen displays (see below).
  • Or: Just deactivate a virtual display, resize the respective window on your host's desktop and reactivate the virtual display again in your XFCE guest. XFCE then automatically recognizes the new screen size and adapts.
  • When logging out choose an option to save the desktop.

Unfortunately, XFCE has no seamless coexistence with KDE or Gome on Kali, yet. The following sequence of steps may fail:
1) Log out of a XFCE desktop-session, 2) choose a KDE or Gnome session afterwards /(this works without rebooting via the gdm menu), 3) log out of KDE/Gnome again and 4) try to restart a XFCE desktop.
On my Kali guest (on an Opensuse KVM host) I always have to reboot the guest to get a working XFCE session again, even if I did not change the screen dimensions during the KDE/Gnome session .. 🙁

Netstat reveals multiple connections corresponding to Spice channels

Using remote-viewer with a netowrk port is a really simple exercise. Most of the text above was spend on looking at the general virtualization environment. But, actually, the situation behind the scenes is not so simple. When we use netstat again, we get the following:

  
MySRV:/etc/libvirt/qemu # netstat -a -n | grep 20001
tcp        0      0 0.0.0.0:20001           0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.1:20001         127.0.0.1:46738         ESTABLISHED
tcp        0      0 127.0.0.1:20001         127.0.0.1:46750         ESTABLISHED
tcp        0      0 127.0.0.1:46734         127.0.0.1:20001         ESTABLISHED
tcp        0      0 127.0.0.1:46750         127.0.0.1:20001         ESTABLISHED
tcp        0      0 127.0.0.1:20001         127.0.0.1:46742         ESTABLISHED
tcp        0      0 127.0.0.1:46738         127.0.0.1:20001         ESTABLISHED
tcp        0      0 127.0.0.1:46740         127.0.0.1:20001         ESTABLISHED
tcp        0      0 127.0.0.1:46748         127.0.0.1:20001         ESTABLISHED
tcp        0      0 127.0.0.1:20001         127.0.0.1:46740         ESTABLISHED
tcp        0      0 127.0.0.1:20001         127.0.0.1:46734         ESTABLISHED
tcp        0      0 127.0.0.1:20001         127.0.0.1:46728         ESTABLISHED
tcp        0      0 127.0.0.1:20001         127.0.0.1:46744         ESTABLISHED
tcp        0      0 127.0.0.1:46728         127.0.0.1:20001         ESTABLISHED
tcp        0      0 127.0.0.1:46742         127.0.0.1:20001         ESTABLISHED
tcp        0      0 127.0.0.1:20001         127.0.0.1:46748         ESTABLISHED
tcp        0      0 127.0.0.1:46744         127.0.0.1:20001         ESTABLISHED

We find many, namely eight (8), active established connections. What do these connections correspond to? Well, they correspond to individual data exchange channels; I quote from
https://libvirt.org/formatdomain.html#video-devices:

"When SPICE has both a normal and TLS secured TCP port configured, it can be desirable to restrict what channels can be run on each port. This is achieved by adding one or more elements inside the main element and setting the mode attribute to either secure or insecure. Setting the mode attribute overrides the default value as set by the defaultMode attribute. (Note that specifying any as mode discards the entry as the channel would inherit the default mode anyways.) Valid channel names include main, display, inputs, cursor, playback, record (all since 0.8.6 ); smartcard ( since 0.8.8 ); and usbredir ( since 0.9.12 )."

See also: spice_for_newbies.pdf

Theoretically, one should be able to use remote-viewer itself to activate or deactivate channels by setting up a "target file" for the connection; see the man-pages for more details. But on my machines it does not work neither locally not remotely. 🙁 I did not look into the details of this problem, yet ....

Testing the "one seat situation" at a Spice console

Now its time to test what I claimed in the last article: The Spice console can be accessed only by one user at a time. Let us assume, that user "uvma" is a member of the libvirt-group. Let us in addition create another user "uvmb" which is member of the group "users" on the host, only. I assume that "uvma" has opened a graphical desktop session on the KVM host (MySRV). E.g. a KDE session.

Step 1: As user "uvma" start virt-manager and boot your VM "debianx". On a terminal window (A) use remote-viewer to open the Spice console of the VM with 2 screens. Log into a graphical desktop of the VM.

Terminal A:

uvma@MySRV:~> remote-viewer -v  spice://localhost:20001
Guest (null) has a spice display
Opening connection to display at spice://localhost:20001

(remote-viewer:20735): GSpice-WARNING **: 18:21:31.344: Warning no automount-inhibiting implementation available

Step 2: On your host-desktop open another terminal window (N) and login via "su - uvmb" as the other user. As "uvmb" now enter "remote-viewer spice://localhost:20001".

uvma@MySRV:~> su - uvmb
Passwort: 
uvmb@MySRV:~> remote-viewer -v  spice://localhost:20001

This will at once close the open two Spice windows and after a blink of a second open up one or two new Spice windows again. The effect is clearly visible.

On terminal A we get:

Guest debianx display has disconnected, shutting down
uvma@MySRV:~> 

On terminal B we get a lot of warnings - but still the spice windows open in exactly the same status as we left them.

uvmb@MySRV:~> remote-viewer -v  spice://localhost:20001

(remote-viewer:20903): dbind-WARNING **: 18:25:40.853: Couldn't register with accessibility bus: Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken.
Guest (null) has a spice display
Opening connection to display at spice://localhost:20001

(remote-viewer:20903): GSpice-WARNING **: 18:25:40.936: PulseAudio context failed Verbindung verweigert

(remote-viewer:20903): GSpice-WARNING **: 18:25:40.936: pa_context_connect() failed: Verbindung verweigert
Cannot connect to server socket err = Datei oder Verzeichnis nicht gefunden
Cannot connect to server request channel
jack server is not running or cannot be started
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
AL lib: (EE) ALCplaybackAlsa_open: Could not open playback device 'default': Keine Berechtigung

(remote-viewer:20903): GLib-GObject-WARNING **: 18:25:40.962: g_object_get_is_valid_property: object class 'GstAutoAudioSink' has no property named 'volume'
AL lib: (EE) ALCplaybackAlsa_open: Could not open playback device 'default': Keine Berechtigung

(remote-viewer:20903): GLib-GObject-WARNING **: 18:25:40.964: g_object_get_is_valid_property: object class 'GstAutoAudioSrc' has no property named 'volume'
AL lib: (EE) ALCplaybackAlsa_open: Could not open playback device 'default': Keine Berechtigung
AL lib: (EE) ALCcaptureAlsa_open: Could not open capture device 'default': Keine Berechtigung

(remote-viewer:20903): GSpice-WARNING **: 18:25:41.015: record: ignoring volume change on audiosrc

(remote-viewer:20903): GSpice-WARNING **: 18:25:41.018: record: ignoring mute change on audiosrc

(remote-viewer:20903): GSpice-WARNING **: 18:25:41.021: Warning no automount-inhibiting implementation available

(remote-viewer:20903): GSpice-WARNING **: 18:25:41.052: playback: ignoring volume change on audiosink

(remote-viewer:20903): GSpice-WARNING **: 18:25:41.052: playback: ignoring mute change on audiosink

 
The warnings are mainly due to the fact that user "uvmb" has no rights to access the Pulseaudio context of the desktop of user "uvma". (I take care of Pulseaudio in one of the next articles ...) At the first switch between the users you may only get one Spice window as "uvmb". Choose two screens then. Afterwards you can switch multiple times between the users uvma and uvmb. Two windows get closed for the user with present Spice console session, two windows are opened for the requesting user, and so on.

Conclusion: "uvmb" and "uvma" can steal each other the Spice console with remote-viewer as and when they like.

By the way: A Spice console can be left regularly by a user by just closing the Spice client window(s). Note that this will not change the status of your desktop session on the host! This is left open - with all consequences.

Restricting access by setting a password

Let us prevent being thrown of the "seat" in front of our Spice console. As root change the <graphics> definition in VM's definition file to

...
 <graphics type='spice' port='20001' autoport='no' listen='0.0.0.0' keymap='de' passwd='spicemeandonlyme' defaultMode='insecure'>
      <listen type='address' address='0.0.0.0'/>
      <image compression='off'/>
      <gl enable='no'/>
    </graphics>
   ... 

If you haven't noticed: The relevant difference is the "passwd" attribute. You could have added this setting via virt-manager, too. Try it out.

Shut down and restart your VM. Any user that now wants to access the Spice console using "remote-viewer spice://localhost:20001" will be asked for a password. You yourself, too, if you close the console and try to reopen it.

Regarding security: The password transfer will to my understanding be AES encrypted. See Daniel P. Berrangé's blog https://www.berrange.com/posts/2016/04/01/improving-qemu-security-part-3-securely-passing-in-credentials/. But as long as the rest of the data exchange with the VM is not encrypted, this precaution measure won't help too much against ambitioned attackers.

Conclusion

Using remote-viewer locally on a KVM host in combination with a network port to access a VM's graphical Spice console is easy and convenient. It requires a minimum of settings. In addition it offers up to four virtual screens which you can open via a menu.
However, using a network port implies security risks. You should think about proper counter-measures first if you want to lean on "remote-viewer". Once configured in the way described above there is no libvirt-layer or anything else which may hinder ambitious attackers to analyze the unencrypted data flow between the Spice client and the VM through the host's network port.

In the next article

KVM/Qemu VMs with a multi-screen Spice console – III – local access with remote-viewer via a Unix socket

I want to show you how one can configure the VM such that remote-viewer can access the Spice console via a local Unix port. A network port is not opened in this approach. Security is none the less hampered a bit as we have to give a standard user some rights which normally only the "qemu" group has. But we shall take care of this aspect by using ACLs.

Links

Virtualization on Opensuse
https://doc.opensuse.org/ documentation/ leap/ virtualization/ single-html/ book.virt/index.html

Libvirtd daemon
https://www.systutorials.com/docs/linux/man/8-libvirtd/

Libvirt connection URIs
https://libvirt.org/uri.html

Find the Spice URI to use
https://access.redhat.com/ documentation/ en-us/ red_hat_enterprise_linux/7/ html/ virtualization_deployment_and_ administration_guide/ sect-Domain_Commands- Displaying_a_URI_ for_connection_to_a_graphical_display

TLS disabled
https://unix.stackexchange.com/ questions/ 148794/ how-to-create-kvm-guest-with-spice-graphics-but-tls-disabled-using-virt-install

General Kali installation
https://www.cyberpratibha.com/blog/how-to-update-and-upgrade-kali-linux-to-latest-version/

KDE, XFCE on Kali
https://technicalnavigator.in/kde-vs-xfce-which-one-is-better/