Skip to content

Commit 7080f50

Browse files
committed
libvirt: Switch to UEFI (and tpm2) by default
Signed-off-by: Colin Walters <walters@verbum.org>
1 parent 5a68fba commit 7080f50

File tree

5 files changed

+230
-12
lines changed

5 files changed

+230
-12
lines changed

crates/kit/src/libvirt/create.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,14 @@ pub struct LibvirtCreateOpts {
8181
#[clap(long)]
8282
pub disk_size: Option<String>,
8383

84+
/// Firmware type for the VM (\"uefi\", \"uefi-secure\", or \"bios\", defaults to \"uefi\")
85+
#[clap(long, default_value = "uefi")]
86+
pub firmware: String,
87+
88+
/// Disable TPM 2.0 support (enabled by default)
89+
#[clap(long)]
90+
pub disable_tpm: bool,
91+
8492
/// Memory size for installation VM during auto-upload (e.g. 2G, 1024M)
8593
#[clap(flatten)]
8694
pub install_memory: MemoryOpts,
@@ -487,7 +495,9 @@ impl LibvirtCreateOpts {
487495
.with_memory(memory_mb)
488496
.with_vcpus(self.vcpus.unwrap_or_else(default_vcpus))
489497
.with_disk(&domain_volume_path)
490-
.with_network(network_config);
498+
.with_network(network_config)
499+
.with_firmware(&self.firmware)
500+
.with_tpm(!self.disable_tpm);
491501

492502
// Add QEMU arguments if we have any
493503
if !qemu_args.is_empty() {

crates/kit/src/libvirt/domain.rs

Lines changed: 189 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ pub struct DomainBuilder {
3636
metadata: HashMap<String, String>,
3737
qemu_args: Vec<String>,
3838
virtiofs_filesystems: Vec<VirtiofsFilesystem>,
39+
firmware: Option<String>, // "uefi" (default), "uefi-secure", or "bios"
40+
tpm: bool,
3941
}
4042

4143
impl Default for DomainBuilder {
@@ -59,6 +61,8 @@ impl DomainBuilder {
5961
metadata: HashMap::new(),
6062
qemu_args: Vec::new(),
6163
virtiofs_filesystems: Vec::new(),
64+
firmware: None, // Defaults to UEFI
65+
tpm: true, // Default to enabled
6266
}
6367
}
6468

@@ -122,6 +126,18 @@ impl DomainBuilder {
122126
self
123127
}
124128

129+
/// Set firmware type (\"uefi\", \"uefi-secure\", or \"bios\", defaults to \"uefi\")
130+
pub fn with_firmware(mut self, firmware: &str) -> Self {
131+
self.firmware = Some(firmware.to_string());
132+
self
133+
}
134+
135+
/// Enable TPM 2.0 support using swtpm
136+
pub fn with_tpm(mut self, tpm: bool) -> Self {
137+
self.tpm = tpm;
138+
self
139+
}
140+
125141
/// Build the domain XML
126142
pub fn build_xml(self) -> Result<String> {
127143
let name = self.name.ok_or_else(|| eyre!("Domain name is required"))?;
@@ -160,17 +176,80 @@ impl DomainBuilder {
160176
)?;
161177
writer.write_text_element("vcpu", &vcpus.to_string())?;
162178

163-
// OS section
164-
writer.start_element("os", &[])?;
165-
writer.write_text_element_with_attrs(
166-
"type",
167-
&arch_config.os_type,
168-
&[
169-
("arch", &arch_config.arch),
170-
("machine", &arch_config.machine),
171-
],
172-
)?;
173-
writer.write_empty_element("boot", &[("dev", "hd")])?;
179+
// OS section with firmware configuration
180+
let use_uefi = self.firmware.as_deref() != Some("bios");
181+
let secure_boot = self.firmware.as_deref() == Some("uefi-secure");
182+
183+
if use_uefi && secure_boot {
184+
// Secure boot requires explicit firmware paths
185+
writer.start_element("os", &[("firmware", "efi")])?;
186+
writer.write_text_element_with_attrs(
187+
"type",
188+
&arch_config.os_type,
189+
&[
190+
("arch", &arch_config.arch),
191+
("machine", &arch_config.machine),
192+
],
193+
)?;
194+
195+
// Define architecture-specific firmware paths
196+
let (code_path, nvram_template) = match arch_config.arch {
197+
"x86_64" => (
198+
"/usr/share/edk2/ovmf/OVMF_CODE.secboot.fd",
199+
"/usr/share/edk2/ovmf/OVMF_VARS.secboot.fd",
200+
),
201+
"aarch64" => (
202+
"/usr/share/edk2/aarch64/QEMU_EFI.fd",
203+
"/usr/share/edk2/aarch64/QEMU_VARS.fd",
204+
),
205+
_ => {
206+
return Err(eyre!(
207+
"Secure boot not supported for architecture: {}",
208+
arch_config.arch
209+
));
210+
}
211+
};
212+
213+
writer.write_text_element_with_attrs(
214+
"loader",
215+
code_path,
216+
&[("readonly", "yes"), ("type", "pflash"), ("secure", "yes")],
217+
)?;
218+
219+
// Generate per-domain NVRAM path
220+
let nvram_path = format!("/var/lib/libvirt/qemu/nvram/{}_VARS.fd", &name);
221+
writer.write_text_element_with_attrs(
222+
"nvram",
223+
&nvram_path,
224+
&[("template", nvram_template)],
225+
)?;
226+
227+
writer.write_empty_element("boot", &[("dev", "hd")])?;
228+
} else if use_uefi {
229+
// Regular UEFI without secure boot
230+
writer.start_element("os", &[("firmware", "efi")])?;
231+
writer.write_text_element_with_attrs(
232+
"type",
233+
&arch_config.os_type,
234+
&[
235+
("arch", &arch_config.arch),
236+
("machine", &arch_config.machine),
237+
],
238+
)?;
239+
writer.write_empty_element("boot", &[("dev", "hd")])?;
240+
} else {
241+
// BIOS firmware
242+
writer.start_element("os", &[])?;
243+
writer.write_text_element_with_attrs(
244+
"type",
245+
&arch_config.os_type,
246+
&[
247+
("arch", &arch_config.arch),
248+
("machine", &arch_config.machine),
249+
],
250+
)?;
251+
writer.write_empty_element("boot", &[("dev", "hd")])?;
252+
}
174253

175254
// Add kernel arguments if specified (for direct boot)
176255
if let Some(ref kargs) = self.kernel_args {
@@ -283,6 +362,15 @@ impl DomainBuilder {
283362
writer.end_element("filesystem")?;
284363
}
285364

365+
// TPM device
366+
if self.tpm {
367+
writer.start_element("tpm", &[("model", "tpm-tis")])?;
368+
writer.start_element("backend", &[("type", "emulator"), ("version", "2.0")])?;
369+
writer.write_empty_element("device", &[("path", "/dev/tpm0")])?;
370+
writer.end_element("backend")?;
371+
writer.end_element("tpm")?;
372+
}
373+
286374
writer.end_element("devices")?;
287375

288376
// QEMU commandline section (if we have QEMU args)
@@ -437,6 +525,96 @@ mod tests {
437525
assert!(xml.contains("<timer name=\"rtc\""));
438526
}
439527

528+
#[test]
529+
fn test_secure_boot_configuration() {
530+
// Test secure boot enabled with uefi-secure
531+
let xml = DomainBuilder::new()
532+
.with_name("test-secure-boot")
533+
.with_firmware("uefi-secure")
534+
.build_xml()
535+
.unwrap();
536+
537+
// Should include explicit loader and nvram configuration
538+
assert!(xml.contains("loader"));
539+
assert!(xml.contains("nvram"));
540+
assert!(xml.contains("secure=\"yes\""));
541+
assert!(xml.contains("template="));
542+
543+
// Should include secure boot firmware paths based on architecture
544+
let arch = std::env::consts::ARCH;
545+
match arch {
546+
"x86_64" => {
547+
assert!(xml.contains("/usr/share/edk2/ovmf/OVMF_CODE.secboot.fd"));
548+
assert!(xml.contains("/usr/share/edk2/ovmf/OVMF_VARS.secboot.fd"));
549+
}
550+
"aarch64" => {
551+
assert!(xml.contains("/usr/share/edk2/aarch64/QEMU_EFI.fd"));
552+
assert!(xml.contains("/usr/share/edk2/aarch64/QEMU_VARS.fd"));
553+
}
554+
_ => {
555+
// Test should still pass for unsupported architectures
556+
}
557+
}
558+
559+
// Test regular UEFI without secure boot
560+
let xml_regular = DomainBuilder::new()
561+
.with_name("test-regular-uefi")
562+
.with_firmware("uefi")
563+
.build_xml()
564+
.unwrap();
565+
566+
// Should use libvirt auto firmware selection
567+
assert!(xml_regular.contains("firmware=\"efi\""));
568+
assert!(!xml_regular.contains("secure=\"yes\""));
569+
assert!(!xml_regular.contains("template="));
570+
571+
// Test BIOS firmware (no secure boot)
572+
let xml_bios = DomainBuilder::new()
573+
.with_name("test-bios")
574+
.with_firmware("bios")
575+
.build_xml()
576+
.unwrap();
577+
578+
// Should not have firmware="efi" or secure boot settings
579+
assert!(!xml_bios.contains("firmware=\"efi\""));
580+
assert!(!xml_bios.contains("secure=\"yes\""));
581+
}
582+
583+
#[test]
584+
fn test_tpm_configuration() {
585+
// Test TPM enabled (default)
586+
let xml = DomainBuilder::new()
587+
.with_name("test-tpm-enabled")
588+
.build_xml()
589+
.unwrap();
590+
591+
// Should include TPM device by default
592+
assert!(xml.contains("<tpm model=\"tpm-tis\">"));
593+
assert!(xml.contains("<backend type=\"emulator\" version=\"2.0\">"));
594+
assert!(xml.contains("<device path=\"/dev/tpm0\"/>"));
595+
596+
// Test TPM explicitly enabled
597+
let xml_enabled = DomainBuilder::new()
598+
.with_name("test-tpm-explicit")
599+
.with_tpm(true)
600+
.build_xml()
601+
.unwrap();
602+
603+
assert!(xml_enabled.contains("<tpm model=\"tpm-tis\">"));
604+
assert!(xml_enabled.contains("backend type=\"emulator\""));
605+
606+
// Test TPM disabled
607+
let xml_disabled = DomainBuilder::new()
608+
.with_name("test-tpm-disabled")
609+
.with_tpm(false)
610+
.build_xml()
611+
.unwrap();
612+
613+
// Should not contain TPM configuration
614+
assert!(!xml_disabled.contains("<tpm"));
615+
assert!(!xml_disabled.contains("backend type=\"emulator\""));
616+
}
617+
440618
#[test]
441619
fn test_virtiofs_filesystem_configuration() {
442620
// Test read-write virtiofs filesystem

crates/kit/src/libvirt/run.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@ pub struct LibvirtRunOpts {
6565
/// Mount host container storage (RO) at /run/virtiofs-mnt-hoststorage
6666
#[clap(long = "bind-storage-ro")]
6767
pub bind_storage_ro: bool,
68+
69+
/// Firmware type for the VM ("uefi", "uefi-secure", or "bios", defaults to "uefi")
70+
#[clap(long, default_value = "uefi")]
71+
pub firmware: String,
72+
73+
/// Disable TPM 2.0 support (enabled by default)
74+
#[clap(long)]
75+
pub disable_tpm: bool,
6876
}
6977

7078
/// Execute the libvirt run command
@@ -542,6 +550,8 @@ fn create_libvirt_domain_from_disk(
542550
.with_vcpus(opts.cpus)
543551
.with_disk(disk_path.as_str())
544552
.with_network("none") // Use QEMU args for SSH networking instead
553+
.with_firmware(&opts.firmware)
554+
.with_tpm(!opts.disable_tpm)
545555
.with_metadata("bootc:source-image", &opts.image)
546556
.with_metadata("bootc:memory-mb", &opts.memory.to_string())
547557
.with_metadata("bootc:vcpus", &opts.cpus.to_string())

docs/src/man/bcvk-libvirt-create.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,16 @@ Create and start domains from uploaded bootc volumes
8585

8686
Size of the disk image for automatic upload (e.g., '20G', '10240M')
8787

88+
**--firmware**=*FIRMWARE*
89+
90+
Firmware type for the VM (\"uefi\", \"uefi-secure\", or \"bios\", defaults to \"uefi\")
91+
92+
Default: uefi
93+
94+
**--disable-tpm**
95+
96+
Disable TPM 2.0 support (enabled by default)
97+
8898
**--memory**=*MEMORY*
8999

90100
Memory size (e.g. 4G, 2048M, or plain number for MB)

docs/src/man/bcvk-libvirt-run.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,16 @@ Run a bootable container as a persistent VM
7373

7474
Mount host container storage (RO) at /run/virtiofs-mnt-hoststorage
7575

76+
**--firmware**=*FIRMWARE*
77+
78+
Firmware type for the VM ("uefi", "uefi-secure", or "bios", defaults to "uefi")
79+
80+
Default: uefi
81+
82+
**--disable-tpm**
83+
84+
Disable TPM 2.0 support (enabled by default)
85+
7686
<!-- END GENERATED OPTIONS -->
7787

7888
# EXAMPLES

0 commit comments

Comments
 (0)