Skip to main content
Version: v0.1.0-rc.1

💻 Boot it in QEMU

Put the UKI, the machine config and an admin identity onto a disk the way the installer would, then start the node in a local virtual machine with a software TPM.

Before you start

You need:

Run the commands from the root of the cryptos-appliance checkout.

Why not task qemu:run

The Taskfile has a task qemu:run target, but it does not get you through this walkthrough. It boots the image with an empty state disk and no machine config, so the node waits in re-provision maintenance mode on the address DHCP gave it, and its port forward points at 10.0.0.10, an address the node only takes once it has a config. The steps below stage the config first, the way the integration test does.

1. Build the installed disk​

Make a 2 GiB disk image with the two partitions the installer would create: a 512 MiB EFI partition named EFI, and the rest as cryptos-state, which the node encrypts on first boot. Then copy the machine config onto the EFI partition at EFI/cryptos/machine.yaml, where the node looks for it.

truncate -s 2G "$LAB/installed.img"
sgdisk \
--new=1:0:+512MiB --typecode=1:C12A7328-F81F-11D2-BA4B-00A0C93EC93B --change-name=1:EFI \
--new=2:0:0 --typecode=2:CA7D7CCB-63ED-4C53-861C-1742536059CC --change-name=2:cryptos-state \
"$LAB/installed.img"
sgdisk --info=1 "$LAB/installed.img" | grep 'First sector'
Expected output

sgdisk reports that it wrote the new table, then prints where the EFI partition starts:

First sector: 2048 (at 1024.0 KiB)

The EFI partition starts 1024 KiB, that is 1048576 bytes, into the image. If your number differs, multiply the first sector by 512 and use that in place of 1048576 below.

ESP="$LAB/installed.img@@1048576"
mformat -F -v EFI -i "$ESP" ::
mmd -i "$ESP" ::/EFI ::/EFI/cryptos
mcopy -i "$ESP" "$LAB/machine.yaml" ::/EFI/cryptos/machine.yaml
mdir -i "$ESP" ::/EFI/cryptos
Expected output

The last command lists machine.yaml on the EFI partition. If mformat fails, the offset is probably wrong: check the first sector again.

2. Start the software TPM​

swtpm socket --tpm2 --tpmstate dir="$LAB/swtpm" --ctrl type=unixio,path="$LAB/swtpm/sock" &

swtpm keeps the TPM's state in $LAB/swtpm and waits for QEMU on the socket.

The TPM state and the disk belong together

On first boot the node seals its disk key to this software TPM and creates the Root key inside it. Keep $LAB/swtpm with installed.img. If you delete or swap the TPM folder, the node can never unlock its disk again, and the Root key is gone with it. The same happens if you boot this disk with a rebuilt UKI.

3. Boot the node​

qemu-system-x86_64 \
-machine q35,accel=kvm:tcg -m 2048 -nographic \
-drive if=pflash,format=raw,unit=0,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \
-drive if=pflash,format=raw,unit=1,file="$LAB/OVMF_VARS.fd" \
-chardev socket,id=chrtpm,path="$LAB/swtpm/sock" \
-tpmdev emulator,id=tpm0,chardev=chrtpm \
-device tpm-tis,tpmdev=tpm0 \
-drive format=raw,file="fat:rw:$LAB/esp" \
-drive if=none,id=state,format=raw,file="$LAB/installed.img" \
-device virtio-blk-pci,drive=state \
-netdev user,id=n0,net=10.0.0.0/24,host=10.0.0.1,hostfwd=tcp:127.0.0.1:4443-10.0.0.10:443 \
-device virtio-net-pci,netdev=n0

This terminal is now the node's serial console. The flags are the ones the integration test uses:

  • 🔐 TPM: -tpmdev emulator with tpm-tis connects the VM to swtpm.
  • 💾 Disks: the fat:rw: drive is the boot partition with the UKI; installed.img is the node's disk, as a virtio disk.
  • 🌐 Network: QEMU's private network is 10.0.0.0/24 with the host side at 10.0.0.1. Only port 443 of the node is reachable, as 127.0.0.1:4443 on your machine.
  • ⚡ Speed: accel=kvm:tcg uses KVM when it can and falls back to software emulation when it cannot.

4. Watch the first boot​

The console shows the CryptOS mark and the version, then each boot stage as it finishes. The debug image also prints the detailed kernel and init log in between.

  1. state volume. The cryptos-state partition is empty, so the node formats it as an encrypted LUKS2 volume, seals the key to the TPM, and creates a filesystem inside it.
  2. configuration. The node reads EFI/cryptos/machine.yaml, saves it inside the encrypted volume, and deletes the staged copy.
  3. network. It sets its hostname and takes 10.0.0.10.
  4. embedded etcd. It starts its internal database.
  5. management API. It opens the mutual-TLS API on port 443.
Expected output

Each stage is marked [ok] as it finishes, mixed in with the detailed log lines. When management API is marked, the node is up and waiting for its ceremony, and the console moves on to the node's dashboard.

.-----. | .-----.
| | |
| | |
| .--+--. |
| |#####| |
| |#####| |
| '-----' |
| |
'---------------'

CryptOS PKI v0.1.0

[ok] state volume
[ok] configuration
[ok] network
[ok] embedded etcd
[ok] management API

A stage marked [!!] failed. The boot stops there and the node restarts; there is no shell to fall back to.

With KVM the boot is quick. Under software emulation, give it several minutes.

Ctrl-R on the console starts a reset

The dashboard takes keys from this terminal. Once the node has its Root, Ctrl-R opens the reset confirmation, and typing the Root CA's common name then Enter erases the node's identity. Press Esc to back out if you hit it by mistake.

Leave QEMU running and open a second terminal for the next pages.

Ctrl-A X pulls the power

QEMU quits on Ctrl-A, then X, but that is the same as pulling the power cord: the node gets no orderly shutdown. Once the ceremony is done, stop the node with cryptosctl reboot --power-off instead, as shown in Check identity and status.

Next step​

Install the CLI: Install cryptosctl.