Skip to main content

Tools for confidential virtual machines

Project description

cvmutils -- Prepare OS Images for Confidential Environments

Overview

cvmutils is a toolkit for preparing Linux OS images to run in confidential virtual machine (CVM) environments. It provides two CLI tools:

  • cvm-encrypt-image -- Pre-encrypts the root volume and seals the encryption key to a target vTPM using predicted PCR values. Also supports creating immutable, integrity-protected images with dm-verity.
  • cvm-reseal (experimental) -- Re-seals LUKS volume keys on a running system when PCR measurements change (e.g., after a kernel or UKI update).

Previously named encrypt-rhel-image, the toolkit was originally targeted at Fedora/RHEL but now supports multiple other distributions.

Supported Distributions

  • RHEL 9.4+, RHEL 10.0+
  • Fedora 41+
  • Azure Linux 3
  • CentOS Stream 9, CentOS Stream 10

Other Linux distributions using UKI boot scheme with GPT partitioning should also work.

Image Formats

cvm-encrypt-image accepts image files in the following formats:

  • VHD (.vhd)
  • QCOW2 (.qcow2)
  • Raw (.raw)
  • Block devices directly (e.g., /dev/nbd0)

Image files are attached via qemu-nbd; block devices are used as-is.

Installation

From source (pip)

pip install .

or via the Makefile:

make install

To uninstall:

make uninstall

RPM packages

Three RPM subpackages are provided:

Package Contents
python3-cvmutils cvm-encrypt-image CLI, Python library, man page, utility scripts
cvmutils-reseal (experimental) cvm-reseal CLI
cvmutils-dracut (experimental) Dracut module, cvmutils-rquote, systemd service

To build RPMs locally:

rm -rf dist
make -f .copr/Makefile srpm outdir=. spec=.distro/python-cvmutils.spec
mock python-cvmutils-0.3.2-1.fc43.src.rpm

Container

See Running in a Container below.

Image Prerequisites

  • Partition Table: GUID Partition Table (GPT).
  • ESP Partition: Image must contain an ESP partition with GUID C12A7328-F81F-11D2-BA4B-00A0C93EC93B.
  • Root Partition: Image must contain a "Linux root (x86-64)" partition with GUID 4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709.
  • LUKS Conversion: The root partition must be convertible to LUKS. To accommodate the LUKS header, the tool supports:
    • Resizing the root partition (requires available space after root).
    • Shrinking an ext4 filesystem.
  • PCR7: The expected value can be specified explicitly (--pcr7 <sha256value>) or predicted (--pcr7 auto) using one of the following profiles:
    • UEFI profile (--uefi-profile)
    • Azure Disk Profile (--az-disk-profile)
    • EFI vars profile (--efivars-profile)

      Example templates are present in tests/test-data.

  • PCR4: Can optionally be included in the set of PCRs for root volume key sealing. Its value can be specified directly (--pcr4 <sha256value>) or predicted (--pcr4 auto).

How It Works

Image preparation is a two-phase workflow:

  1. Encrypt -- The root partition is converted to LUKS2 in-place using cryptsetup reencrypt --encrypt. A temporary cleartext password (derived from the root partition UUID) is used during this phase.

  2. Deploy -- The ESP is mounted and boot chains are discovered from BOOTX64.CSV (UTF-16LE file enumerating the bootloader and UKI paths). For each boot chain, the expected PCR4 and/or PCR7 values are predicted by:

    • Parsing the Secure Boot configuration (PK, KEK, db, dbx) from a UEFI profile, Azure disk profile, or efivars
    • Computing Authenticode hashes of PE binaries in the boot chain (shim, bootloader, UKI, addons)
    • Simulating TPM PCR extend operations

    The root volume key is then sealed to the target vTPM via systemd-cryptenroll with the predicted PCR policy. The temporary cleartext password is removed.

An optional third mode, makeverity, creates a dm-verity partition and a signed UKI addon containing the root hash instead of encrypting.

After deployment, the reseal tool (cvm-reseal) can be used on a running system to update TPM2 tokens when the boot chain changes.

TPM2

The SRK (Storage Root Key) public key is required for the deploy phase. It can be obtained from the target vTPM with:

systemd-analyze srk > public.srk

or

tpm2_readpublic -c 0x81000001 -o public.srk -t primary.handle

Usage: cvm-encrypt-image

Actions

Action Description
encrypt Encrypt the root partition (LUKS2 conversion)
encrypt-resume Resume an interrupted encryption
deploy Seal the encryption key to a target vTPM
makeverity (experimental) Create a dm-verity partition and signed UKI addon

Basic Example

cvm-encrypt-image encrypt /path/to/image.vhd
cvm-encrypt-image deploy \
    -s /path/to/public.srk \
    --pcr7 auto \
    --uefi-profile uefi_profile.json \
    /path/to/image.vhd

Common Options

Option Description
-n, --nbddev N NBD device number (default: 0)
-v, --verbose Print additional info
--encryption-password Use the specified temporary password for LUKS encryption instead of a randomly generated one

Encrypt Options

Option Description
-g, --growpart Grow root partition to the size of the volume before encryption
--no-cloud-init Do not create /cc_growpart_keydata and LUKS keyslot for root volume resize
--encrypt-progress-json FILE Write encryption progress to a file or - for stdout

Deploy Options

Option Description
-s, --srkpub FILE SRK public key (required)
--pcr7 VALUE Expected PCR7 sha256 value, or auto for prediction
--pcr4 VALUE Expected PCR4 sha256 value, or auto for prediction
--uefi-profile FILE UEFI profile JSON for --pcr7 auto (mutually exclusive with other profile options)
--az-disk-profile FILE Azure disk profile JSON for --pcr7 auto (mutually exclusive with other profile options)
--efivars-profile DIR Efivars-format directory for --pcr7 auto (mutually exclusive with other profile options)
--efivars-profile-no-attrs Efivars files lack the 4-byte attribute header (--efivars-profile only)
--nosecureboot Predict PCRs with Secure Boot disabled
-r, --recovery-key FILE Add a recovery key passphrase to the root volume
--recovery-key-type {binary,text,both} Recovery key format (default: binary)

Makeverity Options (Experimental)

Option Description
--secureboot-cert FILE SecureBoot certificate (PEM) to sign UKI addon (required)
--secureboot-key FILE SecureBoot key (PEM) to sign UKI addon (required)
--volatile-overlay Add systemd.volatile=overlay for read-write overlay

Example:

cvm-encrypt-image makeverity \
    --secureboot-key=custom_db.key \
    --secureboot-cert=custom_db.pem \
    --volatile-overlay vol.raw

The signed root hash is placed on the ESP as /loader/addons/roothash.addon.efi.

Usage: cvm-reseal

cvm-reseal runs on a live system to update TPM2 tokens when the boot chain or Secure Boot configuration changes (e.g., after a kernel update that installs a new UKI).

It reads the current Secure Boot state from /sys/firmware/efi/efivars/, discovers boot chains from the ESP, and compares predicted PCR policies against existing LUKS tokens. New tokens are added and stale tokens can be removed.

Basic Example

cvm-reseal

Dry-run to see what would change:

cvm-reseal --dry-run

Options

Option Description
-d, --dry-run List volumes needing re-seal without making changes
-p, --pcrs PCRS PCRs to seal against, e.g., 4,7 or 4+7 (default: auto, reuses existing scheme)
-r, --remove {none,matching,all} Remove unused tokens: none (default), matching PCR set, or all
-u, --unlock {tpm,key-file} How to unlock LUKS for re-enrollment (default: tpm)
--unlock-keyfile FILE Key file for --unlock key-file
--tpm-srk-pub FILE TPM SRK public key (default: /run/systemd/tpm2-srk-public-key.tpm2b_public)
-v, --verbose Print additional info

Running in a Container

Three Containerfiles are provided:

Containerfile Base Image Supported Actions
Containerfile.fedora Fedora Rawhide encrypt, deploy, makeverity
Containerfile.c10s CentOS Stream 10 encrypt, deploy, makeverity
Containerfile.ubi10 UBI 10 deploy only (no e2fsprogs, veritysetup, or ukify)

Example (assuming data is in /data on the host):

docker build -f Containerfile.fedora -t cvmutils-fedora .
modprobe nbd max_part=8
qemu-nbd -c /dev/nbd0 vol.raw
docker run -it --privileged \
    --mount type=bind,source=/data,target=/data \
    --mount type=bind,source=/run/udev,target=/run/udev \
    --mount type=bind,source=/dev,target=/dev \
    cvmutils-fedora:latest \
    cvm-encrypt-image encrypt -v /dev/nbd0
docker run -it --privileged \
    --mount type=bind,source=/data,target=/data \
    --mount type=bind,source=/run/udev,target=/run/udev \
    --mount type=bind,source=/dev,target=/dev \
    cvmutils-fedora:latest \
    cvm-encrypt-image deploy \
        -s /data/public.srk \
        --pcr7 auto \
        --uefi-profile /data/uefi-profile.json \
        -v /dev/nbd0
qemu-nbd --disconnect /dev/nbd0

Utility Scripts

The scripts/ directory contains helper tools:

  • tpm_eventlog_to_uefi_profile.py -- Converts a TPM2 event log (YAML format) to a UEFI profile JSON suitable for --uefi-profile. Requires PyYAML.

    python3 scripts/tpm_eventlog_to_uefi_profile.py /path/to/eventlog.yaml -o uefi_profile.json
    
  • print-recovery-key.py -- Converts a binary recovery key file to a human-readable dash-separated numeric format.

    python3 scripts/print-recovery-key.py /path/to/recovery.key
    

Dracut Module

The cvmutils-dracut RPM package installs a dracut module (99cvmutils) for use inside Azure CVM guests during initrd. It includes:

  • cvmutils-rquote -- A shell script that reads Azure attestation data from TPM2 NV indices, generates a nonce, and obtains a TPM2 quote. The results are stored under /run/cvmutils/.
  • cvmutils-initrd.service -- A systemd oneshot service that runs cvmutils-rquote after tpm2.target and before cryptsetup.target. Only activates when ConditionSecurity=measured-uki is met.

The dracut module depends on the tpm2-tss dracut module.

Dependencies

Basic:

  • python3.x
  • setuptools (for Python < 3.13)

Encrypt phase:

  • qemu-nbd (for image files; not needed for block devices)
  • e2fsprogs (e2fsck, resize2fs)
  • cryptsetup
  • util-linux (blkid, sfdisk)

Deploy phase additionally requires:

  • openssl
  • systemd-cryptenroll >= 255

Makeverity phase additionally requires:

  • veritysetup
  • ukify

Reseal (cvm-reseal) requires:

  • cryptsetup
  • systemd-cryptenroll
  • bootctl (to locate ESP)
  • lsblk

Optional (scripts):

  • PyYAML (for scripts/tpm_eventlog_to_uefi_profile.py)

Testing

The tool comes with unit tests which can be executed with pytest. The test-data submodule must be checked out first.

pip install pytest
git submodule update --init
pytest

Or via the Makefile:

make test

Out of tree end-to-end tests are available at https://gitlab.com/vkuznets/cvmutils-tests-e2e.

Development

Prerequisites

Install the development tools as needed:

pip install pylint build twine pytest

Additional tools required by specific targets: help2man (man), pyp2rpm, rpmbuild, and createrepo (rpm).

Makefile Targets

Target Description
make lint, make pylint Run pylint on the cvmutils package
make test, make check Run pytest
make dist, make tarball Build source distribution and run twine check
make rpm Build RPM packages via pyp2rpm and rpmbuild
make man Regenerate the man page with help2man
make install Install with pip install --user .
make uninstall Uninstall the package
make clean Remove build artifacts

Troubleshooting

Problem Cause Fix
qemu-nbd fails with "No such file or directory" The nbd kernel module is not loaded Run modprobe nbd max_part=8 before using image files
Device or mount errors inside a container Container lacks device access Run the container with --privileged (see Running in a Container)
"Permission denied reading …" from cvm-reseal Reading /sys/firmware/efi/efivars/ requires root Run cvm-reseal as root or with sudo
NBD device is busy A previous run did not disconnect Run qemu-nbd --disconnect /dev/nbd<N> to release the device
Encryption was interrupted Power loss or process killed during cryptsetup reencrypt Resume with cvm-encrypt-image encrypt-resume
udevadm settle fails in a container No udev daemon running Ensure /run/udev from the host is bind-mounted into the container

Unused Features Queued for Deprecation

ESP may contain an efivars.json file in Azure format:

{
    "type": "Microsoft.Compute/disks",
    "properties": {
        "uefiSettings": {
            "Boot0004": {
                "guid": "Yd/ki8qT0hGqDQDgmAMrjA==",
                "attributes": "Bw==",
                "value": "AQAAAGIAUwBoAGkAbQAgAGIAbwBvAHQAIAB0AG8AIAA1AC4AMQA0AC4AMAAtADIAMwA4AF8AdQBrAGkAXwB0AGUAcwB0ADEAOQAuAGUAbAA5AC4AeAA4ADYAXwA2ADQAAAAEASoAAgAAAAAoAAAAAAAAAOAHAAAAAABibF3EAi9J4o1TPhDbQRiuAgIEBDQAXABFAEYASQBcAHIAZQBkAGgAYQB0AFwAcwBoAGkAbQB4ADYANAAuAGUAZgBpAAAAf/8EAFwARQBGAEkAXABMAGkAbgB1AHgAXAB2AG0AbABpAG4AdQB6AC0ANQAuADEANAAuADAALQAyADMAOABfAHUAawBpAF8AdABlAHMAdAAxADkALgBlAGwAOQAuAHgAOAA2AF8ANgA0AC0AdgBpAHIAdAAuAGUAZgBpAAAA"
            }
        }
    }
}

In case it does, its size and content will be written to a special 'Linux reserved' GUID 8DA63339-0007-60C0-C436-083AC8230908 partition starting at offset 2048 * 512 = 1048576.

License

cvmutils is licensed under the GNU Lesser General Public License v2.1 or later (LGPL-2.1-or-later).

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cvmutils-0.3.2.tar.gz (55.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cvmutils-0.3.2-py3-none-any.whl (49.5 kB view details)

Uploaded Python 3

File details

Details for the file cvmutils-0.3.2.tar.gz.

File metadata

  • Download URL: cvmutils-0.3.2.tar.gz
  • Upload date:
  • Size: 55.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for cvmutils-0.3.2.tar.gz
Algorithm Hash digest
SHA256 9fe5e2c5e17b2f5672d5bd2da185697fb3a3f03eede0d1c902583c414f13d51f
MD5 63edb90a0ac816db39831e7539ea3a66
BLAKE2b-256 b46bb525cf8914476594f39a229f4e2f779d9ab427c9e8ee4fa07b7c68cfdaae

See more details on using hashes here.

File details

Details for the file cvmutils-0.3.2-py3-none-any.whl.

File metadata

  • Download URL: cvmutils-0.3.2-py3-none-any.whl
  • Upload date:
  • Size: 49.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for cvmutils-0.3.2-py3-none-any.whl
Algorithm Hash digest
SHA256 65c0e2b864012bbc12534a2ceef9b2d0e5f94d44d14759aecadb8abe1128d08f
MD5 946a60f9fda4afea4cf247fc17d0dbee
BLAKE2b-256 1940944759c69139d10077c38ee7ed57af2176831336a444bae0ef6a008a2d50

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page