From 4b12dd11f0dae6db903386e4358b87098e690d46 Mon Sep 17 00:00:00 2001 From: Nadiia Dubchak Date: Wed, 17 Dec 2025 18:18:23 +0000 Subject: [PATCH 01/21] Started implementing custom pcrlock policy path --- crates/osutils/src/dependencies.rs | 6 + crates/osutils/src/encryption.rs | 4 +- crates/osutils/src/pcrlock.rs | 105 ++++++++++-------- crates/trident/src/engine/rollback.rs | 13 ++- .../trident/src/engine/storage/encryption.rs | 19 +++- crates/trident/src/engine/storage/mod.rs | 2 + .../src/subsystems/storage/encryption.rs | 24 ++-- crates/trident_api/src/error.rs | 3 + 8 files changed, 116 insertions(+), 60 deletions(-) diff --git a/crates/osutils/src/dependencies.rs b/crates/osutils/src/dependencies.rs index 6d8907ccb..ef4f98321 100644 --- a/crates/osutils/src/dependencies.rs +++ b/crates/osutils/src/dependencies.rs @@ -261,6 +261,12 @@ impl Command { self.output()?.check_output() } + pub fn output_and_stderr_and_check(&self) -> Result<(String, String), Box> { + let output = self.output()?; + let stdout = output.check_output()?; + Ok((stdout, output.error_output())) + } + pub fn raw_output_and_check(&self) -> Result> { self.output()?.check_raw_output() } diff --git a/crates/osutils/src/encryption.rs b/crates/osutils/src/encryption.rs index 5e2646a8c..598a9472d 100644 --- a/crates/osutils/src/encryption.rs +++ b/crates/osutils/src/encryption.rs @@ -8,7 +8,7 @@ use anyhow::{Context, Error}; use enumflags2::BitFlags; use log::debug; -use crate::{dependencies::Dependency, pcrlock::PCRLOCK_POLICY_JSON_PATH}; +use crate::{dependencies::Dependency, pcrlock::PCRLOCK_POLICY_JSON}; use sysdefs::tpm2::Pcr; use trident_api::constants::LUKS_HEADER_SIZE_IN_MIB; @@ -51,7 +51,7 @@ pub fn systemd_cryptenroll( if let Some(pcrs) = pcrs { cmd.arg(to_tpm2_pcrs_arg(pcrs)); } else { - cmd.arg(format!("--tpm2-pcrlock={PCRLOCK_POLICY_JSON_PATH}")); + cmd.arg(format!("--tpm2-pcrlock={PCRLOCK_POLICY_JSON}")); } cmd.run_and_check().context(format!( diff --git a/crates/osutils/src/pcrlock.rs b/crates/osutils/src/pcrlock.rs index 90285cc5b..f6b254afe 100644 --- a/crates/osutils/src/pcrlock.rs +++ b/crates/osutils/src/pcrlock.rs @@ -31,9 +31,10 @@ use crate::{ /// the files exclusively in this directory. pub const PCRLOCK_DIR: &str = "/var/lib/pcrlock.d"; -/// Path to the pcrlock policy JSON file. This represents the TPM 2.0 access policy that has been -/// generated by `systemd-pcrlock`. -pub const PCRLOCK_POLICY_JSON_PATH: &str = "/var/lib/systemd/pcrlock.json"; +/// Name of the pcrlock policy JSON file that represents the TPM 2.0 access policy that has been +/// generated by `systemd-pcrlock`. This file is placed into the directory adjacent to the +/// datastore. +pub const PCRLOCK_POLICY_JSON: &str = "pcrlock.json"; /// `/var/lib/pcrlock.d/630-boot-loader-code-shim.pcrlock.d`, where `lock-pe` measures the shim /// bootloader binary, i.e. `/EFI/AZL{A/B}/bootx64.efi`, as recorded into PCR 4 following @@ -67,6 +68,7 @@ struct PcrPolicy { /// Generates a new pcrlock policy for the given PCRs, UKI binaries, and bootloader binaries. pub fn generate_pcrlock_policy( pcrs: BitFlags, + pcrlock_policy_path: &Path, uki_binaries: Vec, bootloader_binaries: Vec, ) -> Result<(), TridentError> { @@ -81,15 +83,37 @@ pub fn generate_pcrlock_policy( // Generate pcrlock policy; on A/B update, the existing binding will be automatically // updated with the new pcrlock policy - generate_tpm2_access_policy(pcrs).structured(ServicingError::GenerateTpm2AccessPolicy)?; + generate_tpm2_access_policy(pcrs, pcrlock_policy_path) + .structured(ServicingError::GenerateTpm2AccessPolicy)?; Ok(()) } +pub fn construct_pcrlock_path(datastore_path: &Path) -> Result { + // Fetch the directory path from the full datastore path + let Some(datastore_dir) = datastore_path.parent() else { + bail!( + "Failed to get parent directory for datastore path '{}'", + datastore_path.display() + ); + }; + + // Construct full path to pcrlock policy JSON file + let pcrlock_policy_path = datastore_dir.join(PCRLOCK_POLICY_JSON); + trace!( + "Constructed full pcrlock policy JSON path at '{}'", + pcrlock_policy_path.display() + ); + Ok(pcrlock_policy_path) +} + /// Calls a helper function `systemd-pcrlock make-policy` to generate a TPM 2.0 access policy. /// Parses the contents of the JSON to validate that the pcrlock policy has been updated as /// expected. -fn generate_tpm2_access_policy(pcrs: BitFlags) -> Result<(), Error> { +fn generate_tpm2_access_policy( + pcrs: BitFlags, + pcrlock_policy_path: &Path, +) -> Result<(), Error> { debug!( "Generating a new TPM 2.0 access policy with the following PCRs: {:?}", pcrs.iter().map(|pcr| pcr.to_num()).collect::>() @@ -102,13 +126,13 @@ fn generate_tpm2_access_policy(pcrs: BitFlags) -> Result<(), Error> { { let host_root = container::get_host_root_path().unstructured("Failed to get host root path")?; - let host_pcrlock_json_path = path::join_relative(host_root, PCRLOCK_POLICY_JSON_PATH); + let host_pcrlock_json_path = path::join_relative(host_root, pcrlock_policy_path); if host_pcrlock_json_path.exists() { debug!("Running inside of a container, so copying pcrlock policy JSON from the host at '{}' into the container at '{}'", host_pcrlock_json_path.display(), - PCRLOCK_POLICY_JSON_PATH + pcrlock_policy_path.display() ); - fs::copy(host_pcrlock_json_path, PCRLOCK_POLICY_JSON_PATH) + fs::copy(host_pcrlock_json_path, pcrlock_policy_path) .context("Failed to copy pcrlock policy JSON from host to container")?; } } @@ -116,15 +140,17 @@ fn generate_tpm2_access_policy(pcrs: BitFlags) -> Result<(), Error> { // Run predict command to view predictions, to then compare to the generated pcrlock policy predict().context("Failed to run 'systemd-pcrlock predict' command")?; - make_policy(pcrs).context("Failed to run 'systemd-pcrlock make-policy' command")?; + make_policy(pcrs, pcrlock_policy_path) + .context("Failed to run 'systemd-pcrlock make-policy' command")?; // Log pcrlock policy JSON contents - let pcrlock_policy = fs::read_to_string(PCRLOCK_POLICY_JSON_PATH).context(format!( - "Failed to read pcrlock policy JSON at path '{PCRLOCK_POLICY_JSON_PATH}'" + let pcrlock_policy = fs::read_to_string(pcrlock_policy_path).context(format!( + "Failed to read pcrlock policy JSON at path '{}'", + pcrlock_policy_path.display() ))?; trace!( "Contents of pcrlock policy JSON at '{}':\n{}", - PCRLOCK_POLICY_JSON_PATH, + pcrlock_policy_path.display(), pcrlock_policy ); @@ -134,12 +160,12 @@ fn generate_tpm2_access_policy(pcrs: BitFlags) -> Result<(), Error> { { let host_root = container::get_host_root_path().unstructured("Failed to get host root path")?; - let host_pcrlock_json_path = path::join_relative(host_root, PCRLOCK_POLICY_JSON_PATH); + let host_pcrlock_json_path = path::join_relative(host_root, pcrlock_policy_path); debug!("Running inside of a container, so copying pcrlock policy JSON from the container at '{}' onto the host at '{}'", - PCRLOCK_POLICY_JSON_PATH, + pcrlock_policy_path.display(), host_pcrlock_json_path.display() ); - fs::copy(PCRLOCK_POLICY_JSON_PATH, host_pcrlock_json_path) + fs::copy(pcrlock_policy_path, host_pcrlock_json_path) .context("Failed to copy pcrlock policy JSON from container to host")?; } @@ -304,43 +330,30 @@ fn unrecognized_log_entries( /// Runs `systemd-pcrlock make-policy` command to predict the PCR state for future boots and then /// generate a TPM 2.0 access policy, stored in a TPM 2.0 NV index. The prediction and info about -/// the used TPM 2.0 and its NV index are written to PCRLOCK_POLICY_JSON_PATH. -fn make_policy(pcrs: BitFlags) -> Result<(), Error> { +/// the used TPM 2.0 and its NV index are written to pcrlock_policy_path. +fn make_policy(pcrs: BitFlags, pcrlock_policy_path: &Path) -> Result<(), Error> { debug!( "Running 'systemd-pcrlock make-policy' command to make a new pcrlock policy \ with the following PCRs: {:?}", pcrs.iter().map(|pcr| pcr.to_num()).collect::>() ); - // Run command directly since pcrlock may write to stderr even when a pcrlock policy is - // successfully generated - let mut cmd = Command::new("/usr/lib/systemd/systemd-pcrlock"); - cmd.arg("make-policy").arg(to_pcr_arg(pcrs)); - - // Execute command and capture full output - let output = cmd - .output() + // Use the dependency system to execute the command and capture both stdout and stderr + let (stdout_str, stderr_str) = Dependency::SystemdPcrlock + .cmd() + .arg("make-policy") + .arg(to_pcr_arg(pcrs)) + .arg(format!("--policy={}", pcrlock_policy_path.display())) + .output_and_stderr_and_check() .context("Failed to execute 'systemd-pcrlock make-policy' command")?; - // Check exit status using standard fields - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - warn!( - "Command 'systemd-pcrlock make-policy' failed with status {}: {}", - output.status, stderr - ); - } - - // Convert stdout to UTF-8 - let stdout_str = String::from_utf8(output.stdout) - .context("Failed to convert stdout of 'systemd-pcrlock make-policy' to a string as it contains invalid UTF-8")?; - let stderr_str = String::from_utf8(output.stderr) - .context("Failed to convert stderr of 'systemd-pcrlock make-policy' to a string as it contains invalid UTF-8")?; - // Log both outputs debug!( - "Output of 'systemd-pcrlock make-policy':\nSTDOUT:\n{}\nSTDERR:\n{}", - stdout_str, stderr_str + "Output of 'systemd-pcrlock make-policy --policy={} --pcr={}':\nSTDOUT:\n{}\nSTDERR:\n{}", + pcrlock_policy_path.display(), + to_pcr_arg(pcrs), + stdout_str, + stderr_str ); // Join stdout and stderr for parsing, since systemd-pcrlock will output to stderr even when we // don't get an error, e.g. when components for PCRs we don't care about aren't recognized @@ -372,9 +385,9 @@ fn predict() -> Result<(), Error> { } /// Removes the previously generated pcrlock policy and deallocates the NV index. -pub fn remove_policy() -> Result<(), Error> { +pub fn remove_policy(pcrlock_policy_path: &Path) -> Result<(), Error> { // Remove the pcrlock policy - let mut pcrlock_policy = vec![PathBuf::from(PCRLOCK_POLICY_JSON_PATH)]; + let mut pcrlock_policy = vec![PathBuf::from(pcrlock_policy_path)]; // If running from inside a container, also remove the pcrlock policy on the host if container::is_running_in_container() @@ -382,7 +395,7 @@ pub fn remove_policy() -> Result<(), Error> { { let host_root = container::get_host_root_path().unstructured("Failed to get host root path")?; - let host_pcrlock_json_path = path::join_relative(host_root, PCRLOCK_POLICY_JSON_PATH); + let host_pcrlock_json_path = path::join_relative(host_root, pcrlock_policy_path); // Append this host path to vector pcrlock_policy.push(host_pcrlock_json_path); } @@ -790,6 +803,6 @@ mod functional_test { ); // Clean up the generated pcrlock policy - fs::remove_file(PCRLOCK_POLICY_JSON_PATH).unwrap(); + fs::remove_file(PCRLOCK_POLICY_JSON).unwrap(); } } diff --git a/crates/trident/src/engine/rollback.rs b/crates/trident/src/engine/rollback.rs index f46593de8..5f60501c2 100644 --- a/crates/trident/src/engine/rollback.rs +++ b/crates/trident/src/engine/rollback.rs @@ -253,8 +253,19 @@ fn commit_finalized_on_expected_root( encryption::get_binary_paths_pcrlock(ctx, pcrs, None) .structured(ServicingError::GetBinaryPathsForPcrlockEncryption)?; + // Construct full path to pcrlock policy JSON file + let pcrlock_policy_path = pcrlock::construct_pcrlock_path( + &datastore.host_status().spec.trident.datastore_path, + ) + .structured(ServicingError::ConstructPcrlockPolicyPath)?; + // Generate a pcrlock policy - pcrlock::generate_pcrlock_policy(pcrs, uki_binaries, bootloader_binaries)?; + pcrlock::generate_pcrlock_policy( + pcrs, + &pcrlock_policy_path, + uki_binaries, + bootloader_binaries, + )?; } else { debug!( "Target OS image is a grub image, \ diff --git a/crates/trident/src/engine/storage/encryption.rs b/crates/trident/src/engine/storage/encryption.rs index f38611098..37beb9544 100644 --- a/crates/trident/src/engine/storage/encryption.rs +++ b/crates/trident/src/engine/storage/encryption.rs @@ -17,7 +17,7 @@ use osutils::{ encryption::{self, KeySlotType}, lsblk::{self, BlockDeviceType}, path::join_relative, - pcrlock, + pcrlock::{self, PCRLOCK_POLICY_JSON}, }; use sysdefs::tpm2::Pcr; use trident_api::{ @@ -31,7 +31,7 @@ use trident_api::{ }; use crate::{ - bootentries, + bootentries, datastore, engine::{ boot::{self, uki}, storage::encryption::uki::{TMP_UKI_NAME, UKI_DIRECTORY}, @@ -122,11 +122,22 @@ pub(super) fn create_encrypted_devices(ctx: &EngineContext) -> Result<(), Triden let pcr = if ctx.is_uki()? { debug!("Target OS image is a UKI image, so sealing against a pcrlock policy of PCR 0"); + // Construct full path to pcrlock policy JSON file + let pcrlock_policy_path = + pcrlock::construct_pcrlock_path(&ctx.spec.trident.datastore_path) + .structured(ServicingError::ConstructPcrlockPolicyPath)?; + // Remove any pre-existing policy - pcrlock::remove_policy().structured(ServicingError::RemovePcrlockPolicy)?; + pcrlock::remove_policy(&pcrlock_policy_path) + .structured(ServicingError::RemovePcrlockPolicy)?; // Generate a pcrlock policy for the first time - pcrlock::generate_pcrlock_policy(BitFlags::from(Pcr::Pcr0), vec![], vec![])?; + pcrlock::generate_pcrlock_policy( + BitFlags::from(Pcr::Pcr0), + &pcrlock_policy_path, + vec![], + vec![], + )?; None } else { debug!("Target OS image is a grub image, so sealing against PCR 7"); diff --git a/crates/trident/src/engine/storage/mod.rs b/crates/trident/src/engine/storage/mod.rs index be10dc58c..b79f24552 100644 --- a/crates/trident/src/engine/storage/mod.rs +++ b/crates/trident/src/engine/storage/mod.rs @@ -1,3 +1,5 @@ +use std::path::Path; + use log::{debug, info, trace, warn}; use osutils::{e2fsck, lsblk}; diff --git a/crates/trident/src/subsystems/storage/encryption.rs b/crates/trident/src/subsystems/storage/encryption.rs index a3333fb32..aba76fce8 100644 --- a/crates/trident/src/subsystems/storage/encryption.rs +++ b/crates/trident/src/subsystems/storage/encryption.rs @@ -9,7 +9,7 @@ use log::{debug, info, trace}; use osutils::{ container, efivar, encryption as osutils_encryption, files, path, - pcrlock::{self, PCRLOCK_POLICY_JSON_PATH}, + pcrlock::{self, PCRLOCK_POLICY_JSON}, }; use sysdefs::tpm2::Pcr; use trident_api::{ @@ -188,21 +188,31 @@ pub fn provision(ctx: &EngineContext, mount_path: &Path) -> Result<(), TridentEr engine_encryption::get_binary_paths_pcrlock(ctx, pcrs, Some(mount_path)) .structured(ServicingError::GetBinaryPathsForPcrlockEncryption)?; + // Construct full path to pcrlock policy JSON file + let pcrlock_policy_path = + pcrlock::construct_pcrlock_path(&ctx.spec.trident.datastore_path) + .structured(ServicingError::ConstructPcrlockPolicyPath)?; + // Re-generate pcrlock policy - pcrlock::generate_pcrlock_policy(pcrs, uki_binaries, bootloader_binaries)?; + pcrlock::generate_pcrlock_policy( + pcrs, + &pcrlock_policy_path, + uki_binaries, + bootloader_binaries, + )?; } // If a pcrlock policy JSON file exists, copy it to the update volume - if Path::new(PCRLOCK_POLICY_JSON_PATH).exists() { - let pcrlock_json_copy = path::join_relative(mount_path, PCRLOCK_POLICY_JSON_PATH); + if Path::new(PCRLOCK_POLICY_JSON).exists() { + let pcrlock_json_copy = path::join_relative(mount_path, PCRLOCK_POLICY_JSON); debug!( "Copying pcrlock policy JSON from path '{}' to update volume at path '{}'", - PCRLOCK_POLICY_JSON_PATH, + PCRLOCK_POLICY_JSON, pcrlock_json_copy.display() ); - fs::copy(PCRLOCK_POLICY_JSON_PATH, pcrlock_json_copy.clone()).structured( + fs::copy(PCRLOCK_POLICY_JSON, pcrlock_json_copy.clone()).structured( ServicingError::CopyPcrlockPolicyJson { - path: PCRLOCK_POLICY_JSON_PATH.to_string(), + path: PCRLOCK_POLICY_JSON.to_string(), destination: pcrlock_json_copy.display().to_string(), }, )?; diff --git a/crates/trident_api/src/error.rs b/crates/trident_api/src/error.rs index 87b8f808b..21d469193 100644 --- a/crates/trident_api/src/error.rs +++ b/crates/trident_api/src/error.rs @@ -370,6 +370,9 @@ pub enum ServicingError { explanation: String, }, + #[error("Failed to construct pcrlock policy path in directory adjacent to the datastore")] + ConstructPcrlockPolicyPath, + #[error("Failed to create extension image directories on target OS")] CreateExtensionImageDirectories, From 645c074645e659c47bb10cf09a9ee44fcadfb348 Mon Sep 17 00:00:00 2001 From: Nadiia Dubchak Date: Wed, 17 Dec 2025 19:41:39 +0000 Subject: [PATCH 02/21] Updated --- crates/osutils/src/dependencies.rs | 2 + crates/osutils/src/encryption.rs | 90 ++++++++++++++----- crates/osutils/src/pcrlock.rs | 9 +- .../trident/src/engine/storage/encryption.rs | 27 +++--- crates/trident/src/engine/storage/mod.rs | 2 - .../src/subsystems/storage/encryption.rs | 24 +++-- 6 files changed, 104 insertions(+), 50 deletions(-) diff --git a/crates/osutils/src/dependencies.rs b/crates/osutils/src/dependencies.rs index ef4f98321..4af83600b 100644 --- a/crates/osutils/src/dependencies.rs +++ b/crates/osutils/src/dependencies.rs @@ -121,6 +121,8 @@ pub enum Dependency { SystemdConfext, #[strum(serialize = "systemd-cryptenroll")] SystemdCryptenroll, + #[strum(serialize = "systemd-cryptsetup")] + SystemdCryptsetup, #[strum(serialize = "systemd-firstboot")] SystemdFirstboot, #[strum(serialize = "systemd-pcrlock")] diff --git a/crates/osutils/src/encryption.rs b/crates/osutils/src/encryption.rs index 598a9472d..437052af9 100644 --- a/crates/osutils/src/encryption.rs +++ b/crates/osutils/src/encryption.rs @@ -8,7 +8,7 @@ use anyhow::{Context, Error}; use enumflags2::BitFlags; use log::debug; -use crate::{dependencies::Dependency, pcrlock::PCRLOCK_POLICY_JSON}; +use crate::dependencies::Dependency; use sysdefs::tpm2::Pcr; use trident_api::constants::LUKS_HEADER_SIZE_IN_MIB; @@ -34,6 +34,7 @@ pub fn systemd_cryptenroll( key_file: impl AsRef, device_path: impl AsRef, pcrs: Option>, + pcrlock_policy_path: Option<&Path>, ) -> Result<(), Error> { debug!( "Enrolling TPM 2.0 device for underlying encrypted volume '{}'", @@ -50,8 +51,8 @@ pub fn systemd_cryptenroll( // against a pcrlock policy. if let Some(pcrs) = pcrs { cmd.arg(to_tpm2_pcrs_arg(pcrs)); - } else { - cmd.arg(format!("--tpm2-pcrlock={PCRLOCK_POLICY_JSON}")); + } else if let Some(pcrlock_policy_path) = pcrlock_policy_path { + cmd.arg(format!("--tpm2-pcrlock={}", pcrlock_policy_path.display())); } cmd.run_and_check().context(format!( @@ -177,20 +178,34 @@ pub fn cryptsetup_open( key_file: impl AsRef, device_path: impl AsRef, device_name: &str, + pcrlock_policy_path: Option<&Path>, ) -> Result<(), Error> { - Dependency::Cryptsetup - .cmd() - .arg("luksOpen") + debug!( + "Opening underlying encrypted device '{}' as '{}'", + device_path.as_ref().display(), + device_name + ); + + let mut cmd = Dependency::Cryptsetup.cmd(); + cmd.arg("luksOpen") .arg("--key-file") .arg(key_file.as_ref().as_os_str()) .arg(device_path.as_ref().as_os_str()) - .arg(device_name) - .run_and_check() - .context(format!( - "Failed to open underlying encrypted device '{}' as '{}'", - device_path.as_ref().display(), - device_name - )) + .arg(device_name); + + // If provided, specify custom pcrlock policy path + if let Some(pcrlock_policy_path) = pcrlock_policy_path { + cmd.arg(format!( + "--pcrlock-policy={}", + pcrlock_policy_path.display() + )); + } + + cmd.run_and_check().context(format!( + "Failed to open underlying encrypted device '{}' as '{}'", + device_path.as_ref().display(), + device_name + )) } /// Runs `cryptsetup luksClose` to close the given LUKS2 device. @@ -337,6 +352,7 @@ mod functional_test { use pytest_gen::functional_test; use sysdefs::partition_types::DiscoverablePartitionType; + use trident_api::constants::TRIDENT_DATASTORE_PATH_DEFAULT; use crate::{ filesystems::MkfsFileSystemType, @@ -416,13 +432,27 @@ mod functional_test { copy_static_pcrlock_files(); // Generate a pcrlock policy that only includes PCR 0 let pcrs = BitFlags::from(Pcr::Pcr0); - pcrlock::generate_pcrlock_policy(pcrs, vec![], vec![]).unwrap(); + let pcrlock_policy_path = + pcrlock::construct_pcrlock_path(Path::new(TRIDENT_DATASTORE_PATH_DEFAULT)).unwrap(); + pcrlock::generate_pcrlock_policy(pcrs, &pcrlock_policy_path, vec![], vec![]).unwrap(); // Run `systemd-cryptenroll` on the partition - systemd_cryptenroll(key_file_path, &partition1.node, None).unwrap(); + systemd_cryptenroll( + key_file_path, + &partition1.node, + None, + Some(&pcrlock_policy_path), + ) + .unwrap(); // Open the encrypted volume, to make the block device available - cryptsetup_open(key_file_path, &partition1.node, ENCRYPTED_VOLUME_NAME).unwrap(); + cryptsetup_open( + key_file_path, + &partition1.node, + ENCRYPTED_VOLUME_NAME, + Some(&pcrlock_policy_path), + ) + .unwrap(); // Format the unlocked volume with ext4 mkfs::run(Path::new(ENCRYPTED_VOLUME_PATH), MkfsFileSystemType::Ext4).unwrap(); @@ -471,7 +501,13 @@ mod functional_test { cryptsetup_close(ENCRYPTED_VOLUME_NAME).unwrap(); // Re-open the encrypted volume - cryptsetup_open(key_file_path, &partition1.node, ENCRYPTED_VOLUME_NAME).unwrap(); + cryptsetup_open( + key_file_path, + &partition1.node, + ENCRYPTED_VOLUME_NAME, + Some(&pcrlock_policy_path), + ) + .unwrap(); // Re-mount the encrypted volume Dependency::Mount @@ -558,13 +594,27 @@ mod functional_test { copy_static_pcrlock_files(); // Generate a pcrlock policy that only includes PCR 0 let pcrs = BitFlags::from(Pcr::Pcr0); - pcrlock::generate_pcrlock_policy(pcrs, vec![], vec![]).unwrap(); + let pcrlock_policy_path = + pcrlock::construct_pcrlock_path(Path::new(TRIDENT_DATASTORE_PATH_DEFAULT)).unwrap(); + pcrlock::generate_pcrlock_policy(pcrs, &pcrlock_policy_path, vec![], vec![]).unwrap(); // Run `systemd-cryptenroll` on the partition - systemd_cryptenroll(key_file_path, &partition1.node, None).unwrap(); + systemd_cryptenroll( + key_file_path, + &partition1.node, + None, + Some(&pcrlock_policy_path), + ) + .unwrap(); // Open the encrypted volume, to make the block device available - cryptsetup_open(key_file_path, &partition1.node, ENCRYPTED_VOLUME_NAME).unwrap(); + cryptsetup_open( + key_file_path, + &partition1.node, + ENCRYPTED_VOLUME_NAME, + Some(&pcrlock_policy_path), + ) + .unwrap(); // Verify the test data exists at the expected offset let mut decrypted_device = OpenOptions::new() diff --git a/crates/osutils/src/pcrlock.rs b/crates/osutils/src/pcrlock.rs index f6b254afe..5a543ce9c 100644 --- a/crates/osutils/src/pcrlock.rs +++ b/crates/osutils/src/pcrlock.rs @@ -783,19 +783,22 @@ mod functional_test { use super::*; use pytest_gen::functional_test; + use trident_api::constants::TRIDENT_DATASTORE_PATH_DEFAULT; #[functional_test(feature = "helpers")] fn test_generate_tpm2_access_policy() { // Test case #0. Since no .pcrlock files have been generated yet, only 0-valued PCRs can be // used to generate a TPM 2.0 access policy. let zero_pcrs = Pcr::Pcr11 | Pcr::Pcr12 | Pcr::Pcr13; - generate_tpm2_access_policy(zero_pcrs).unwrap(); + let pcrlock_policy_path = + construct_pcrlock_path(Path::new(TRIDENT_DATASTORE_PATH_DEFAULT)).unwrap(); + generate_tpm2_access_policy(zero_pcrs, &pcrlock_policy_path).unwrap(); // Test case #1. Try to generate a TPM 2.0 access policy with all PCRs; should return an // error since no .pcrlock files have been generated yet. let pcrs = BitFlags::::all(); assert_eq!( - generate_tpm2_access_policy(pcrs) + generate_tpm2_access_policy(pcrs, &pcrlock_policy_path) .unwrap_err() .root_cause() .to_string(), @@ -803,6 +806,6 @@ mod functional_test { ); // Clean up the generated pcrlock policy - fs::remove_file(PCRLOCK_POLICY_JSON).unwrap(); + remove_policy(&pcrlock_policy_path).unwrap(); } } diff --git a/crates/trident/src/engine/storage/encryption.rs b/crates/trident/src/engine/storage/encryption.rs index 37beb9544..0b61395fa 100644 --- a/crates/trident/src/engine/storage/encryption.rs +++ b/crates/trident/src/engine/storage/encryption.rs @@ -17,7 +17,7 @@ use osutils::{ encryption::{self, KeySlotType}, lsblk::{self, BlockDeviceType}, path::join_relative, - pcrlock::{self, PCRLOCK_POLICY_JSON}, + pcrlock, }; use sysdefs::tpm2::Pcr; use trident_api::{ @@ -31,7 +31,7 @@ use trident_api::{ }; use crate::{ - bootentries, datastore, + bootentries, engine::{ boot::{self, uki}, storage::encryption::uki::{TMP_UKI_NAME, UKI_DIRECTORY}, @@ -119,7 +119,7 @@ pub(super) fn create_encrypted_devices(ctx: &EngineContext) -> Result<(), Triden // If this is for a grub ROS, seal against the value of PCR 7; if this is for a UKI ROS, // seal against a "bootstrapping" pcrlock policy that exclusively contains PCR 0. - let pcr = if ctx.is_uki()? { + let (pcr, pcrlock_policy_path) = if ctx.is_uki()? { debug!("Target OS image is a UKI image, so sealing against a pcrlock policy of PCR 0"); // Construct full path to pcrlock policy JSON file @@ -138,14 +138,17 @@ pub(super) fn create_encrypted_devices(ctx: &EngineContext) -> Result<(), Triden vec![], vec![], )?; - None + (None, Some(pcrlock_policy_path)) } else { debug!("Target OS image is a grub image, so sealing against PCR 7"); - Some( - encryption - .pcrs - .iter() - .fold(BitFlags::empty(), |acc, &pcr| acc | BitFlags::from(pcr)), + ( + Some( + encryption + .pcrs + .iter() + .fold(BitFlags::empty(), |acc, &pcr| acc | BitFlags::from(pcr)), + ), + None, ) }; @@ -196,6 +199,7 @@ pub(super) fn create_encrypted_devices(ctx: &EngineContext) -> Result<(), Triden &key_file_path, encryption_type, pcr, + pcrlock_policy_path.as_deref(), ) .structured(ServicingError::EncryptBlockDevice { device_path: device_path.to_string_lossy().to_string(), @@ -246,6 +250,7 @@ fn encrypt_and_open_device( key_file: &Path, encryption_type: EncryptionType, pcr: Option>, + pcrlock_policy_path: Option<&Path>, ) -> Result<(), Error> { match encryption_type { EncryptionType::Reencrypt => { @@ -276,7 +281,7 @@ fn encrypt_and_open_device( ); // Enroll the TPM 2.0 device for the underlying device - encryption::systemd_cryptenroll(key_file, device_path, pcr)?; + encryption::systemd_cryptenroll(key_file, device_path, pcr, pcrlock_policy_path)?; debug!( "Opening underlying encrypted device '{}' as '{}'", @@ -284,7 +289,7 @@ fn encrypt_and_open_device( device_name ); - encryption::cryptsetup_open(key_file, device_path, device_name)?; + encryption::cryptsetup_open(key_file, device_path, device_name, pcrlock_policy_path)?; Ok(()) } diff --git a/crates/trident/src/engine/storage/mod.rs b/crates/trident/src/engine/storage/mod.rs index b79f24552..be10dc58c 100644 --- a/crates/trident/src/engine/storage/mod.rs +++ b/crates/trident/src/engine/storage/mod.rs @@ -1,5 +1,3 @@ -use std::path::Path; - use log::{debug, info, trace, warn}; use osutils::{e2fsck, lsblk}; diff --git a/crates/trident/src/subsystems/storage/encryption.rs b/crates/trident/src/subsystems/storage/encryption.rs index aba76fce8..bf38361bb 100644 --- a/crates/trident/src/subsystems/storage/encryption.rs +++ b/crates/trident/src/subsystems/storage/encryption.rs @@ -7,10 +7,7 @@ use std::{ use enumflags2::BitFlags; use log::{debug, info, trace}; -use osutils::{ - container, efivar, encryption as osutils_encryption, files, path, - pcrlock::{self, PCRLOCK_POLICY_JSON}, -}; +use osutils::{container, efivar, encryption as osutils_encryption, files, path, pcrlock}; use sysdefs::tpm2::Pcr; use trident_api::{ config::{ @@ -177,6 +174,10 @@ pub fn provision(ctx: &EngineContext, mount_path: &Path) -> Result<(), TridentEr } }; + // Construct full path to pcrlock policy JSON file + let pcrlock_policy_path = pcrlock::construct_pcrlock_path(&ctx.spec.trident.datastore_path) + .structured(ServicingError::ConstructPcrlockPolicyPath)?; + // If updated PCRs are specified, re-generate pcrlock policy if let Some(pcrs) = updated_pcrs { debug!( @@ -188,11 +189,6 @@ pub fn provision(ctx: &EngineContext, mount_path: &Path) -> Result<(), TridentEr engine_encryption::get_binary_paths_pcrlock(ctx, pcrs, Some(mount_path)) .structured(ServicingError::GetBinaryPathsForPcrlockEncryption)?; - // Construct full path to pcrlock policy JSON file - let pcrlock_policy_path = - pcrlock::construct_pcrlock_path(&ctx.spec.trident.datastore_path) - .structured(ServicingError::ConstructPcrlockPolicyPath)?; - // Re-generate pcrlock policy pcrlock::generate_pcrlock_policy( pcrs, @@ -203,16 +199,16 @@ pub fn provision(ctx: &EngineContext, mount_path: &Path) -> Result<(), TridentEr } // If a pcrlock policy JSON file exists, copy it to the update volume - if Path::new(PCRLOCK_POLICY_JSON).exists() { - let pcrlock_json_copy = path::join_relative(mount_path, PCRLOCK_POLICY_JSON); + if pcrlock_policy_path.exists() { + let pcrlock_json_copy = path::join_relative(mount_path, &pcrlock_policy_path); debug!( "Copying pcrlock policy JSON from path '{}' to update volume at path '{}'", - PCRLOCK_POLICY_JSON, + pcrlock_policy_path.display(), pcrlock_json_copy.display() ); - fs::copy(PCRLOCK_POLICY_JSON, pcrlock_json_copy.clone()).structured( + fs::copy(&pcrlock_policy_path, pcrlock_json_copy.clone()).structured( ServicingError::CopyPcrlockPolicyJson { - path: PCRLOCK_POLICY_JSON.to_string(), + path: pcrlock_policy_path.display().to_string(), destination: pcrlock_json_copy.display().to_string(), }, )?; From 78d66f886eb2965e1a2bb0e2fbfb94610a917857 Mon Sep 17 00:00:00 2001 From: Nadiia Dubchak Date: Wed, 17 Dec 2025 19:43:26 +0000 Subject: [PATCH 03/21] Removed random AI bs --- crates/osutils/src/dependencies.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/osutils/src/dependencies.rs b/crates/osutils/src/dependencies.rs index 4af83600b..ef4f98321 100644 --- a/crates/osutils/src/dependencies.rs +++ b/crates/osutils/src/dependencies.rs @@ -121,8 +121,6 @@ pub enum Dependency { SystemdConfext, #[strum(serialize = "systemd-cryptenroll")] SystemdCryptenroll, - #[strum(serialize = "systemd-cryptsetup")] - SystemdCryptsetup, #[strum(serialize = "systemd-firstboot")] SystemdFirstboot, #[strum(serialize = "systemd-pcrlock")] From ae111c98c3759d1ab4a424fcbf5502c1634a9128 Mon Sep 17 00:00:00 2001 From: Nadiia Dubchak Date: Wed, 17 Dec 2025 19:57:21 +0000 Subject: [PATCH 04/21] Corrected arg --- crates/osutils/src/encryption.rs | 5 +---- crates/osutils/src/pcrlock.rs | 4 ++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/crates/osutils/src/encryption.rs b/crates/osutils/src/encryption.rs index 437052af9..4b112a0fc 100644 --- a/crates/osutils/src/encryption.rs +++ b/crates/osutils/src/encryption.rs @@ -195,10 +195,7 @@ pub fn cryptsetup_open( // If provided, specify custom pcrlock policy path if let Some(pcrlock_policy_path) = pcrlock_policy_path { - cmd.arg(format!( - "--pcrlock-policy={}", - pcrlock_policy_path.display() - )); + cmd.arg(format!("--tpm2-pcrlock={}", pcrlock_policy_path.display())); } cmd.run_and_check().context(format!( diff --git a/crates/osutils/src/pcrlock.rs b/crates/osutils/src/pcrlock.rs index 5a543ce9c..b7c649eda 100644 --- a/crates/osutils/src/pcrlock.rs +++ b/crates/osutils/src/pcrlock.rs @@ -29,12 +29,12 @@ use crate::{ /// /// `systemd-pcrlock` will search for .pcrlock files in a number of dir-s, but Trident will place /// the files exclusively in this directory. -pub const PCRLOCK_DIR: &str = "/var/lib/pcrlock.d"; +pub(crate) const PCRLOCK_DIR: &str = "/var/lib/pcrlock.d"; /// Name of the pcrlock policy JSON file that represents the TPM 2.0 access policy that has been /// generated by `systemd-pcrlock`. This file is placed into the directory adjacent to the /// datastore. -pub const PCRLOCK_POLICY_JSON: &str = "pcrlock.json"; +const PCRLOCK_POLICY_JSON: &str = "pcrlock.json"; /// `/var/lib/pcrlock.d/630-boot-loader-code-shim.pcrlock.d`, where `lock-pe` measures the shim /// bootloader binary, i.e. `/EFI/AZL{A/B}/bootx64.efi`, as recorded into PCR 4 following From e2a61159e5339731e8f7b8b36181efdc777df9c4 Mon Sep 17 00:00:00 2001 From: Nadiia Dubchak Date: Wed, 17 Dec 2025 21:31:01 +0000 Subject: [PATCH 05/21] Use correct arg --- crates/osutils/src/pcrlock.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/osutils/src/pcrlock.rs b/crates/osutils/src/pcrlock.rs index b7c649eda..7a2d4d2b6 100644 --- a/crates/osutils/src/pcrlock.rs +++ b/crates/osutils/src/pcrlock.rs @@ -343,13 +343,16 @@ fn make_policy(pcrs: BitFlags, pcrlock_policy_path: &Path) -> Result<(), Er .cmd() .arg("make-policy") .arg(to_pcr_arg(pcrs)) - .arg(format!("--policy={}", pcrlock_policy_path.display())) + // TODO: In v255, expected arg `--policy` is ignored and instead, `--pcrlock=` is + // respected. This issue has been fixed in v258: + // https://github.com/systemd/systemd/issues/38506. + .arg(format!("--pcrlock={}", pcrlock_policy_path.display())) .output_and_stderr_and_check() .context("Failed to execute 'systemd-pcrlock make-policy' command")?; // Log both outputs debug!( - "Output of 'systemd-pcrlock make-policy --policy={} --pcr={}':\nSTDOUT:\n{}\nSTDERR:\n{}", + "Output of 'systemd-pcrlock make-policy --pcrlock={} {}':\nSTDOUT:\n{}\nSTDERR:\n{}", pcrlock_policy_path.display(), to_pcr_arg(pcrs), stdout_str, @@ -364,8 +367,10 @@ fn make_policy(pcrs: BitFlags, pcrlock_policy_path: &Path) -> Result<(), Er if !output_str.contains("Calculated new PCR policy") || !output_str.contains("Updated NV index") { warn!( - "The 'systemd-pcrlock make-policy' command did not update the PCR policy as expected. \ + "The 'systemd-pcrlock make-policy --pcrlock={} {}' command did not update the PCR policy as expected. \ Output:\n{}", + pcrlock_policy_path.display(), + to_pcr_arg(pcrs), output_str ); } From 844c7aef636eb4540017e7e7e062b46848cc5393 Mon Sep 17 00:00:00 2001 From: Nadiia Dubchak Date: Wed, 17 Dec 2025 23:08:02 +0000 Subject: [PATCH 06/21] Tried fixing with a workaround --- crates/osutils/src/dependencies.rs | 8 +-- crates/osutils/src/encryption.rs | 51 ++++++------------- crates/osutils/src/pcrlock.rs | 12 +++++ .../trident/src/engine/storage/encryption.rs | 2 +- 4 files changed, 33 insertions(+), 40 deletions(-) diff --git a/crates/osutils/src/dependencies.rs b/crates/osutils/src/dependencies.rs index ef4f98321..901a9d53a 100644 --- a/crates/osutils/src/dependencies.rs +++ b/crates/osutils/src/dependencies.rs @@ -1,7 +1,7 @@ use std::{ borrow::Cow, ffi::{OsStr, OsString}, - io, + fmt, io, os::unix::process::ExitStatusExt, path::PathBuf, process::{Command as StdCommand, Output}, @@ -121,6 +121,8 @@ pub enum Dependency { SystemdConfext, #[strum(serialize = "systemd-cryptenroll")] SystemdCryptenroll, + #[strum(serialize = "systemd-cryptsetup")] + SystemdCryptsetup, #[strum(serialize = "systemd-firstboot")] SystemdFirstboot, #[strum(serialize = "systemd-pcrlock")] @@ -149,8 +151,8 @@ pub enum Dependency { False, } -impl std::fmt::Display for Dependency { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for Dependency { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.into()) } } diff --git a/crates/osutils/src/encryption.rs b/crates/osutils/src/encryption.rs index 4b112a0fc..89ea52bb2 100644 --- a/crates/osutils/src/encryption.rs +++ b/crates/osutils/src/encryption.rs @@ -52,6 +52,8 @@ pub fn systemd_cryptenroll( if let Some(pcrs) = pcrs { cmd.arg(to_tpm2_pcrs_arg(pcrs)); } else if let Some(pcrlock_policy_path) = pcrlock_policy_path { + // TODO: need to check container path existence? + cmd.arg(format!("--tpm2-pcrlock={}", pcrlock_policy_path.display())); } @@ -178,7 +180,6 @@ pub fn cryptsetup_open( key_file: impl AsRef, device_path: impl AsRef, device_name: &str, - pcrlock_policy_path: Option<&Path>, ) -> Result<(), Error> { debug!( "Opening underlying encrypted device '{}' as '{}'", @@ -186,23 +187,19 @@ pub fn cryptsetup_open( device_name ); - let mut cmd = Dependency::Cryptsetup.cmd(); - cmd.arg("luksOpen") + Dependency::Cryptsetup + .cmd() + .arg("luksOpen") .arg("--key-file") .arg(key_file.as_ref().as_os_str()) .arg(device_path.as_ref().as_os_str()) - .arg(device_name); - - // If provided, specify custom pcrlock policy path - if let Some(pcrlock_policy_path) = pcrlock_policy_path { - cmd.arg(format!("--tpm2-pcrlock={}", pcrlock_policy_path.display())); - } - - cmd.run_and_check().context(format!( - "Failed to open underlying encrypted device '{}' as '{}'", - device_path.as_ref().display(), - device_name - )) + .arg(device_name) + .run_and_check() + .context(format!( + "Failed to open underlying encrypted device '{}' as '{}'", + device_path.as_ref().display(), + device_name + )) } /// Runs `cryptsetup luksClose` to close the given LUKS2 device. @@ -443,13 +440,7 @@ mod functional_test { .unwrap(); // Open the encrypted volume, to make the block device available - cryptsetup_open( - key_file_path, - &partition1.node, - ENCRYPTED_VOLUME_NAME, - Some(&pcrlock_policy_path), - ) - .unwrap(); + cryptsetup_open(key_file_path, &partition1.node, ENCRYPTED_VOLUME_NAME).unwrap(); // Format the unlocked volume with ext4 mkfs::run(Path::new(ENCRYPTED_VOLUME_PATH), MkfsFileSystemType::Ext4).unwrap(); @@ -498,13 +489,7 @@ mod functional_test { cryptsetup_close(ENCRYPTED_VOLUME_NAME).unwrap(); // Re-open the encrypted volume - cryptsetup_open( - key_file_path, - &partition1.node, - ENCRYPTED_VOLUME_NAME, - Some(&pcrlock_policy_path), - ) - .unwrap(); + cryptsetup_open(key_file_path, &partition1.node, ENCRYPTED_VOLUME_NAME).unwrap(); // Re-mount the encrypted volume Dependency::Mount @@ -605,13 +590,7 @@ mod functional_test { .unwrap(); // Open the encrypted volume, to make the block device available - cryptsetup_open( - key_file_path, - &partition1.node, - ENCRYPTED_VOLUME_NAME, - Some(&pcrlock_policy_path), - ) - .unwrap(); + cryptsetup_open(key_file_path, &partition1.node, ENCRYPTED_VOLUME_NAME).unwrap(); // Verify the test data exists at the expected offset let mut decrypted_device = OpenOptions::new() diff --git a/crates/osutils/src/pcrlock.rs b/crates/osutils/src/pcrlock.rs index 7a2d4d2b6..2ec7ca8a6 100644 --- a/crates/osutils/src/pcrlock.rs +++ b/crates/osutils/src/pcrlock.rs @@ -36,6 +36,9 @@ pub(crate) const PCRLOCK_DIR: &str = "/var/lib/pcrlock.d"; /// datastore. const PCRLOCK_POLICY_JSON: &str = "pcrlock.json"; +/// Default path to the pcrlock policy JSON. +const PCRLOCK_POLICY_JSON_DEFAULT: &str = "/var/lib/systemd/pcrlock.json"; + /// `/var/lib/pcrlock.d/630-boot-loader-code-shim.pcrlock.d`, where `lock-pe` measures the shim /// bootloader binary, i.e. `/EFI/AZL{A/B}/bootx64.efi`, as recorded into PCR 4 following /// Microsoft's Authenticode hash spec, @@ -375,6 +378,15 @@ fn make_policy(pcrs: BitFlags, pcrlock_policy_path: &Path) -> Result<(), Er ); } + // Copy pcrlock policy JSON to PCRLOCK_POLICY_JSON_DEFAULT_PATH because that is what + // cryptsetup expects + debug!( + "Copying generated pcrlock policy JSON from '{}' to default location at '{}'", + pcrlock_policy_path.display(), + PCRLOCK_POLICY_JSON_DEFAULT + ); + fs::copy(pcrlock_policy_path, PCRLOCK_POLICY_JSON_DEFAULT)?; + Ok(()) } diff --git a/crates/trident/src/engine/storage/encryption.rs b/crates/trident/src/engine/storage/encryption.rs index 0b61395fa..f27595bd6 100644 --- a/crates/trident/src/engine/storage/encryption.rs +++ b/crates/trident/src/engine/storage/encryption.rs @@ -289,7 +289,7 @@ fn encrypt_and_open_device( device_name ); - encryption::cryptsetup_open(key_file, device_path, device_name, pcrlock_policy_path)?; + encryption::cryptsetup_open(key_file, device_path, device_name)?; Ok(()) } From e7b75c6280d959e7706ddd440ca7665dedeaeab0 Mon Sep 17 00:00:00 2001 From: Nadiia Dubchak Date: Thu, 18 Dec 2025 00:39:37 +0000 Subject: [PATCH 07/21] Added logic to modify crypttab --- crates/osutils/src/dependencies.rs | 2 - .../src/subsystems/storage/encryption.rs | 101 ++++++++++-------- 2 files changed, 57 insertions(+), 46 deletions(-) diff --git a/crates/osutils/src/dependencies.rs b/crates/osutils/src/dependencies.rs index 901a9d53a..c4696f4e3 100644 --- a/crates/osutils/src/dependencies.rs +++ b/crates/osutils/src/dependencies.rs @@ -121,8 +121,6 @@ pub enum Dependency { SystemdConfext, #[strum(serialize = "systemd-cryptenroll")] SystemdCryptenroll, - #[strum(serialize = "systemd-cryptsetup")] - SystemdCryptsetup, #[strum(serialize = "systemd-firstboot")] SystemdFirstboot, #[strum(serialize = "systemd-pcrlock")] diff --git a/crates/trident/src/subsystems/storage/encryption.rs b/crates/trident/src/subsystems/storage/encryption.rs index bf38361bb..614e8a9ae 100644 --- a/crates/trident/src/subsystems/storage/encryption.rs +++ b/crates/trident/src/subsystems/storage/encryption.rs @@ -223,54 +223,67 @@ pub fn configure(ctx: &EngineContext) -> Result<(), TridentError> { let path = PathBuf::from(CRYPTTAB_PATH); let mut contents = String::new(); - let Some(ref encryption) = ctx.spec.storage.encryption else { - return Ok(()); - }; - - for ev in encryption.volumes.iter() { - let backing_partition = - ctx.get_first_backing_partition(&ev.device_id) - .structured(InvalidInputError::from( + if let Some(ref encryption) = ctx.spec.storage.encryption { + for ev in encryption.volumes.iter() { + let backing_partition = ctx.get_first_backing_partition(&ev.device_id).structured( + InvalidInputError::from( HostConfigurationStaticValidationError::EncryptedVolumeNotPartitionOrRaid { encrypted_volume: ev.id.clone(), }, - ))?; - let device_path = &ctx.get_block_device_path(&ev.device_id).structured( - ServicingError::FindEncryptedVolumeBlockDevice { - device_id: ev.device_id.clone(), - encrypted_volume: ev.id.clone(), - }, - )?; + ), + )?; + let device_path = &ctx.get_block_device_path(&ev.device_id).structured( + ServicingError::FindEncryptedVolumeBlockDevice { + device_id: ev.device_id.clone(), + encrypted_volume: ev.id.clone(), + }, + )?; - // An encrypted swap device is special-cased in the crypttab due to the unique nature and - // requirements of swap spaces in a Linux system. Since it often contains sensitive data - // temporarily stored in RAM, encrypting it is crucial for security. However, unlike the - // regular partitions, which use TPM 2.0 devices for passwordless startup, systemd - // completely wipes the swap device and formats it on each system startup. - // - // For systemd to do this, it needs a key, and here in the crypttab, the swap device is - // configured with a randomly generated key from `/dev/random`. This is the most reliable - // way to generate a truly random key on Linux systems. - // - // The default cipher (aes-cbc-essiv:sha256) and key size (256) are not used here, to - // enhance the security posture of the swap space and align it with the rest of the - // encrypted devices. - if backing_partition.partition_type == PartitionType::Swap { - contents.push_str(&format!( - "{}\t{}\t{}\tluks,swap,cipher={},size={}\n", - ev.device_name, - device_path.display(), - osutils_encryption::DEV_RANDOM_PATH, - osutils_encryption::CIPHER, - osutils_encryption::KEY_SIZE - )); - } else { - contents.push_str(&format!( - "{}\t{}\t{}\tluks,tpm2-device=auto\n", - ev.device_name, - device_path.display(), - "none" - )); + // Build options + let options = if ctx.is_uki()? { + // Construct full path to pcrlock policy JSON file + let pcrlock_policy_path = + pcrlock::construct_pcrlock_path(&ctx.spec.trident.datastore_path) + .structured(ServicingError::ConstructPcrlockPolicyPath)?; + format!( + "luks,tpm2-device=auto,tpm2-pcrlock={}", + pcrlock_policy_path.display() + ) + } else { + "luks,tpm2-device=auto".to_string() + }; + + // An encrypted swap device is special-cased in the crypttab due to the unique nature and + // requirements of swap spaces in a Linux system. Since it often contains sensitive data + // temporarily stored in RAM, encrypting it is crucial for security. However, unlike the + // regular partitions, which use TPM 2.0 devices for passwordless startup, systemd + // completely wipes the swap device and formats it on each system startup. + // + // For systemd to do this, it needs a key, and here in the crypttab, the swap device is + // configured with a randomly generated key from `/dev/random`. This is the most reliable + // way to generate a truly random key on Linux systems. + // + // The default cipher (aes-cbc-essiv:sha256) and key size (256) are not used here, to + // enhance the security posture of the swap space and align it with the rest of the + // encrypted devices. + if backing_partition.partition_type == PartitionType::Swap { + contents.push_str(&format!( + "{}\t{}\t{}\tluks,swap,cipher={},size={}\n", + ev.device_name, + device_path.display(), + osutils_encryption::DEV_RANDOM_PATH, + osutils_encryption::CIPHER, + osutils_encryption::KEY_SIZE + )); + } else { + contents.push_str(&format!( + "{}\t{}\t{}\t{}\n", + ev.device_name, + device_path.display(), + "none", + options + )); + } } } From 863d5d21643199e25a5f91daa02eace44ee93280 Mon Sep 17 00:00:00 2001 From: Nadiia Dubchak Date: Thu, 18 Dec 2025 02:10:09 +0000 Subject: [PATCH 08/21] Added logic to persist pcrlock JSON --- crates/osutils/src/encryption.rs | 6 +++-- crates/osutils/src/pcrlock.rs | 16 +++++++++--- crates/trident/src/engine/clean_install.rs | 26 +++++++++++++++++++ crates/trident/src/engine/rollback.rs | 1 + .../trident/src/engine/storage/encryption.rs | 2 +- .../src/subsystems/storage/encryption.rs | 7 ++--- crates/trident_api/src/error.rs | 3 +++ 7 files changed, 52 insertions(+), 9 deletions(-) diff --git a/crates/osutils/src/encryption.rs b/crates/osutils/src/encryption.rs index 89ea52bb2..9cdd944b4 100644 --- a/crates/osutils/src/encryption.rs +++ b/crates/osutils/src/encryption.rs @@ -427,7 +427,8 @@ mod functional_test { // Generate a pcrlock policy that only includes PCR 0 let pcrs = BitFlags::from(Pcr::Pcr0); let pcrlock_policy_path = - pcrlock::construct_pcrlock_path(Path::new(TRIDENT_DATASTORE_PATH_DEFAULT)).unwrap(); + pcrlock::construct_pcrlock_path(Path::new(TRIDENT_DATASTORE_PATH_DEFAULT), None) + .unwrap(); pcrlock::generate_pcrlock_policy(pcrs, &pcrlock_policy_path, vec![], vec![]).unwrap(); // Run `systemd-cryptenroll` on the partition @@ -577,7 +578,8 @@ mod functional_test { // Generate a pcrlock policy that only includes PCR 0 let pcrs = BitFlags::from(Pcr::Pcr0); let pcrlock_policy_path = - pcrlock::construct_pcrlock_path(Path::new(TRIDENT_DATASTORE_PATH_DEFAULT)).unwrap(); + pcrlock::construct_pcrlock_path(Path::new(TRIDENT_DATASTORE_PATH_DEFAULT), None) + .unwrap(); pcrlock::generate_pcrlock_policy(pcrs, &pcrlock_policy_path, vec![], vec![]).unwrap(); // Run `systemd-cryptenroll` on the partition diff --git a/crates/osutils/src/pcrlock.rs b/crates/osutils/src/pcrlock.rs index 2ec7ca8a6..e0e2ad381 100644 --- a/crates/osutils/src/pcrlock.rs +++ b/crates/osutils/src/pcrlock.rs @@ -92,7 +92,12 @@ pub fn generate_pcrlock_policy( Ok(()) } -pub fn construct_pcrlock_path(datastore_path: &Path) -> Result { +/// Constructs the full path to the pcrlock policy JSON file, located in the directory adjacent +/// to the datastore. +pub fn construct_pcrlock_path( + datastore_path: &Path, + newroot_path: Option<&Path>, +) -> Result { // Fetch the directory path from the full datastore path let Some(datastore_dir) = datastore_path.parent() else { bail!( @@ -102,11 +107,16 @@ pub fn construct_pcrlock_path(datastore_path: &Path) -> Result { }; // Construct full path to pcrlock policy JSON file - let pcrlock_policy_path = datastore_dir.join(PCRLOCK_POLICY_JSON); + let pcrlock_policy_path = if let Some(new_root) = newroot_path { + path::join_relative(new_root, datastore_dir).join(PCRLOCK_POLICY_JSON) + } else { + datastore_dir.join(PCRLOCK_POLICY_JSON) + }; trace!( "Constructed full pcrlock policy JSON path at '{}'", pcrlock_policy_path.display() ); + Ok(pcrlock_policy_path) } @@ -808,7 +818,7 @@ mod functional_test { // used to generate a TPM 2.0 access policy. let zero_pcrs = Pcr::Pcr11 | Pcr::Pcr12 | Pcr::Pcr13; let pcrlock_policy_path = - construct_pcrlock_path(Path::new(TRIDENT_DATASTORE_PATH_DEFAULT)).unwrap(); + construct_pcrlock_path(Path::new(TRIDENT_DATASTORE_PATH_DEFAULT), None).unwrap(); generate_tpm2_access_policy(zero_pcrs, &pcrlock_policy_path).unwrap(); // Test case #1. Try to generate a TPM 2.0 access policy with all PCRs; should return an diff --git a/crates/trident/src/engine/clean_install.rs b/crates/trident/src/engine/clean_install.rs index ff5279f94..d6c3f58a9 100644 --- a/crates/trident/src/engine/clean_install.rs +++ b/crates/trident/src/engine/clean_install.rs @@ -1,4 +1,5 @@ use std::{ + fs, path::{Path, PathBuf}, sync::MutexGuard, time::Instant, @@ -11,6 +12,7 @@ use osutils::{ installation_media::{self, BootType}, mount, mountpoint, path::join_relative, + pcrlock, }; use trident_api::{ config::{HostConfiguration, Operations}, @@ -347,6 +349,30 @@ pub(crate) fn finalize_clean_install( state.host_status().servicing_state, ); + // If needed, persist the pcrlock policy JSON + let pcrlock_policy_path = + pcrlock::construct_pcrlock_path(&state.host_status().spec.trident.datastore_path, None) + .structured(ServicingError::ConstructPcrlockPolicyPath)?; + if pcrlock_policy_path.exists() { + let target_pcrlock_policy_path = pcrlock::construct_pcrlock_path( + &state.host_status().spec.trident.datastore_path, + Some(new_root.path()), + ) + .structured(ServicingError::ConstructPcrlockPolicyPath)?; + + debug!( + "Persisting pcrlock policy JSON at '{}' to new root at '{}'", + pcrlock_policy_path.display(), + target_pcrlock_policy_path.display() + ); + fs::copy(&pcrlock_policy_path, &target_pcrlock_policy_path).structured( + ServicingError::PersistPcrlockPolicy { + path: pcrlock_policy_path.to_string_lossy().to_string(), + destination: target_pcrlock_policy_path.to_string_lossy().to_string(), + }, + )?; + } + if let Err(e) = new_root.unmount_all() { error!("Failed to unmount new root: {e:?}"); } diff --git a/crates/trident/src/engine/rollback.rs b/crates/trident/src/engine/rollback.rs index 5f60501c2..3264f88dc 100644 --- a/crates/trident/src/engine/rollback.rs +++ b/crates/trident/src/engine/rollback.rs @@ -256,6 +256,7 @@ fn commit_finalized_on_expected_root( // Construct full path to pcrlock policy JSON file let pcrlock_policy_path = pcrlock::construct_pcrlock_path( &datastore.host_status().spec.trident.datastore_path, + None, ) .structured(ServicingError::ConstructPcrlockPolicyPath)?; diff --git a/crates/trident/src/engine/storage/encryption.rs b/crates/trident/src/engine/storage/encryption.rs index f27595bd6..ecd59f8d9 100644 --- a/crates/trident/src/engine/storage/encryption.rs +++ b/crates/trident/src/engine/storage/encryption.rs @@ -124,7 +124,7 @@ pub(super) fn create_encrypted_devices(ctx: &EngineContext) -> Result<(), Triden // Construct full path to pcrlock policy JSON file let pcrlock_policy_path = - pcrlock::construct_pcrlock_path(&ctx.spec.trident.datastore_path) + pcrlock::construct_pcrlock_path(&ctx.spec.trident.datastore_path, None) .structured(ServicingError::ConstructPcrlockPolicyPath)?; // Remove any pre-existing policy diff --git a/crates/trident/src/subsystems/storage/encryption.rs b/crates/trident/src/subsystems/storage/encryption.rs index 614e8a9ae..472308760 100644 --- a/crates/trident/src/subsystems/storage/encryption.rs +++ b/crates/trident/src/subsystems/storage/encryption.rs @@ -175,8 +175,9 @@ pub fn provision(ctx: &EngineContext, mount_path: &Path) -> Result<(), TridentEr }; // Construct full path to pcrlock policy JSON file - let pcrlock_policy_path = pcrlock::construct_pcrlock_path(&ctx.spec.trident.datastore_path) - .structured(ServicingError::ConstructPcrlockPolicyPath)?; + let pcrlock_policy_path = + pcrlock::construct_pcrlock_path(&ctx.spec.trident.datastore_path, None) + .structured(ServicingError::ConstructPcrlockPolicyPath)?; // If updated PCRs are specified, re-generate pcrlock policy if let Some(pcrs) = updated_pcrs { @@ -243,7 +244,7 @@ pub fn configure(ctx: &EngineContext) -> Result<(), TridentError> { let options = if ctx.is_uki()? { // Construct full path to pcrlock policy JSON file let pcrlock_policy_path = - pcrlock::construct_pcrlock_path(&ctx.spec.trident.datastore_path) + pcrlock::construct_pcrlock_path(&ctx.spec.trident.datastore_path, None) .structured(ServicingError::ConstructPcrlockPolicyPath)?; format!( "luks,tpm2-device=auto,tpm2-pcrlock={}", diff --git a/crates/trident_api/src/error.rs b/crates/trident_api/src/error.rs index 21d469193..915a8a867 100644 --- a/crates/trident_api/src/error.rs +++ b/crates/trident_api/src/error.rs @@ -548,6 +548,9 @@ pub enum ServicingError { #[error("Failed to parse non-Unicode path '{path}'")] PathIsNotUnicode { path: String }, + #[error("Failed to persist pcrlock policy from '{path}' to '{destination}'")] + PersistPcrlockPolicy { path: String, destination: String }, + #[error("Failed to do a read operation with efibootmgr")] ReadEfibootmgr, From d64e70a6fe25099c5a3b4ab9585f8e5841428b0f Mon Sep 17 00:00:00 2001 From: Nadiia Dubchak Date: Thu, 18 Dec 2025 03:10:27 +0000 Subject: [PATCH 09/21] Misspelled option to see if error --- crates/osutils/src/encryption.rs | 3 +-- crates/trident/src/subsystems/storage/encryption.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/osutils/src/encryption.rs b/crates/osutils/src/encryption.rs index 9cdd944b4..d25c6fdd4 100644 --- a/crates/osutils/src/encryption.rs +++ b/crates/osutils/src/encryption.rs @@ -52,8 +52,7 @@ pub fn systemd_cryptenroll( if let Some(pcrs) = pcrs { cmd.arg(to_tpm2_pcrs_arg(pcrs)); } else if let Some(pcrlock_policy_path) = pcrlock_policy_path { - // TODO: need to check container path existence? - + // TODO: ADJUST PATH FOR CONTAINER! cmd.arg(format!("--tpm2-pcrlock={}", pcrlock_policy_path.display())); } diff --git a/crates/trident/src/subsystems/storage/encryption.rs b/crates/trident/src/subsystems/storage/encryption.rs index 472308760..df45a547c 100644 --- a/crates/trident/src/subsystems/storage/encryption.rs +++ b/crates/trident/src/subsystems/storage/encryption.rs @@ -247,7 +247,7 @@ pub fn configure(ctx: &EngineContext) -> Result<(), TridentError> { pcrlock::construct_pcrlock_path(&ctx.spec.trident.datastore_path, None) .structured(ServicingError::ConstructPcrlockPolicyPath)?; format!( - "luks,tpm2-device=auto,tpm2-pcrlock={}", + "luks,tpm2-device=auto,tpm2-pcrlck={}", pcrlock_policy_path.display() ) } else { From 2d80772e3efc7a276dbcb1c49cfb4d001c8eed42 Mon Sep 17 00:00:00 2001 From: Nadiia Dubchak Date: Thu, 18 Dec 2025 03:30:40 +0000 Subject: [PATCH 10/21] Also persist to default pcrlock JSON location to see if that fixes decryption --- crates/osutils/src/pcrlock.rs | 2 +- .../trident/src/subsystems/storage/encryption.rs | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/crates/osutils/src/pcrlock.rs b/crates/osutils/src/pcrlock.rs index e0e2ad381..da6d81b06 100644 --- a/crates/osutils/src/pcrlock.rs +++ b/crates/osutils/src/pcrlock.rs @@ -37,7 +37,7 @@ pub(crate) const PCRLOCK_DIR: &str = "/var/lib/pcrlock.d"; const PCRLOCK_POLICY_JSON: &str = "pcrlock.json"; /// Default path to the pcrlock policy JSON. -const PCRLOCK_POLICY_JSON_DEFAULT: &str = "/var/lib/systemd/pcrlock.json"; +pub const PCRLOCK_POLICY_JSON_DEFAULT: &str = "/var/lib/systemd/pcrlock.json"; /// `/var/lib/pcrlock.d/630-boot-loader-code-shim.pcrlock.d`, where `lock-pe` measures the shim /// bootloader binary, i.e. `/EFI/AZL{A/B}/bootx64.efi`, as recorded into PCR 4 following diff --git a/crates/trident/src/subsystems/storage/encryption.rs b/crates/trident/src/subsystems/storage/encryption.rs index df45a547c..9e929a09a 100644 --- a/crates/trident/src/subsystems/storage/encryption.rs +++ b/crates/trident/src/subsystems/storage/encryption.rs @@ -213,6 +213,21 @@ pub fn provision(ctx: &EngineContext, mount_path: &Path) -> Result<(), TridentEr destination: pcrlock_json_copy.display().to_string(), }, )?; + + // TODO: Remove + // Also copy to PCRLOCK_POLICY_JSON_DEFAULT to see if that fixes decryption + let pcrlock_json_default_copy = + path::join_relative(mount_path, pcrlock::PCRLOCK_POLICY_JSON_DEFAULT); + debug!( + "Also copying pcrlock policy JSON to default path '{}' on update volume", + pcrlock_json_default_copy.display() + ); + fs::copy(&pcrlock_policy_path, pcrlock_json_default_copy.clone()).structured( + ServicingError::CopyPcrlockPolicyJson { + path: pcrlock_policy_path.display().to_string(), + destination: pcrlock_json_default_copy.display().to_string(), + }, + )?; } } From f490ea3c85d5b1f482fa59d44f4dce961295a3f5 Mon Sep 17 00:00:00 2001 From: Nadiia Dubchak Date: Fri, 19 Dec 2025 23:56:17 +0000 Subject: [PATCH 11/21] Tried removing default pcrlock after luksOpen --- crates/trident/src/engine/storage/encryption.rs | 9 ++++++++- .../src/subsystems/storage/encryption.rs | 17 +---------------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/crates/trident/src/engine/storage/encryption.rs b/crates/trident/src/engine/storage/encryption.rs index ecd59f8d9..d1f0c5716 100644 --- a/crates/trident/src/engine/storage/encryption.rs +++ b/crates/trident/src/engine/storage/encryption.rs @@ -17,7 +17,7 @@ use osutils::{ encryption::{self, KeySlotType}, lsblk::{self, BlockDeviceType}, path::join_relative, - pcrlock, + pcrlock::{self, PCRLOCK_POLICY_JSON_DEFAULT}, }; use sysdefs::tpm2::Pcr; use trident_api::{ @@ -290,6 +290,13 @@ fn encrypt_and_open_device( ); encryption::cryptsetup_open(key_file, device_path, device_name)?; + // TODO: test + // If exists, remove file at PCRLOCK_POLICY_JSON_DEFAULT + if Path::new(PCRLOCK_POLICY_JSON_DEFAULT).exists() { + fs::remove_file(PCRLOCK_POLICY_JSON_DEFAULT).context(format!( + "Failed to remove existing pcrlock policy file at '{PCRLOCK_POLICY_JSON_DEFAULT}'" + ))?; + } Ok(()) } diff --git a/crates/trident/src/subsystems/storage/encryption.rs b/crates/trident/src/subsystems/storage/encryption.rs index 9e929a09a..472308760 100644 --- a/crates/trident/src/subsystems/storage/encryption.rs +++ b/crates/trident/src/subsystems/storage/encryption.rs @@ -213,21 +213,6 @@ pub fn provision(ctx: &EngineContext, mount_path: &Path) -> Result<(), TridentEr destination: pcrlock_json_copy.display().to_string(), }, )?; - - // TODO: Remove - // Also copy to PCRLOCK_POLICY_JSON_DEFAULT to see if that fixes decryption - let pcrlock_json_default_copy = - path::join_relative(mount_path, pcrlock::PCRLOCK_POLICY_JSON_DEFAULT); - debug!( - "Also copying pcrlock policy JSON to default path '{}' on update volume", - pcrlock_json_default_copy.display() - ); - fs::copy(&pcrlock_policy_path, pcrlock_json_default_copy.clone()).structured( - ServicingError::CopyPcrlockPolicyJson { - path: pcrlock_policy_path.display().to_string(), - destination: pcrlock_json_default_copy.display().to_string(), - }, - )?; } } @@ -262,7 +247,7 @@ pub fn configure(ctx: &EngineContext) -> Result<(), TridentError> { pcrlock::construct_pcrlock_path(&ctx.spec.trident.datastore_path, None) .structured(ServicingError::ConstructPcrlockPolicyPath)?; format!( - "luks,tpm2-device=auto,tpm2-pcrlck={}", + "luks,tpm2-device=auto,tpm2-pcrlock={}", pcrlock_policy_path.display() ) } else { From 2cf8ab69f72fe93019edf141f4e74ba2e6367e2e Mon Sep 17 00:00:00 2001 From: Nadiia Dubchak Date: Mon, 22 Dec 2025 21:27:36 +0000 Subject: [PATCH 12/21] Moved logic to remove default pcrlock JSON after both volumes are opened --- crates/trident/src/engine/storage/encryption.rs | 14 +++++++------- crates/trident_api/src/error.rs | 5 +++++ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/crates/trident/src/engine/storage/encryption.rs b/crates/trident/src/engine/storage/encryption.rs index d1f0c5716..48c1ed83d 100644 --- a/crates/trident/src/engine/storage/encryption.rs +++ b/crates/trident/src/engine/storage/encryption.rs @@ -208,6 +208,13 @@ pub(super) fn create_encrypted_devices(ctx: &EngineContext) -> Result<(), Triden encrypted_volume: ev.id.clone(), })?; + // TODO: test + // If exists, remove file at PCRLOCK_POLICY_JSON_DEFAULT + if Path::new(PCRLOCK_POLICY_JSON_DEFAULT).exists() { + fs::remove_file(PCRLOCK_POLICY_JSON_DEFAULT) + .structured(ServicingError::RemoveDefaultPcrlockPolicyJson)?; + } + // If the key file was randomly generated and NOT provided by the user as a // recovery key, remove the password key slot from the encrypted volume, as it's // not needed, for security @@ -290,13 +297,6 @@ fn encrypt_and_open_device( ); encryption::cryptsetup_open(key_file, device_path, device_name)?; - // TODO: test - // If exists, remove file at PCRLOCK_POLICY_JSON_DEFAULT - if Path::new(PCRLOCK_POLICY_JSON_DEFAULT).exists() { - fs::remove_file(PCRLOCK_POLICY_JSON_DEFAULT).context(format!( - "Failed to remove existing pcrlock policy file at '{PCRLOCK_POLICY_JSON_DEFAULT}'" - ))?; - } Ok(()) } diff --git a/crates/trident_api/src/error.rs b/crates/trident_api/src/error.rs index 915a8a867..883b7d7e5 100644 --- a/crates/trident_api/src/error.rs +++ b/crates/trident_api/src/error.rs @@ -578,6 +578,11 @@ pub enum ServicingError { #[error("Failed to remove crypttab at path '{crypttab_path}'")] RemoveCrypttab { crypttab_path: String }, + #[error( + "Failed to remove default pcrlock policy JSON file at '/var/lib/systemd/pcrlock.json'" + )] + RemoveDefaultPcrlockPolicyJson, + #[error("Failed to remove Netplan config")] RemoveNetplanConfig, From 7d49678ac758fb94779b4e0ac655e50424fcae3a Mon Sep 17 00:00:00 2001 From: Nadiia Dubchak Date: Mon, 22 Dec 2025 21:35:45 +0000 Subject: [PATCH 13/21] Validate pcrlock JSON was removed --- crates/trident/src/engine/storage/encryption.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/trident/src/engine/storage/encryption.rs b/crates/trident/src/engine/storage/encryption.rs index 48c1ed83d..44766c1b0 100644 --- a/crates/trident/src/engine/storage/encryption.rs +++ b/crates/trident/src/engine/storage/encryption.rs @@ -213,6 +213,13 @@ pub(super) fn create_encrypted_devices(ctx: &EngineContext) -> Result<(), Triden if Path::new(PCRLOCK_POLICY_JSON_DEFAULT).exists() { fs::remove_file(PCRLOCK_POLICY_JSON_DEFAULT) .structured(ServicingError::RemoveDefaultPcrlockPolicyJson)?; + + // Validate that the file has been removed + if Path::new(PCRLOCK_POLICY_JSON_DEFAULT).exists() { + return Err(TridentError::new( + ServicingError::RemoveDefaultPcrlockPolicyJson, + )); + } } // If the key file was randomly generated and NOT provided by the user as a From e2e7e6042f8b4698011349ecfc6427a354352d1c Mon Sep 17 00:00:00 2001 From: Nadiia Dubchak Date: Mon, 22 Dec 2025 22:23:39 +0000 Subject: [PATCH 14/21] Removed dup logic --- crates/trident/src/engine/clean_install.rs | 26 ------------------- .../trident/src/engine/storage/encryption.rs | 4 +++ 2 files changed, 4 insertions(+), 26 deletions(-) diff --git a/crates/trident/src/engine/clean_install.rs b/crates/trident/src/engine/clean_install.rs index d6c3f58a9..ff5279f94 100644 --- a/crates/trident/src/engine/clean_install.rs +++ b/crates/trident/src/engine/clean_install.rs @@ -1,5 +1,4 @@ use std::{ - fs, path::{Path, PathBuf}, sync::MutexGuard, time::Instant, @@ -12,7 +11,6 @@ use osutils::{ installation_media::{self, BootType}, mount, mountpoint, path::join_relative, - pcrlock, }; use trident_api::{ config::{HostConfiguration, Operations}, @@ -349,30 +347,6 @@ pub(crate) fn finalize_clean_install( state.host_status().servicing_state, ); - // If needed, persist the pcrlock policy JSON - let pcrlock_policy_path = - pcrlock::construct_pcrlock_path(&state.host_status().spec.trident.datastore_path, None) - .structured(ServicingError::ConstructPcrlockPolicyPath)?; - if pcrlock_policy_path.exists() { - let target_pcrlock_policy_path = pcrlock::construct_pcrlock_path( - &state.host_status().spec.trident.datastore_path, - Some(new_root.path()), - ) - .structured(ServicingError::ConstructPcrlockPolicyPath)?; - - debug!( - "Persisting pcrlock policy JSON at '{}' to new root at '{}'", - pcrlock_policy_path.display(), - target_pcrlock_policy_path.display() - ); - fs::copy(&pcrlock_policy_path, &target_pcrlock_policy_path).structured( - ServicingError::PersistPcrlockPolicy { - path: pcrlock_policy_path.to_string_lossy().to_string(), - destination: target_pcrlock_policy_path.to_string_lossy().to_string(), - }, - )?; - } - if let Err(e) = new_root.unmount_all() { error!("Failed to unmount new root: {e:?}"); } diff --git a/crates/trident/src/engine/storage/encryption.rs b/crates/trident/src/engine/storage/encryption.rs index 44766c1b0..ab26d83df 100644 --- a/crates/trident/src/engine/storage/encryption.rs +++ b/crates/trident/src/engine/storage/encryption.rs @@ -220,6 +220,10 @@ pub(super) fn create_encrypted_devices(ctx: &EngineContext) -> Result<(), Triden ServicingError::RemoveDefaultPcrlockPolicyJson, )); } + debug!( + "Removed default pcrlock policy JSON file at '{}'", + PCRLOCK_POLICY_JSON_DEFAULT + ); } // If the key file was randomly generated and NOT provided by the user as a From c9787972e128e8ff7c70f6d593ecb3a69edf489b Mon Sep 17 00:00:00 2001 From: Nadiia Dubchak Date: Mon, 22 Dec 2025 22:35:51 +0000 Subject: [PATCH 15/21] Moved removal logic until after all devices are encrypted --- .../trident/src/engine/storage/encryption.rs | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/crates/trident/src/engine/storage/encryption.rs b/crates/trident/src/engine/storage/encryption.rs index ab26d83df..e45c22ebc 100644 --- a/crates/trident/src/engine/storage/encryption.rs +++ b/crates/trident/src/engine/storage/encryption.rs @@ -208,24 +208,6 @@ pub(super) fn create_encrypted_devices(ctx: &EngineContext) -> Result<(), Triden encrypted_volume: ev.id.clone(), })?; - // TODO: test - // If exists, remove file at PCRLOCK_POLICY_JSON_DEFAULT - if Path::new(PCRLOCK_POLICY_JSON_DEFAULT).exists() { - fs::remove_file(PCRLOCK_POLICY_JSON_DEFAULT) - .structured(ServicingError::RemoveDefaultPcrlockPolicyJson)?; - - // Validate that the file has been removed - if Path::new(PCRLOCK_POLICY_JSON_DEFAULT).exists() { - return Err(TridentError::new( - ServicingError::RemoveDefaultPcrlockPolicyJson, - )); - } - debug!( - "Removed default pcrlock policy JSON file at '{}'", - PCRLOCK_POLICY_JSON_DEFAULT - ); - } - // If the key file was randomly generated and NOT provided by the user as a // recovery key, remove the password key slot from the encrypted volume, as it's // not needed, for security @@ -243,6 +225,25 @@ pub(super) fn create_encrypted_devices(ctx: &EngineContext) -> Result<(), Triden })?; } } + + // TODO: test + // If exists, remove file at PCRLOCK_POLICY_JSON_DEFAULT + if Path::new(PCRLOCK_POLICY_JSON_DEFAULT).exists() { + fs::remove_file(PCRLOCK_POLICY_JSON_DEFAULT) + .structured(ServicingError::RemoveDefaultPcrlockPolicyJson)?; + + // Validate that the file has been removed + if Path::new(PCRLOCK_POLICY_JSON_DEFAULT).exists() { + return Err(TridentError::new( + ServicingError::RemoveDefaultPcrlockPolicyJson, + )); + } + debug!( + "Removed default pcrlock policy JSON file at '{}'", + PCRLOCK_POLICY_JSON_DEFAULT + ); + } + tracing::Span::current().record("total_partition_size_bytes", total_partition_size_bytes); } From 002381adf4a2a5ae7a50743b557625c51965c293 Mon Sep 17 00:00:00 2001 From: Nadiia Dubchak Date: Tue, 23 Dec 2025 19:27:39 +0000 Subject: [PATCH 16/21] Require /var/lib/trident to be mounted --- .../trident_configurations/combined/trident-config.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/e2e_tests/trident_configurations/combined/trident-config.yaml b/tests/e2e_tests/trident_configurations/combined/trident-config.yaml index 722b7e508..10c02938d 100644 --- a/tests/e2e_tests/trident_configurations/combined/trident-config.yaml +++ b/tests/e2e_tests/trident_configurations/combined/trident-config.yaml @@ -156,7 +156,9 @@ storage: options: defaults,ro - deviceId: web source: new - mountPoint: /web + mountPoint: + path: /web + options: defaults,nofail,x-systemd.device-timeout=4,x-systemd.requires=var-lib-trident.mount,x-systemd.after=var-lib-trident.mount - deviceId: trident source: new mountPoint: /var/lib/trident From 0869c4873cc46b4afbbaf5bcc5ecbfd8ac1008f3 Mon Sep 17 00:00:00 2001 From: Nadiia Dubchak Date: Tue, 23 Dec 2025 20:00:53 +0000 Subject: [PATCH 17/21] Removed nofail --- .../trident_configurations/combined/trident-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e_tests/trident_configurations/combined/trident-config.yaml b/tests/e2e_tests/trident_configurations/combined/trident-config.yaml index 10c02938d..d25a44a61 100644 --- a/tests/e2e_tests/trident_configurations/combined/trident-config.yaml +++ b/tests/e2e_tests/trident_configurations/combined/trident-config.yaml @@ -158,7 +158,7 @@ storage: source: new mountPoint: path: /web - options: defaults,nofail,x-systemd.device-timeout=4,x-systemd.requires=var-lib-trident.mount,x-systemd.after=var-lib-trident.mount + options: defaults,x-systemd.device-timeout=4,x-systemd.requires=var-lib-trident.mount,x-systemd.after=var-lib-trident.mount - deviceId: trident source: new mountPoint: /var/lib/trident From 4745d87e580d6b393a80a85400fe1555d646a0ac Mon Sep 17 00:00:00 2001 From: Nadiia Dubchak Date: Fri, 2 Jan 2026 21:13:04 +0000 Subject: [PATCH 18/21] Try adding systemd dropins --- .../src/subsystems/storage/encryption.rs | 46 +++++++++++++++++++ crates/trident_api/src/error.rs | 3 ++ 2 files changed, 49 insertions(+) diff --git a/crates/trident/src/subsystems/storage/encryption.rs b/crates/trident/src/subsystems/storage/encryption.rs index 472308760..dcc097f03 100644 --- a/crates/trident/src/subsystems/storage/encryption.rs +++ b/crates/trident/src/subsystems/storage/encryption.rs @@ -1,5 +1,6 @@ use std::{ fs, + io::Write, os::unix::fs::PermissionsExt, path::{Path, PathBuf}, }; @@ -214,6 +215,51 @@ pub fn provision(ctx: &EngineContext, mount_path: &Path) -> Result<(), TridentEr }, )?; } + + // TODO: REMOVE BEFORE MERGING + // Create drop in systemd files under /etc/systemd to check if that will ensure that /var/lib/trident + // is mounted before systemd-cryptsetup@.services are run + if matches!(ctx.servicing_type, ServicingType::CleanInstall) { + const DROPIN_CONTENTS: &str = "[Unit]\nRequiresMountsFor=/var/lib/trident\n"; + + // Helper: create one drop-in file for a given unit instance name + let create_dropin = |unit: &str| -> Result<(), TridentError> { + // unit is e.g. "systemd-cryptsetup@web\\x2da.service" + let dropin_dir = mount_path + .join("etc/systemd/system") + .join(format!("{unit}.d")); + + fs::create_dir_all(&dropin_dir).structured( + ServicingError::WriteDropInForSystemdCryptsetup { + path: dropin_dir.display().to_string(), + }, + )?; + + let dropin_path = dropin_dir.join("10-trident-requires-trident.mount.conf"); + let mut f = fs::File::create(&dropin_path).structured( + ServicingError::WriteDropInForSystemdCryptsetup { + path: dropin_path.display().to_string(), + }, + )?; + + f.write_all(DROPIN_CONTENTS.as_bytes()).structured( + ServicingError::WriteDropInForSystemdCryptsetup { + path: dropin_path.display().to_string(), + }, + )?; + + debug!( + "Wrote systemd drop-in '{}' with RequiresMountsFor=/var/lib/trident", + dropin_path.display() + ); + Ok(()) + }; + + // web-a instance: systemd-cryptsetup@web\x2da.service + create_dropin("systemd-cryptsetup@web\\x2da.service")?; + // web-b instance: systemd-cryptsetup@web\x2db.service + create_dropin("systemd-cryptsetup@web\\x2db.service")?; + } } Ok(()) diff --git a/crates/trident_api/src/error.rs b/crates/trident_api/src/error.rs index 883b7d7e5..c59f13fc9 100644 --- a/crates/trident_api/src/error.rs +++ b/crates/trident_api/src/error.rs @@ -663,6 +663,9 @@ pub enum ServicingError { #[error("Failed to write an additional file '{file_name}'")] WriteAdditionalFile { file_name: String }, + #[error("Failed to write drop-in for systemd-cryptsetup at path '{path}'")] + WriteDropInForSystemdCryptsetup { path: String }, + #[error("Failed to write Netplan config")] WriteNetplanConfig, } From 574cf84d601aae3adf3d52e3e56695c9303ae6b2 Mon Sep 17 00:00:00 2001 From: Nadiia Dubchak Date: Tue, 13 Jan 2026 00:15:41 +0000 Subject: [PATCH 19/21] Logged --- .github/workflows/deploy-website.yaml | 20 +++++--- .../src/subsystems/storage/encryption.rs | 11 ++++ docs/Reference/Trident-CLI.md | 51 +++++++++++++++++++ website/scripts/create_versioned_docs.sh | 7 +++ 4 files changed, 82 insertions(+), 7 deletions(-) diff --git a/.github/workflows/deploy-website.yaml b/.github/workflows/deploy-website.yaml index d00ebcc52..f6287a1f2 100644 --- a/.github/workflows/deploy-website.yaml +++ b/.github/workflows/deploy-website.yaml @@ -1,7 +1,6 @@ name: Deploy to GitHub Pages on: - # Manual trigger workflow_dispatch: @@ -10,9 +9,9 @@ on: types: [opened, reopened, synchronize, ready_for_review] branches: [main] paths: - - 'docs/**' - - 'website/**' - - '.github/workflows/deploy-website.yaml' + - "docs/**" + - "website/**" + - ".github/workflows/deploy-website.yaml" # # Trigger on release # release: @@ -44,8 +43,15 @@ jobs: echo ${{ secrets.GITHUB_TOKEN }} | gh auth login --with-token # For pull requests, use branch as dev version so branch docs are validated if [[ "${{github.event_name}}" == "pull_request" ]]; then - export DEV_BRANCH=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}} - echo "Using PR branch as dev branch: $DEV_BRANCH" + # For external PRs, use the merge commit ref instead of the branch name + if [[ "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]]; then + export DEV_BRANCH="refs/pull/${{ github.event.number }}/merge" + export USE_MERGE_COMMIT="true" + echo "External PR detected. Using merge ref: $DEV_BRANCH" + else + export DEV_BRANCH=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}} + echo "Using PR branch as dev branch: $DEV_BRANCH" + fi fi # Construct multi-version docs ./scripts/create_versioned_docs.sh @@ -84,4 +90,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 \ No newline at end of file + uses: actions/deploy-pages@v4 diff --git a/crates/trident/src/subsystems/storage/encryption.rs b/crates/trident/src/subsystems/storage/encryption.rs index dcc097f03..a288f1ec7 100644 --- a/crates/trident/src/subsystems/storage/encryption.rs +++ b/crates/trident/src/subsystems/storage/encryption.rs @@ -252,12 +252,23 @@ pub fn provision(ctx: &EngineContext, mount_path: &Path) -> Result<(), TridentEr "Wrote systemd drop-in '{}' with RequiresMountsFor=/var/lib/trident", dropin_path.display() ); + + // log contents for debugging + let dropin_file_contents = fs::read_to_string(&dropin_path).structured( + ServicingError::WriteDropInForSystemdCryptsetup { + path: dropin_path.display().to_string(), + }, + )?; + debug!("Drop-in file contents:\n{}", dropin_file_contents); + Ok(()) }; // web-a instance: systemd-cryptsetup@web\x2da.service + debug!("Creating systemd drop-in for web-a encrypted volume"); create_dropin("systemd-cryptsetup@web\\x2da.service")?; // web-b instance: systemd-cryptsetup@web\x2db.service + debug!("Creating systemd drop-in for web-b encrypted volume"); create_dropin("systemd-cryptsetup@web\\x2db.service")?; } } diff --git a/docs/Reference/Trident-CLI.md b/docs/Reference/Trident-CLI.md index 7fd9388ec..3d3295683 100644 --- a/docs/Reference/Trident-CLI.md +++ b/docs/Reference/Trident-CLI.md @@ -49,6 +49,7 @@ Options: - [get](#get) - [validate](#validate) - [offline-initialize](#offline-initialize) +- [daemon](#daemon) - [help](#help) @@ -506,6 +507,56 @@ Logging verbosity [OFF, ERROR, WARN, INFO, DEBUG, TRACE] Default: `DEBUG` +## daemon + +Usage: + +``` +trident daemon [OPTIONS] +``` + +Argument summary: + +``` +Options: + --inactivity-timeout + Inactivity timeout. The server will shut down automatically + after being inactive for this duration. Supports + human-readable durations, e.g., "5m", "1h30m", "300s" + [default: 300s] + -v, --verbosity + Logging verbosity [OFF, ERROR, WARN, INFO, DEBUG, TRACE] + [default: DEBUG] + --socket-path + Path to the UNIX socket to listen on when not running in + systemd socket-activated mode [default: + /var/run/trident.sock] +``` + + +### Argument Details + +#### --inactivity_timeout <INACTIVITY_TIMEOUT> + +Inactivity timeout. The server will shut down automatically after being inactive for this duration. Supports human-readable durations, e.g., "5m", "1h30m", "300s" + +Default: `300s` + + +#### --socket_path <SOCKET_PATH> + +Path to the UNIX socket to listen on when not running in systemd socket-activated mode + +Default: `/var/run/trident.sock` + + +#### --verbosity <VERBOSITY> + +Logging verbosity [OFF, ERROR, WARN, INFO, DEBUG, TRACE] + +Default: `DEBUG` + + ## help Print this message or the help of the given subcommand(s) diff --git a/website/scripts/create_versioned_docs.sh b/website/scripts/create_versioned_docs.sh index b83254d92..31b9a0d57 100755 --- a/website/scripts/create_versioned_docs.sh +++ b/website/scripts/create_versioned_docs.sh @@ -14,6 +14,7 @@ MAX_VERSION_COUNT=${MAX_VERSION_COUNT:-'-1'} EXCLUDED_VERSIONS=${EXCLUDED_VERSIONS:-''} DEV_BRANCH=${DEV_BRANCH:-'main'} +USE_MERGE_COMMIT=${USE_MERGE_COMMIT:-'false'} # Configuration REPO="microsoft/trident" @@ -95,6 +96,12 @@ create_version_docs() { if [[ "$DEBUG_USE_DEV_BRANCH" == "true" ]]; then # Debug: clone the dev branch gh repo clone "https://github.com/${REPO}.git" "${tmp_dir}" -- --depth 1 --branch "${DEV_BRANCH}" + elif [[ "$version" == "$DEV_BRANCH" ]] && [[ "$USE_MERGE_COMMIT" == "true" ]]; then + # For merge commits (external PRs), we need to clone and checkout the specific ref + gh repo clone "https://github.com/${REPO}.git" "${tmp_dir}" + cd "${tmp_dir}" + git fetch origin "${DEV_BRANCH}" + git checkout FETCH_HEAD else gh repo clone "https://github.com/${REPO}.git" "${tmp_dir}" -- --depth 1 --branch "${version}" fi From 3361fa6eed62c6f8db883417251f25f97051a526 Mon Sep 17 00:00:00 2001 From: Nadiia Dubchak Date: Tue, 13 Jan 2026 17:42:13 +0000 Subject: [PATCH 20/21] Reverted unneeded changes --- .github/workflows/deploy-website.yaml | 20 ++++------ docs/Reference/Trident-CLI.md | 51 ------------------------ website/scripts/create_versioned_docs.sh | 7 ---- 3 files changed, 7 insertions(+), 71 deletions(-) diff --git a/.github/workflows/deploy-website.yaml b/.github/workflows/deploy-website.yaml index f6287a1f2..d00ebcc52 100644 --- a/.github/workflows/deploy-website.yaml +++ b/.github/workflows/deploy-website.yaml @@ -1,6 +1,7 @@ name: Deploy to GitHub Pages on: + # Manual trigger workflow_dispatch: @@ -9,9 +10,9 @@ on: types: [opened, reopened, synchronize, ready_for_review] branches: [main] paths: - - "docs/**" - - "website/**" - - ".github/workflows/deploy-website.yaml" + - 'docs/**' + - 'website/**' + - '.github/workflows/deploy-website.yaml' # # Trigger on release # release: @@ -43,15 +44,8 @@ jobs: echo ${{ secrets.GITHUB_TOKEN }} | gh auth login --with-token # For pull requests, use branch as dev version so branch docs are validated if [[ "${{github.event_name}}" == "pull_request" ]]; then - # For external PRs, use the merge commit ref instead of the branch name - if [[ "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]]; then - export DEV_BRANCH="refs/pull/${{ github.event.number }}/merge" - export USE_MERGE_COMMIT="true" - echo "External PR detected. Using merge ref: $DEV_BRANCH" - else - export DEV_BRANCH=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}} - echo "Using PR branch as dev branch: $DEV_BRANCH" - fi + export DEV_BRANCH=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}} + echo "Using PR branch as dev branch: $DEV_BRANCH" fi # Construct multi-version docs ./scripts/create_versioned_docs.sh @@ -90,4 +84,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v4 \ No newline at end of file diff --git a/docs/Reference/Trident-CLI.md b/docs/Reference/Trident-CLI.md index 3d3295683..7fd9388ec 100644 --- a/docs/Reference/Trident-CLI.md +++ b/docs/Reference/Trident-CLI.md @@ -49,7 +49,6 @@ Options: - [get](#get) - [validate](#validate) - [offline-initialize](#offline-initialize) -- [daemon](#daemon) - [help](#help) @@ -507,56 +506,6 @@ Logging verbosity [OFF, ERROR, WARN, INFO, DEBUG, TRACE] Default: `DEBUG` -## daemon - -Usage: - -``` -trident daemon [OPTIONS] -``` - -Argument summary: - -``` -Options: - --inactivity-timeout - Inactivity timeout. The server will shut down automatically - after being inactive for this duration. Supports - human-readable durations, e.g., "5m", "1h30m", "300s" - [default: 300s] - -v, --verbosity - Logging verbosity [OFF, ERROR, WARN, INFO, DEBUG, TRACE] - [default: DEBUG] - --socket-path - Path to the UNIX socket to listen on when not running in - systemd socket-activated mode [default: - /var/run/trident.sock] -``` - - -### Argument Details - -#### --inactivity_timeout <INACTIVITY_TIMEOUT> - -Inactivity timeout. The server will shut down automatically after being inactive for this duration. Supports human-readable durations, e.g., "5m", "1h30m", "300s" - -Default: `300s` - - -#### --socket_path <SOCKET_PATH> - -Path to the UNIX socket to listen on when not running in systemd socket-activated mode - -Default: `/var/run/trident.sock` - - -#### --verbosity <VERBOSITY> - -Logging verbosity [OFF, ERROR, WARN, INFO, DEBUG, TRACE] - -Default: `DEBUG` - - ## help Print this message or the help of the given subcommand(s) diff --git a/website/scripts/create_versioned_docs.sh b/website/scripts/create_versioned_docs.sh index 31b9a0d57..b83254d92 100755 --- a/website/scripts/create_versioned_docs.sh +++ b/website/scripts/create_versioned_docs.sh @@ -14,7 +14,6 @@ MAX_VERSION_COUNT=${MAX_VERSION_COUNT:-'-1'} EXCLUDED_VERSIONS=${EXCLUDED_VERSIONS:-''} DEV_BRANCH=${DEV_BRANCH:-'main'} -USE_MERGE_COMMIT=${USE_MERGE_COMMIT:-'false'} # Configuration REPO="microsoft/trident" @@ -96,12 +95,6 @@ create_version_docs() { if [[ "$DEBUG_USE_DEV_BRANCH" == "true" ]]; then # Debug: clone the dev branch gh repo clone "https://github.com/${REPO}.git" "${tmp_dir}" -- --depth 1 --branch "${DEV_BRANCH}" - elif [[ "$version" == "$DEV_BRANCH" ]] && [[ "$USE_MERGE_COMMIT" == "true" ]]; then - # For merge commits (external PRs), we need to clone and checkout the specific ref - gh repo clone "https://github.com/${REPO}.git" "${tmp_dir}" - cd "${tmp_dir}" - git fetch origin "${DEV_BRANCH}" - git checkout FETCH_HEAD else gh repo clone "https://github.com/${REPO}.git" "${tmp_dir}" -- --depth 1 --branch "${version}" fi From 3fa8aa99596af1653c01885e371d28b1a3b521bb Mon Sep 17 00:00:00 2001 From: Brian Fjeldstad Date: Tue, 13 Jan 2026 22:54:12 +0000 Subject: [PATCH 21/21] allow version to be passed --- .pipelines/templates/trident-platform-cicd-template.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.pipelines/templates/trident-platform-cicd-template.yml b/.pipelines/templates/trident-platform-cicd-template.yml index 98907fa09..403ea0fd7 100644 --- a/.pipelines/templates/trident-platform-cicd-template.yml +++ b/.pipelines/templates/trident-platform-cicd-template.yml @@ -17,6 +17,12 @@ parameters: - amd64 - arm64 + - name: micVersion + displayName: MIC Version + type: string + default: "*.*.*" + + stages: - ${{ if eq( parameters.targetArchitecture, 'amd64') }}: - template: e2e-template.yml @@ -27,6 +33,8 @@ stages: forceFunctionalTestImageRebuild: true baremetalTestsEnabled: ${{ parameters.baremetalTestsEnabled }} baseImageArtifactStage: ${{ parameters.baseImageArtifactStage }} + micVersion: ${{ parameters.micVersion }} + micBuildType: dev - ${{ if eq( parameters.targetArchitecture, 'arm64') }}: - template: e2e-arm64-template.yml