KVM (Kernel-based Virtual Machine) is a full virtualization solution for Linux that turns the kernel into a hypervisor. KVM provides hardware-assisted virtualization with strong isolation between guests and host. This guide covers securing KVM hosts, libvirt management, VM hardening, image security, network isolation, and monitoring.
Restrict libvirt to authorized users:
# Add users to libvirt group (use with caution)
sudo usermod -aG libvirt admin-user
# Better: Use polkit for fine-grained access control
# /etc/polkit-1/rules.d/49-libvirt.rules
polkit.addRule(function(action, subject) {
if (action.id == "org.libvirt.unix.manage" &&
subject.isInGroup("libvirt-admin")) {
return polkit.Result.YES;
}
if (action.id == "org.libvirt.unix.read" &&
subject.isInGroup("libvirt-readonly")) {
return polkit.Result.YES;
}
});
Configure libvirt daemon:
<!-- /etc/libvirt/libvirtd.conf -->
# Listen only on localhost (disable remote access if not needed)
listen_tls = 0
listen_tcp = 0
unix_sock_group = "libvirt"
unix_sock_ro_perms = "0777"
unix_sock_rw_perms = "0770"
unix_sock_admin_perms = "0770"
# Enable authentication
auth_unix_ro = "none"
auth_unix_rw = "none"
auth_tcp = "sasl"
# Enable TLS for remote connections
listen_tls = 1
tls_port = "16514"
ca_file = "/etc/pki/CA/cacert.pem"
key_file = "/etc/pki/libvirt/private/serverkey.pem"
cert_file = "/etc/pki/libvirt/servercert.pem"
Restart libvirt:
sudo systemctl restart libvirtd
Configure SSH for KVM hosts:
# Generate SSH key
ssh-keygen -t ed25519 -C "kvm-admin@example.com"
# Copy to KVM host
ssh-copy-id kvm-host.example.com
# Configure SSH server
# /etc/ssh/sshd_config
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
MaxAuthTries 3
AllowUsers kvm-admin kvm-operator
Configure MFA for SSH:
# Install Google Authenticator PAM module
sudo apt install libpam-google-authenticator # Debian/Ubuntu
sudo dnf install google-authenticator # RHEL/Fedora
# Configure PAM
# /etc/pam.d/sshd
auth required pam_google_authenticator.so
# Configure SSH
# /etc/ssh/sshd_config
AuthenticationMethods publickey,keyboard-interactive
Enable automatic security updates:
# Install unattended-upgrades
sudo apt install unattended-upgrades # Debian/Ubuntu
# Configure for virtualization packages
# /etc/apt/apt.conf.d/50unattended-upgrades
Unattended-Upgrade::Package-Blacklist {
# Don't auto-update these if testing needed
# "qemu-kvm";
# "libvirt-daemon";
};
# Or use cron for scheduled updates
sudo crontab -e
0 3 * * 0 /usr/bin/apt-get update && /usr/bin/apt-get upgrade -y
Monitor for updates:
# Check for available updates
sudo apt list --upgradable | grep -E "qemu|libvirt|linux-image"
# Check current versions
qemu-system-x86_64 --version
libvirtd --version
uname -r
Identify running services:
# List libvirt services
systemctl list-units | grep libvirt
# List QEMU processes
ps aux | grep qemu
# Check listening ports
sudo ss -tulpn | grep -E "16509|16514|5900|6080"
Disable unused services:
# Disable libvirt-guests if not needed
sudo systemctl disable libvirt-guests
# Disable libvirt socket if only using system instance
sudo systemctl disable libvirt.socket
sudo systemctl disable libvirt-admin.socket
# Disable VNC/SPICE if not needed
# Edit VM XML to remove graphics devices
virsh edit vm-name
# Remove or restrict <graphics> section
Download from trusted sources:
# Official sources only
# - Cloud Images: https://cloud-images.ubuntu.com/
# - Fedora Cloud: https://getfedora.org/en/cloud/download/
# - CentOS Stream: https://cloud.centos.org/
# - Debian Cloud: https://cloud.debian.org/images/
# Verify checksums
wget https://cloud-images.ubuntu.com/jammy/current/SHA256SUMS
wget https://cloud-images.ubuntu.com/jammy/current/SHA256SUMS.gpg
gpg --verify SHA256SUMS.gpg SHA256SUMS
sha256sum -c SHA256SUMS
Verify image signatures:
# Import Ubuntu cloud image key
gpg --keyserver keyserver.ubuntu.com --recv-keys <key-id>
# Verify signature
gpg --verify SHA256SUMS.gpg SHA256SUMS
Secure image storage:
# Set proper permissions on image directory
sudo chown -R libvirt-qemu:kvm /var/lib/libvirt/images
sudo chmod 750 /var/lib/libvirt/images
# Restrict image file permissions
sudo chmod 640 /var/lib/libvirt/images/*.qcow2
# Verify permissions
ls -la /var/lib/libvirt/images/
Encrypt VM images:
# Create encrypted LUKS volume
sudo cryptsetup luksFormat /dev/sdX
# Or use QEMU native encryption
qemu-img create -f qcow2 \
-o encryption=luks,luks.key-secret=sec0 \
encrypted-disk.qcow2 100G
# Define secret in libvirt
cat > secret.xml << EOF
<secret ephemeral='no' private='yes'>
<uuid>00000000-0000-0000-0000-000000000000</uuid>
<description>VM encryption key</description>
<usage type='volume'>
<volume>/var/lib/libvirt/images/encrypted-disk.qcow2</volume>
</usage>
</secret>
EOF
virsh secret-define secret.xml
virsh secret-set-value --secret 00000000-0000-0000-0000-000000000000 \
--base64 "$(base64 /path/to/keyfile)"
Scan images before deployment:
# Install virt-scan
sudo apt install libguestfs-tools # Debian/Ubuntu
sudo dnf install libguestfs-tools # RHEL/Fedora
# Scan for security issues
virt-scan -a vm-image.qcow2
# Scan for malware
virt-scan -a vm-image.qcow2 --virus-scan
# Check for compliance
virt-scan -a vm-image.qcow2 --compliance
Inspect image contents:
# List files in image
virt-ls -a vm-image.qcow2 -R /
# Check for sensitive files
virt-find -a vm-image.qcow2 /etc/shadow
virt-find -a vm-image.qcow2 /root/.ssh
# Check installed packages
virt-rpm -a vm-image.qcow2 -qa
Configure libvirt storage pools:
<!-- /etc/libvirt/storage-pools/secure-pool.xml -->
<pool type='dir'>
<name>secure-pool</name>
<source>
</source>
<target>
<path>/var/lib/libvirt/secure-images</path>
<permissions>
<mode>0750</mode>
<owner>0</owner>
<group>0</group>
</permissions>
</target>
</pool>
Apply storage pool:
# Define and start storage pool
virsh pool-define secure-pool.xml
virsh pool-start secure-pool
virsh pool-autostart secure-pool
Create hardened VM XML:
<!-- /etc/libvirt/qemu/vm-name.xml -->
<domain type='kvm'>
<name>secure-vm</name>
<!-- Restrict memory -->
<memory unit='GiB'>4</memory>
<currentMemory unit='GiB'>4</currentMemory>
<!-- Restrict CPUs -->
<vcpu placement='static'>2</vcpu>
<cputune>
<vcpupin vcpu='0' cpuset='0'/>
<vcpupin vcpu='1' cpuset='1'/>
</cputune>
<!-- Security settings -->
<secmodel type='dac'>
<model>dac</model>
<baselabel>user:group</baselabel>
</secmodel>
<!-- Disable unnecessary devices -->
<devices>
<!-- Remove USB controller -->
<!-- Remove sound card -->
<!-- Remove serial/parallel ports -->
<!-- Restrict network -->
<interface type='network'>
<source network='default'/>
<model type='virtio'/>
</interface>
<!-- Use virtio for disk -->
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2' cache='none' io='native'/>
<source file='/var/lib/libvirt/images/vm-disk.qcow2'/>
<target dev='vda' bus='virtio'/>
</disk>
</devices>
<!-- QEMU command line restrictions -->
<qemu:commandline xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
<qemu:arg value='-seccomp'/>
<qemu:arg value='on'/>
</qemu:commandline>
</domain>
AppArmor for libvirt (Debian/Ubuntu):
# Check AppArmor status
sudo aa-status
# Ensure libvirt profiles are loaded
sudo aa-enforce /etc/apparmor.d/usr.sbin.libvirtd
sudo aa-enforce /etc/apparmor.d/usr.lib.libvirt.virt-aa-helper
# Verify VM profiles
sudo aa-status | grep libvirt
SELinux for libvirt (RHEL/CentOS/Fedora):
# Check SELinux status
getenforce
# Verify libvirt SELinux context
ls -Z /var/lib/libvirt/images/
# Restore contexts if needed
restorecon -Rv /var/lib/libvirt/
# Check for SELinux denials
ausearch -m avc -ts recent | grep libvirt
Remove unnecessary hardware:
# Edit VM to remove devices
virsh edit vm-name
# Remove:
# - Floppy drives
# - CD-ROM (unless needed)
# - Sound cards
# - USB controllers (unless needed)
# - Serial/parallel ports
# - Smartcard devices
# - TPM (unless needed for attestation)
Disable copy-paste with host:
<!-- Remove or disable QEMU guest agent channel -->
<channel type='unix'>
<target type='virtio' name='org.qemu.guest_agent.0'/>
</channel>
<!-- Or restrict in VM -->
# Inside VM
systemctl disable qemu-guest-agent
Isolate VM networks:
# Create isolated network
cat > isolated-net.xml << EOF
<network>
<name>isolated</name>
<forward mode='nat'/>
<bridge name='virbr1' stp='on' delay='0'/>
<ip address='192.168.100.1' netmask='255.255.255.0'>
<dhcp>
<range start='192.168.100.100' end='192.168.100.200'/>
</dhcp>
</ip>
</network>
EOF
virsh net-define isolated-net.xml
virsh net-start isolated
virsh net-autostart isolated
Configure VM firewall:
# Install firewall in VM
# Inside VM
sudo ufw enable # Ubuntu/Debian
sudo firewall-cmd --state # RHEL/Fedora
# Or use libvirt nwfilter
virsh nwfilter-define - <<EOF
<filter name='clean-traffic' chain='root'>
<filterref filter='no-mac-spoofing'/>
<filterref filter='no-ip-spoofing'/>
<filterref filter='no-arp-spoofing'/>
</filter>
EOF
# Apply to VM interface
virsh attach-interface vm-name network isolated \
--model virtio \
--filterref clean-traffic
Store credentials securely:
# Create secret for iSCSI authentication
cat > secret.xml << EOF
<secret ephemeral='no' private='yes'>
<uuid>00000000-0000-0000-0000-000000000000</uuid>
<description>iSCSI CHAP secret</description>
<usage type='iscsi'>
<target>iqn.2024-01.com.example:storage</target>
</usage>
</secret>
EOF
virsh secret-define secret.xml
virsh secret-set-value --secret 00000000-0000-0000-0000-000000000000 \
--base64 "$(echo -n 'chap-secret' | base64)"
Reference secret in storage pool:
<!-- Storage pool with authentication -->
<pool type='iscsi'>
<name>iscsi-pool</name>
<source>
<host name='iscsi.example.com' port='3260'/>
<device path='iqn.2024-01.com.example:storage'/>
<auth type='chap' username='iscsi-user'>
<secret type='iscsi' uuid='00000000-0000-0000-0000-000000000000'/>
</auth>
</source>
</pool>
Use Vault for VM secrets:
# Install Vault agent in VM
# Configure Vault agent for auto-inject
# Or use cloud-init with Vault
# user-data:
# vault:
# address: https://vault.example.com:8200
# role: vm-role
# secrets:
# - path: secret/data/vm-credentials
# destination: /etc/vm/credentials.env
Configure audit logging:
<!-- /etc/libvirt/libvirtd.conf -->
audit_logging = 1
audit_level = 2
View audit logs:
# View libvirt logs
sudo journalctl -u libvirtd -f
# View audit logs
sudo ausearch -m VIRT_RESOURCE -i
# View QEMU audit logs
sudo ausearch -m VIRT_CONTROL -i
Check VM status:
# List all VMs
virsh list --all
# Check VM resource usage
virt-top
# Monitor VM network
virt-df vm-name
# Check VM block I/O
virt-df vm-name --block
Monitor for security events:
# Watch for VM creation/deletion
virsh event --loop --all
# Watch for lifecycle changes
virsh event --loop --event lifecycle
# Monitor in logs
grep -E "START|SHUTDOWN|DESTROY" /var/log/libvirt/qemu/*.log
Install libvirt exporter:
# Download libvirt exporter
wget https://github.com/prometheus-community/libvirt_exporter/releases/latest/download/libvirt_exporter
# Run exporter
./libvirt_exporter --libvirt-uri qemu:///system
# Or run as systemd service
sudo systemctl enable libvirt-exporter
sudo systemctl start libvirt-exporter
Create security alerts:
# prometheus-rules.yaml
groups:
- name: kvm-security
rules:
- alert: KVMVMUnexpectedShutdown
expr: changes(libvirt_domain_state{state="shutdown"}[5m]) > 0
for: 1m
labels:
severity: warning
annotations:
summary: "VM unexpectedly shut down"
- alert: KVMHighVMCount
expr: count(libvirt_domain_state) > 50
for: 10m
labels:
severity: warning
annotations:
summary: "High number of VMs running"
| Control | Status | Notes |
|---|---|---|
| libvirt access restricted | ☐ | Polkit rules configured |
| SSH key-based auth | ☐ | Password auth disabled |
| MFA enabled | ☐ | For SSH access |
| Host patched | ☐ | QEMU, libvirt, kernel |
| Unused services disabled | ☐ | Minimal attack surface |
| Trusted images only | ☐ | Verified checksums |
| Images encrypted | ☐ | LUKS or QEMU native |
| VM images scanned | ☐ | Before deployment |
| AppArmor/SELinux enabled | ☐ | Mandatory access control |
| Unnecessary devices removed | ☐ | Minimal VM hardware |
| Network isolation | ☐ | Separate networks |
| Secrets in libvirt secrets | ☐ | Not in VM XML |
| Audit logging enabled | ☐ | libvirt and QEMU |
| Monitoring configured | ☐ | Prometheus/Grafana |
Check KVM security status:
# Check libvirt configuration
virsh capabilities | grep -A5 security
# Check AppArmor status
sudo aa-status | grep libvirt
# Check SELinux status
getenforce
# Check listening ports
sudo ss -tulpn | grep -E "16509|16514|5900|6080"
# Check VM configurations
virsh dumpxml vm-name | grep -E "graphics|channel|hostdev"
# Check image permissions
ls -la /var/lib/libvirt/images/
Audit VM security:
# List all VMs with security info
virsh list --all --name | while read vm; do
echo "=== $vm ==="
virsh dumpxml $vm | grep -E "secmodel|graphics|channel"
done
# Check for VMs with VNC enabled
virsh list --all --name | while read vm; do
virsh dumpxml $vm | grep -q "graphics.*vnc" && echo "$vm has VNC"
done
# Check for VMs with host passthrough
virsh list --all --name | while read vm; do
virsh dumpxml $vm | grep -q "host-passthrough" && echo "$vm has host-passthrough"
done