Virtual machines are great for various purposes,

  • run Kali to hack things, not risking your work/home machine to any adverse effects – if you pwn them, don't let them pwn you.
  • research vulnerable software or OSes
  • practice setting up things
  • try out new OSes

I used to use VirtualBox for all of this. It's OK for light use only. Once, while teaching a workshop, I ran a bunch of vulnerable servers in a private network, bridged that network to a wifi AP, and routed that network's traffic to the internet through a pfsense running as guest, so students could pwn my boxes and use google at the same time. In principle this was a great idea but in practice, the virtualbox kernel driver crashed when the students nmapped and fuzzed my boxes.

I found out about ProxMox and have been happily using it for some time now.

Basic setup

Download the installer image, write it to a USB stick and install it. Give the proxmox computer a static IP in your production LAN. Proxmox only works with a static IP and can't be practically migrated to another address or subnet. Configure the domain name to match that of your pfsense setup.

ISO files are stored in /var/lib/vz/template/iso/ on proxmox. To get the files there, either ssh to the box and run wget in the directory, or scp them over from another computer.

The web GUI can be used to create and interact with virtual machines with quite the same features as on VirtualBox.

Cluster

You can convert all your trash PCs into a proxmox cluster. Refer to the documentation on how to create a cluster and add nodes to it.

I'm running Proxmox on a few PCs. I bought two used Lenovo S30 workstations with 8-core Xeons, one with 32gig and the other with 64gigs RAM, from workstation4u.de. I think they are a very cost effective way to build a platform able to host a few dozen VMs with reasonable performance.

Templates

It doesn't make sense to manually create and install a new VM every time I need a new server instance. Luckily, Proxmox supports cloud-init. Cloud-init is used to automatically configure and provision a new VM based on a template.

The basic workflow to create a new cloud-init template is:

  • create a new VM
  • install it manually, configure any additional software you need in the template
  • install, configure and enable cloud-init on the VM
  • convert the VM to a template

Preparing a Kali template

Create a new VM, call it eg. kali-template, and start it with a recent enough Kali installation image. When installing it, also specify kali-template as the hostname or anything that doesn't look like any of your production machines / VMs, to avoid confusion.

When creating the VM, create a disk just barely large enough to contain the OS. I've found 15G is fine for Kali. The partition table should look like this:

  1. primary 1, 512MB, used as /boot
  2. primary 2, 1 or 2 gigs, used as swap
  3. primary 3, the rest, used as /

Don't use LVM. Cloud-init can't resize an LVM volume. With / as the last partition on the disk, cloud-init will resize the root filesystem to fill the remaining space on the disk.

Now boot into the Kali when it's done installing, then install cloud-init by apt-get install cloud-init ; systemctl enable cloud-init ; systemctl enable cloud-final.

Modify the kernel command line in /etc/default/grub to read:

GRUB_CMDLINE_LINUX_DEFAULT="net.ifnames=0 biosdevname=0 console=tty1 console=ttyS0"

and run update-grub.

Create a /etc/cloud/cloud.cfg.d/99_pve.cfg file to read:

datasource_list: [ ConfigDrive, NoCloud ]

Then replace the /etc/cloud/cloud.cfg file with this one. I'm not sure it's done right, seems to work for me, YMMV. The differences to the original one that comes with the cloud-init package are:

  • it configures the kali repositories on the guest instead of whatever is default (debian)
  • default username is john (just because)
  • After first boot, when cloud-init is done configuring the guest, the machine reboots. This is because the guest sets its hostname after starting the network, and on first boot the hostname changes from that of the parent template to the value configured for the guest. Rebooting the guest after the first boot makes it available on the network with its actual name. (This is a hack – I found it easier to just reboot the box instead of hacking cloud-init to restart its network after the hostname has changed)

One more thing, enable ssh by systemctl enable ssh.

Last, when the VM to be used as template is ready, it needs to be converted into a Proxmox template, by running qm template [VMID]. This can't be undone. If you want to modify the template afterwards, either create a full clone of it when you need to change it, or clone the image before templating it and edit that, then convert into template, etc.

Preparing a Ubuntu or Debian template

The above steps apply, except /etc/cloud/cloud.cfg is good as is except for the hostname hack. Append this to the end of that file:

power_state:
   delay: "now"
   mode: reboot
   message: fuck off
   timeout: 240
   condition: True

Instantiating cloud images

I created this handy script to create new VMs off templates created as described above:

#!/bin/bash

source=$1
target=$2
name=$3
sizeplus=$4
mem=$5
cores=$6

if [ -z ""$cores ]  ; then
echo usage: $0 sourceid targetid name +size mem cores
echo   example: $0 800 200 kali +20G 8192 8
exit
fi

qm clone $source $target --name $name
qm set $target --ide2 local-lvm:cloudinit
qm set $target --boot c --bootdisk scsi0
qm set $target --serial0 socket --vga serial0
qm set $target --sshkey /root/keys/id_rsa.pub
qm set $target --ipconfig0 ip=dhcp
qm set $target --cores $cores
qm set $target --memory $mem
qm resize $target scsi0 $sizeplus

I've saved that as createvm.sh in root's home directory. Note that the script excepts your pubkey to exist as /root/keys/id_rsa.pub. The cloud.cfg file specifies your default username. If it's john, and you named your box pwnbox, you should be able to just ssh john@pwnbox when it's done initializing and rebooting itself.

So, if I have a Kali template with id 816, and I want a new VM with id 510, I'm running

# ./createvm.sh 816 510 pwnbox +10G 8192 8

This creates a new VM called “pwnbox”, gives it 10 gigs more disk as what the template has, assigns it 8 gigs of ram and 8 cores. To run the new box, execute qm start 510. After a while it will be available as pwnbox in the lan. (My pfSense has DNS Resolver configured so it resolves DHCP leases by their hostname). The script above configures the guest to use serial terminal, so I can watch it boot by running qm terminal 510.