Skip to content

Commit a74251b

Browse files
committed
blog: add sealed images deploying post
Fourth post in the sealed images series, covering deployment via bcvk and verification that the full security chain is active. Assisted-by: OpenCode (claude-opus-4-6) Signed-off-by: John Eckersberg <jeckersb@redhat.com>
1 parent 2b2dd8f commit a74251b

1 file changed

Lines changed: 163 additions & 0 deletions

File tree

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
+++
2+
title = "Sealed images: deploying and verifying a sealed image"
3+
date = 2026-04-30
4+
slug = "2026-apr-30-sealed-images-deploying"
5+
6+
[extra]
7+
author = "jeckersb"
8+
+++
9+
10+
# Sealed images: deploying and verifying a sealed image
11+
12+
In the [previous post](@/blog/2026-apr-29-sealed-images-building.md)
13+
we built a sealed, signed container image. Now it's time to deploy
14+
it to a virtual machine and verify that the full security chain is
15+
active.
16+
17+
## bcvk: a tool for running bootc VMs
18+
19+
[bcvk](https://github.com/bootc-dev/bcvk) (bootc virtualization kit)
20+
is a tool that makes it easy to run bootc container images as virtual
21+
machines. Under the hood, it calls `bootc install to-disk` to create
22+
a bootable disk image, then manages the VM lifecycle via libvirt.
23+
For sealed images, bcvk also handles Secure Boot key enrollment into
24+
the VM's UEFI firmware automatically using
25+
[virt-firmware](https://gitlab.com/kraxel/virt-firmware).
26+
27+
bcvk is available as a package on Fedora and EPEL:
28+
29+
```
30+
$ dnf install bcvk
31+
```
32+
33+
## Deploying the image
34+
35+
With the sealed image built from the previous post and keys generated
36+
from the key management post, deploying is a single command:
37+
38+
```
39+
$ bcvk libvirt run \
40+
--ssh-wait \
41+
--name sealed-demo \
42+
--filesystem=ext4 \
43+
--secure-boot-keys target/keys \
44+
localhost/sealed-host:latest
45+
```
46+
47+
Let's break down what's happening here:
48+
49+
- `--filesystem=ext4` tells `bootc install` to use ext4 for the root
50+
filesystem. The default root filesystem may be XFS, which does not
51+
support fs-verity, so we explicitly select ext4 here. (fs-verity
52+
is supported on ext4 and btrfs.)
53+
- `--secure-boot-keys target/keys` points bcvk to the directory
54+
containing our PK, KEK, and db certificates. bcvk uses
55+
`virt-fw-vars` to enroll these keys into a copy of the OVMF
56+
firmware variable store, so the VM boots with Secure Boot enabled
57+
and configured to trust our signing key.
58+
- `--ssh-wait` tells bcvk to wait until SSH is available before
59+
returning, so we know the system has fully booted.
60+
61+
If you're using the
62+
[examples repository](https://github.com/redhat-cop/rhel-bootc-examples/tree/main/sealing),
63+
this is wrapped up in a convenient `just` recipe:
64+
65+
```
66+
$ just bcvk-ssh
67+
```
68+
69+
This builds the image, boots the VM, waits for it to reach
70+
`multi-user.target`, and opens an interactive SSH session.
71+
72+
## Verifying the seal
73+
74+
Once the system is booted and we have an SSH session, we can verify
75+
that the full security chain is active.
76+
77+
### Check the kernel command line
78+
79+
```
80+
$ cat /proc/cmdline
81+
composefs=3a7f... (128-character SHA-512 hex digest) rw
82+
```
83+
84+
The presence of `composefs=<digest>` in the kernel command line
85+
confirms that the initramfs received the composefs digest from the
86+
signed UKI. This is the digest that was computed during the build
87+
and embedded in the UKI's command line section.
88+
89+
### Check the root mount
90+
91+
```
92+
$ findmnt -t overlay /
93+
TARGET SOURCE FSTYPE OPTIONS
94+
/ overlay[/sysroot/state..] overlay ro,relatime,...,verity=require,...
95+
```
96+
97+
The key thing to look for is `verity=require` in the mount options.
98+
This confirms that the kernel is enforcing fs-verity verification
99+
on every file access through the composefs mount. Any file in the
100+
operating system whose content doesn't match its expected fs-verity
101+
digest will produce an I/O error when read.
102+
103+
### Check Secure Boot status
104+
105+
```
106+
$ mokutil --sb-state
107+
SecureBoot enabled
108+
```
109+
110+
This confirms that the firmware verified the bootloader and UKI
111+
signatures before allowing them to execute.
112+
113+
### What this tells us
114+
115+
These three checks together confirm the full chain from the
116+
[first post](@/blog/2026-apr-27-sealed-images-security-chain.md):
117+
118+
1. Secure Boot verified the bootloader and UKI (`mokutil --sb-state`)
119+
2. The UKI delivered the composefs digest to the initramfs
120+
(`/proc/cmdline`)
121+
3. The kernel is enforcing per-file verification against that digest
122+
(`verity=require`)
123+
124+
## What happens if something is tampered with?
125+
126+
It's worth understanding why tampering with a sealed system is
127+
difficult in practice.
128+
129+
The operating system files are stored in a content-addressed object
130+
store on disk. Each object has fs-verity enabled, which means the
131+
kernel has recorded a cryptographic hash of its contents. The files
132+
are immutable: you cannot write to an fs-verity protected file. Any
133+
attempt to open a verity-protected file for writing will fail.
134+
135+
Even if an attacker were to bypass the filesystem and corrupt data
136+
directly on the underlying block device, the kernel would detect
137+
the corruption. When a process attempts to read the tampered file,
138+
the kernel verifies the data against the stored hash before
139+
returning it. If the data doesn't match, the read fails with
140+
`EIO`. The corrupted data is never served to any process.
141+
142+
This is a meaningful distinction from a system where tampered files
143+
are silently served. On a sealed system, corruption doesn't go
144+
unnoticed -- it causes an immediate, visible failure.
145+
146+
## Conclusion
147+
148+
Over the course of this series, we've walked through the complete
149+
lifecycle of a sealed image:
150+
151+
1. [The security chain](@/blog/2026-apr-27-sealed-images-security-chain.md)
152+
from firmware to filesystem that makes sealed images possible
153+
2. [Key management](@/blog/2026-apr-28-sealed-images-key-management.md)
154+
for generating and enrolling Secure Boot keys
155+
3. [Building a sealed image](@/blog/2026-apr-29-sealed-images-building.md)
156+
with a multi-stage container build
157+
4. Deploying and verifying the seal (this post)
158+
159+
The complete working example is available in the
160+
[rhel-bootc-examples](https://github.com/redhat-cop/rhel-bootc-examples)
161+
repository under the
162+
[sealing](https://github.com/redhat-cop/rhel-bootc-examples/tree/main/sealing)
163+
directory.

0 commit comments

Comments
 (0)