From c4fdfa9805f7e7c103650ca7c9e279819835bb4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Be=C3=B1at=20Gartzia=20Arruabarrena?= Date: Tue, 20 Jan 2026 15:56:55 +0100 Subject: [PATCH 01/11] Containerfile: Build from fedora tagged buildroot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit trusted-execution-cluster buildroot was split into two separate images: fedora and centos-stream. That made newly built images to be pushed into those tags, instead of having a :latest tag. For that reason, even if latest fedora images would be shipped with cargo-1.92, only cargo-1.91 would be available on buildroot:latest. This commit updates references to buildroot (which implied :latest implicitly) to buildroot:fedora in all affected Containerfiles. Signed-off-by: Beñat Gartzia Arruabarrena --- Containerfile | 2 +- attestation-key-register/Containerfile | 2 +- compute-pcrs/Containerfile | 2 +- register-server/Containerfile | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Containerfile b/Containerfile index a2d168bd..cb718d20 100644 --- a/Containerfile +++ b/Containerfile @@ -5,7 +5,7 @@ ARG build_type # Dependency build stage -FROM ghcr.io/trusted-execution-clusters/buildroot AS builder +FROM ghcr.io/trusted-execution-clusters/buildroot:fedora AS builder ARG build_type WORKDIR /build diff --git a/attestation-key-register/Containerfile b/attestation-key-register/Containerfile index a287db7a..a49590bc 100644 --- a/attestation-key-register/Containerfile +++ b/attestation-key-register/Containerfile @@ -3,7 +3,7 @@ # SPDX-License-Identifier: CC0-1.0 ARG build_type -FROM ghcr.io/trusted-execution-clusters/buildroot AS builder +FROM ghcr.io/trusted-execution-clusters/buildroot:fedora AS builder ARG build_type WORKDIR /build diff --git a/compute-pcrs/Containerfile b/compute-pcrs/Containerfile index d1aa1e0e..b3154dc3 100644 --- a/compute-pcrs/Containerfile +++ b/compute-pcrs/Containerfile @@ -4,7 +4,7 @@ # SPDX-License-Identifier: CC0-1.0 ARG build_type -FROM ghcr.io/trusted-execution-clusters/buildroot AS builder +FROM ghcr.io/trusted-execution-clusters/buildroot:fedora AS builder ARG build_type WORKDIR /build diff --git a/register-server/Containerfile b/register-server/Containerfile index 975a202f..296af613 100644 --- a/register-server/Containerfile +++ b/register-server/Containerfile @@ -3,7 +3,7 @@ # SPDX-License-Identifier: CC0-1.0 ARG build_type -FROM ghcr.io/trusted-execution-clusters/buildroot AS builder +FROM ghcr.io/trusted-execution-clusters/buildroot:fedora AS builder ARG build_type WORKDIR /build From 56cd2a8b7ea053befcaa90478e16d97019e745db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Be=C3=B1at=20Gartzia=20Arruabarrena?= Date: Tue, 23 Dec 2025 08:33:31 +0100 Subject: [PATCH 02/11] rust: Update minimum toolchain to 1.92 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Minimum rust version was set to 1.85. Fedora is way above that threshold at the moment. Future EL releases will be above that as well. While on it, fix some of the linter errors that arise from the minimum version update. Signed-off-by: Beñat Gartzia Arruabarrena --- .github/workflows/lint.yml | 2 +- Cargo.toml | 2 +- attestation-key-register/src/main.rs | 15 ++++--- operator/src/attestation_key_register.rs | 29 ++++++-------- operator/src/reference_values.rs | 14 +++---- operator/src/trustee.rs | 2 +- test_utils/src/lib.rs | 51 +++++++++++------------- test_utils/src/virt.rs | 35 ++++++---------- tests/attestation.rs | 33 +++++++-------- tests/trusted_execution_cluster.rs | 25 +++++------- 10 files changed, 91 insertions(+), 117 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 50261314..aa3777f2 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -22,7 +22,7 @@ concurrency: env: CARGO_TERM_COLOR: always # Pinned toolchain for linting - ACTIONS_LINTS_TOOLCHAIN: 1.85.0 + ACTIONS_LINTS_TOOLCHAIN: 1.92.0 jobs: linting: diff --git a/Cargo.toml b/Cargo.toml index 87d9752a..ef1ce97a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ resolver = "3" [workspace.package] edition = "2024" -rust-version = "1.85" +rust-version = "1.92" [workspace.dependencies] anyhow = "1.0.100" diff --git a/attestation-key-register/src/main.rs b/attestation-key-register/src/main.rs index 165d686e..8a764973 100644 --- a/attestation-key-register/src/main.rs +++ b/attestation-key-register/src/main.rs @@ -40,7 +40,7 @@ async fn handle_registration( client: Client, addr: Option, ) -> Result { - info!("Received registration request: {:?}", registration); + info!("Received registration request: {registration:?}"); let api: Api = Api::default_namespaced(client); @@ -50,8 +50,7 @@ async fn handle_registration( if key.spec.public_key == registration.public_key { let existing_name = key.metadata.name.unwrap_or_default(); error!( - "Duplicate public key detected: already exists in AttestationKey '{}'", - existing_name + "Duplicate public key detected: already exists in AttestationKey '{existing_name}'" ); return Ok(reply::with_status( reply::json(&serde_json::json!({ @@ -64,11 +63,11 @@ async fn handle_registration( } } Err(e) => { - error!("Failed to list AttestationKeys: {}", e); + error!("Failed to list AttestationKeys: {e}"); return Ok(reply::with_status( reply::json(&serde_json::json!({ "status": "error", - "message": format!("Failed to check for existing keys: {}", e), + "message": format!("Failed to check for existing keys: {e}"), })), StatusCode::INTERNAL_SERVER_ERROR, )); @@ -106,11 +105,11 @@ async fn handle_registration( )) } Err(e) => { - error!("Failed to create AttestationKey: {}", e); + error!("Failed to create AttestationKey: {e}"); Ok(reply::with_status( reply::json(&serde_json::json!({ "status": "error", - "message": format!("Failed to create AttestationKey: {}", e), + "message": format!("Failed to create AttestationKey: {e}"), })), StatusCode::INTERNAL_SERVER_ERROR, )) @@ -145,7 +144,7 @@ async fn main() -> anyhow::Result<()> { .and_then(handle_registration); let addr = SocketAddr::from(([0, 0, 0, 0], args.port)); - info!("Listening on {}", addr); + info!("Listening on {addr}"); warp::serve(register).run(addr).await; diff --git a/operator/src/attestation_key_register.rs b/operator/src/attestation_key_register.rs index 5f56fa54..e126d5a4 100644 --- a/operator/src/attestation_key_register.rs +++ b/operator/src/attestation_key_register.rs @@ -135,13 +135,13 @@ async fn ak_reconcile( client: Arc, ) -> Result { let ak_name = ak.metadata.name.clone().unwrap_or_default(); - info!("Attestation Key reconciliation for: {}", ak_name); + info!("Attestation Key reconciliation for: {ak_name}"); let client = Arc::unwrap_or_clone(client); let machines: Api = Api::default_namespaced(client.clone()); let lp = ListParams::default(); let machine_list: ObjectList = machines.list(&lp).await.map_err(|e| { - eprintln!("Error fetching machine list: {}", e); + eprintln!("Error fetching machine list: {e}"); ControllerError::Anyhow(e.into()) })?; for machine in &machine_list.items { @@ -182,15 +182,15 @@ async fn machine_reconcile( let aks: Api = Api::default_namespaced(client.clone()); let lp = ListParams::default(); let ak_list: ObjectList = aks.list(&lp).await.map_err(|e| { - eprintln!("Error fetching attestation key list: {}", e); + eprintln!("Error fetching attestation key list: {e}"); ControllerError::Anyhow(e.into()) })?; for ak in ak_list.items { - if let Some(ak_address) = &ak.spec.address { - if *ak_address == machine_address { - approve_ak(&ak, &machine, client.clone()).await?; - return Ok(Action::await_change()); - } + if let Some(ak_address) = &ak.spec.address + && *ak_address == machine_address + { + approve_ak(&ak, &machine, client.clone()).await?; + return Ok(Action::await_change()); } } Ok(Action::await_change()) @@ -315,10 +315,7 @@ async fn secret_reconcile( return Ok(Action::await_change()); } - info!( - "Secret reconciliation for AttestationKey secret: {}", - secret_name - ); + info!("Secret reconciliation for AttestationKey secret: {secret_name}"); let secrets: Api = Api::default_namespaced(Arc::unwrap_or_clone(client.clone())); finalizer(&secrets, ATTESTATION_KEY_SECRET_FINALIZER, secret, |ev| async move { @@ -330,15 +327,14 @@ async fn secret_reconcile( .await .map(|_| Action::await_change()) .map_err(|e| { - eprintln!("Error updating attestation key volumes on secret apply: {}", e); + eprintln!("Error updating attestation key volumes on secret apply: {e}"); finalizer::Error::::ApplyFailed(e.into()) }) } Event::Cleanup(secret) => { let secret_name = secret.metadata.name.clone().unwrap_or_default(); info!( - "AttestationKey secret {} is being deleted, updating trustee deployment volumes", - secret_name + "AttestationKey secret {secret_name} is being deleted, updating trustee deployment volumes" ); let client = Arc::unwrap_or_clone(client); // Update trustee deployment - secrets with deletion_timestamp will be filtered out @@ -347,8 +343,7 @@ async fn secret_reconcile( .map(|_| Action::await_change()) .map_err(|e| { eprintln!( - "Error updating attestation key volumes during secret deletion: {}", - e + "Error updating attestation key volumes during secret deletion: {e}" ); finalizer::Error::::CleanupFailed(e.into()) }) diff --git a/operator/src/reference_values.rs b/operator/src/reference_values.rs index 29bd6485..671d5351 100644 --- a/operator/src/reference_values.rs +++ b/operator/src/reference_values.rs @@ -297,13 +297,13 @@ pub async fn handle_new_image( let config_maps: Api = Api::default_namespaced(ctx.client.clone()); let mut image_pcrs_map = config_maps.get(PCR_CONFIG_MAP).await?; let mut image_pcrs = get_image_pcrs(image_pcrs_map.clone())?; - if let Some(pcr) = image_pcrs.0.get(resource_name) { - if pcr.reference == boot_image { - info!("Image {boot_image} was to be allowed, but already was allowed"); - return trustee::update_reference_values(ctx) - .await - .map(|_| COMMITTED_REASON); - } + if let Some(pcr) = image_pcrs.0.get(resource_name) + && pcr.reference == boot_image + { + info!("Image {boot_image} was to be allowed, but already was allowed"); + return trustee::update_reference_values(ctx) + .await + .map(|_| COMMITTED_REASON); } let image_ref: oci_client::Reference = boot_image.parse()?; if image_ref.digest().is_none() { diff --git a/operator/src/trustee.rs b/operator/src/trustee.rs index 74ad65d2..65df147a 100644 --- a/operator/src/trustee.rs +++ b/operator/src/trustee.rs @@ -254,7 +254,7 @@ pub async fn update_attestation_keys(client: Client) -> Result<()> { name: secret_name.to_string(), items: Some(vec![KeyToPath { key: "public_key".to_string(), - path: format!("{}.pub", secret_name), + path: format!("{secret_name}.pub"), ..Default::default() }]), ..Default::default() diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index 60bd4d87..ee7fd4d9 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -225,13 +225,12 @@ impl TestContext { async move { let deployment = api.get(&name).await?; - if let Some(status) = &deployment.status { - if let Some(available_replicas) = status.available_replicas { - if available_replicas == 1 { - test_info!(&tn, "{} deployment has 1 available replica", name); - return Ok(()); - } - } + if let Some(status) = &deployment.status + && let Some(available_replicas) = status.available_replicas + && available_replicas == 1 + { + test_info!(&tn, "{} deployment has 1 available replica", name); + return Ok(()); } Err(anyhow::anyhow!( @@ -357,14 +356,14 @@ impl TestContext { let sa_src = workspace_root.join("config/rbac/service_account.yaml"); let sa_content = std::fs::read_to_string(&sa_src)? - .replace("namespace: system", &format!("namespace: {}", ns)); + .replace("namespace: system", &format!("namespace: {ns}")); let sa_dst = rbac_temp_dir.join("service_account.yaml"); std::fs::write(&sa_dst, sa_content)?; let role_path = rbac_temp_dir.join("role.yaml"); let role_content = std::fs::read_to_string(&role_path)?.replace( "name: trusted-cluster-operator-role", - &format!("name: {}-trusted-cluster-operator-role", ns), + &format!("name: {ns}-trusted-cluster-operator-role"), ); std::fs::write(&role_path, role_content)?; @@ -372,25 +371,25 @@ impl TestContext { let rb_content = std::fs::read_to_string(&rb_src)? .replace( "name: manager-rolebinding", - &format!("name: {}-manager-rolebinding", ns), + &format!("name: {ns}-manager-rolebinding"), ) .replace( "name: trusted-cluster-operator-role", - &format!("name: {}-trusted-cluster-operator-role", ns), + &format!("name: {ns}-trusted-cluster-operator-role"), ) - .replace("namespace: system", &format!("namespace: {}", ns)); + .replace("namespace: system", &format!("namespace: {ns}")); let rb_dst = rbac_temp_dir.join("role_binding.yaml"); std::fs::write(&rb_dst, rb_content)?; let le_role_src = workspace_root.join("config/rbac/leader_election_role.yaml"); let le_role_content = std::fs::read_to_string(&le_role_src)? - .replace("namespace: system", &format!("namespace: {}", ns)); + .replace("namespace: system", &format!("namespace: {ns}")); let le_role_dst = rbac_temp_dir.join("leader_election_role.yaml"); std::fs::write(&le_role_dst, le_role_content)?; let le_rb_src = workspace_root.join("config/rbac/leader_election_role_binding.yaml"); let le_rb_content = std::fs::read_to_string(&le_rb_src)? - .replace("namespace: system", &format!("namespace: {}", ns)); + .replace("namespace: system", &format!("namespace: {ns}")); let le_rb_dst = rbac_temp_dir.join("leader_election_role_binding.yaml"); std::fs::write(&le_rb_dst, le_rb_content)?; @@ -399,7 +398,7 @@ impl TestContext { r#"# SPDX-FileCopyrightText: Generated for testing # SPDX-License-Identifier: CC0-1.0 -namespace: {} +namespace: {ns} resources: - service_account.yaml @@ -407,8 +406,7 @@ resources: - role_binding.yaml - leader_election_role.yaml - leader_election_role_binding.yaml -"#, - ns +"# ); let temp_kustomization_path = rbac_temp_dir.join("kustomization.yaml"); @@ -436,19 +434,19 @@ resources: &self.test_name, "Updating CR manifest with publicTrusteeAddr" ); - let trustee_addr = format!("kbs-service.{}.svc.cluster.local:8080", ns); + let trustee_addr = format!("kbs-service.{ns}.svc.cluster.local:8080"); let cr_manifest_path = manifests_path.join("trusted_execution_cluster_cr.yaml"); let cr_content = std::fs::read_to_string(&cr_manifest_path)?; let mut cr_value: serde_yaml::Value = serde_yaml::from_str(&cr_content)?; - if let Some(spec) = cr_value.get_mut("spec") { - if let Some(spec_map) = spec.as_mapping_mut() { - spec_map.insert( - serde_yaml::Value::String("publicTrusteeAddr".to_string()), - serde_yaml::Value::String(trustee_addr.clone()), - ); - } + if let Some(spec) = cr_value.get_mut("spec") + && let Some(spec_map) = spec.as_mapping_mut() + { + spec_map.insert( + serde_yaml::Value::String("publicTrusteeAddr".to_string()), + serde_yaml::Value::String(trustee_addr.clone()), + ); } let updated_content = serde_yaml::to_string(&cr_value)?; @@ -494,8 +492,7 @@ resources: .with_timeout(Duration::from_secs(60)) .with_interval(Duration::from_secs(5)) .with_error_message(format!( - "image-pcrs ConfigMap in the namespace {} not found", - ns + "image-pcrs ConfigMap in the namespace {ns} not found" )); let test_name_owned = self.test_name.clone(); diff --git a/test_utils/src/virt.rs b/test_utils/src/virt.rs index 5a76574d..a642927f 100644 --- a/test_utils/src/virt.rs +++ b/test_utils/src/virt.rs @@ -49,10 +49,7 @@ pub fn generate_ssh_key_pair() -> anyhow::Result<(String, String, std::path::Pat let stderr = String::from_utf8_lossy(&ssh_add_output.stderr); // Clean up the key file if ssh-add fails let _ = fs::remove_file(&key_path); - return Err(anyhow::anyhow!( - "Failed to add SSH key to agent: {}", - stderr - )); + return Err(anyhow::anyhow!("Failed to add SSH key to agent: {stderr}")); } Ok((private_key_str, public_key_str, key_path)) @@ -138,10 +135,8 @@ pub fn generate_ignition_config( serde_json::to_value(&config).expect("Failed to serialize ignition config"); // Add attestation key registration field - let attestation_url = format!( - "http://attestation-key-register.{}.svc.cluster.local:8001/register-ak", - namespace - ); + let attestation_url = + format!("http://attestation-key-register.{namespace}.svc.cluster.local:8001/register-ak"); if let Some(obj) = ignition_json.as_object_mut() { obj.insert( @@ -268,8 +263,7 @@ pub async fn wait_for_vm_running( .with_timeout(Duration::from_secs(timeout_secs)) .with_interval(Duration::from_secs(5)) .with_error_message(format!( - "VirtualMachine {} did not reach Running phase after {} seconds", - vm_name, timeout_secs + "VirtualMachine {vm_name} did not reach Running phase after {timeout_secs} seconds" )); poller @@ -280,17 +274,15 @@ pub async fn wait_for_vm_running( let vm = api.get(&name).await?; // Check VM status phase - if let Some(status) = vm.status { - if let Some(phase) = status.printable_status { - if phase.as_str() == "Running" { - return Ok(()); - } - } + if let Some(status) = vm.status + && let Some(phase) = status.printable_status + && phase.as_str() == "Running" + { + return Ok(()); } Err(anyhow::anyhow!( - "VirtualMachine {} is not in Running phase yet", - name + "VirtualMachine {name} is not in Running phase yet" )) } }) @@ -309,7 +301,7 @@ pub async fn virtctl_ssh_exec( )); } - let _vm_target = format!("core@vmi/{}/{}", vm_name, namespace); + let _vm_target = format!("core@vmi/{vm_name}/{namespace}"); let full_cmd = format!( "virtctl ssh -i {} core@vmi/{}/{} -t '-o IdentitiesOnly=yes' -t '-o StrictHostKeyChecking=no' --known-hosts /dev/null -c '{}'", key_path.display(), @@ -321,7 +313,7 @@ pub async fn virtctl_ssh_exec( let output = Command::new("sh").arg("-c").arg(full_cmd).output().await?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); - return Err(anyhow::anyhow!("virtctl ssh command failed: {}", stderr)); + return Err(anyhow::anyhow!("virtctl ssh command failed: {stderr}")); } Ok(String::from_utf8_lossy(&output.stdout).to_string()) @@ -357,8 +349,7 @@ async fn wait_for_vm_ssh( .with_timeout(Duration::from_secs(timeout_secs)) .with_interval(Duration::from_secs(10)) .with_error_message(format!( - "SSH access to VM {}/{} did not become {}available after {} seconds", - namespace, vm_name, avail_prefix, timeout_secs + "SSH access to VM {namespace}/{vm_name} did not become {avail_prefix}available after {timeout_secs} seconds", )); poller diff --git a/tests/attestation.rs b/tests/attestation.rs index fecf6aef..94899dae 100644 --- a/tests/attestation.rs +++ b/tests/attestation.rs @@ -46,17 +46,15 @@ impl SingleAttestationContext { let (_private_key, public_key, key_path) = virt::generate_ssh_key_pair()?; test_ctx.info(format!( - "Generated SSH key pair and added to ssh-agent: {:?}", - key_path + "Generated SSH key pair and added to ssh-agent: {key_path:?}" )); let register_server_url = format!( - "http://register-server.{}.svc.cluster.local:8000/ignition-clevis-pin-trustee", - namespace + "http://register-server.{namespace}.svc.cluster.local:8000/ignition-clevis-pin-trustee" ); let image = "quay.io/trusted-execution-clusters/fedora-coreos-kubevirt:2026-14-01"; - test_ctx.info(format!("Creating VM: {}", vm_name)); + test_ctx.info(format!("Creating VM: {vm_name}")); virt::create_kubevirt_vm( client, namespace, @@ -67,11 +65,11 @@ impl SingleAttestationContext { ) .await?; - test_ctx.info(format!("Waiting for VM {} to reach Running state", vm_name)); + test_ctx.info(format!("Waiting for VM {vm_name} to reach Running state")); virt::wait_for_vm_running(client, namespace, vm_name, 300).await?; - test_ctx.info(format!("VM {} is Running", vm_name)); + test_ctx.info(format!("VM {vm_name} is Running")); - test_ctx.info(format!("Waiting for SSH access to VM {}", vm_name)); + test_ctx.info(format!("Waiting for SSH access to VM {vm_name}")); virt::wait_for_vm_ssh_ready(namespace, vm_name, &key_path, 600).await?; test_ctx.info("SSH access is ready"); @@ -124,8 +122,7 @@ async fn test_parallel_vm_attestation() -> anyhow::Result<()> { test_ctx.info("Generated SSH key pairs for both VMs"); let register_server_url = format!( - "http://register-server.{}.svc.cluster.local:8000/ignition-clevis-pin-trustee", - namespace + "http://register-server.{namespace}.svc.cluster.local:8000/ignition-clevis-pin-trustee" ); let image = "quay.io/trusted-execution-clusters/fedora-coreos-kubevirt:2026-14-01"; @@ -233,7 +230,7 @@ async fn test_vm_reboot_attestation() -> anyhow::Result<()> { // Perform multiple reboots let num_reboots = 3; for i in 1..=num_reboots { - test_ctx.info(format!("Performing reboot {} of {}", i, num_reboots)); + test_ctx.info(format!("Performing reboot {i} of {num_reboots}")); // Reboot the VM via SSH let _reboot_result = virt::virtctl_ssh_exec( @@ -244,27 +241,25 @@ async fn test_vm_reboot_attestation() -> anyhow::Result<()> { ) .await; - test_ctx.info(format!("Waiting for lack of SSH access after reboot {}", i)); + test_ctx.info(format!("Waiting for lack of SSH access after reboot {i}")); virt::wait_for_vm_ssh_unavail(namespace, vm_name, &att_ctx.key_path, 30).await?; - test_ctx.info(format!("Waiting for SSH access after reboot {}", i)); + test_ctx.info(format!("Waiting for SSH access after reboot {i}")); virt::wait_for_vm_ssh_ready(namespace, vm_name, &att_ctx.key_path, 300).await?; // Verify encrypted root is still present after reboot - test_ctx.info(format!("Verifying encrypted root after reboot {}", i)); + test_ctx.info(format!("Verifying encrypted root after reboot {i}")); let has_encrypted_root = virt::verify_encrypted_root(namespace, vm_name, &att_ctx.key_path, &att_ctx.root_key).await?; assert!( has_encrypted_root, - "VM should have encrypted root device after reboot {}", - i + "VM should have encrypted root device after reboot {i}" ); - test_ctx.info(format!("Reboot {}: attestation successful", i)); + test_ctx.info(format!("Reboot {i}: attestation successful")); } test_ctx.info(format!( - "VM successfully rebooted {} times with encrypted root device maintained", - num_reboots + "VM successfully rebooted {num_reboots} times with encrypted root device maintained" )); test_ctx.cleanup().await?; diff --git a/tests/trusted_execution_cluster.rs b/tests/trusted_execution_cluster.rs index 6a60cd49..abd0fc9c 100644 --- a/tests/trusted_execution_cluster.rs +++ b/tests/trusted_execution_cluster.rs @@ -60,14 +60,12 @@ async fn test_image_pcrs_configmap_updates() -> anyhow::Result<()> { async move { let cm = api.get("image-pcrs").await?; - if let Some(data) = &cm.data { - if let Some(image_pcrs_json) = data.get("image-pcrs.json") { - if let Ok(image_pcrs) = serde_json::from_str::(image_pcrs_json) { - if !image_pcrs.0.is_empty() { - return Ok(()); - } - } - } + if let Some(data) = &cm.data + && let Some(image_pcrs_json) = data.get("image-pcrs.json") + && let Ok(image_pcrs) = serde_json::from_str::(image_pcrs_json) + && !image_pcrs.0.is_empty() + { + return Ok(()); } Err(anyhow::anyhow!("image-pcrs ConfigMap not yet populated with image-pcrs.json data")) @@ -167,12 +165,11 @@ async fn test_image_disallow() -> anyhow::Result<()> { let api = configmap_api.clone(); async move { let cm = api.get("trustee-data").await?; - if let Some(data) = &cm.data { - if let Some(reference_values_json) = data.get("reference-values.json") { - if !reference_values_json.contains(EXPECTED_PCR4) { - return Ok(()); - } - } + if let Some(data) = &cm.data + && let Some(reference_values_json) = data.get("reference-values.json") + && !reference_values_json.contains(EXPECTED_PCR4) + { + return Ok(()); } Err(anyhow::anyhow!("Reference value not yet removed")) } From fb48953d862bcc2b990dcb35579f8e05e8aaaa4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Be=C3=B1at=20Gartzia=20Arruabarrena?= Date: Tue, 23 Dec 2025 10:04:39 +0100 Subject: [PATCH 03/11] workspace: Add hex as a dependency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit And update those crates that were making use of it. Later we will also use it in some other crates. Signed-off-by: Beñat Gartzia Arruabarrena --- Cargo.toml | 1 + operator/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ef1ce97a..c1ac7c52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ clevis-pin-trustee-lib = { git = "https://github.com/latchset/clevis-pin-trustee compute-pcrs-lib = { git = "https://github.com/trusted-execution-clusters/compute-pcrs" } env_logger = "0.11.8" http = "1.4.0" +hex = "0.4.3" ignition-config = "0.5.0" k8s-openapi = { version = "0.26.1", features = ["v1_33", "schemars"] } kube = { version = "2.0.1", default-features = false, features = ["derive", "runtime", "openssl-tls"] } diff --git a/operator/Cargo.toml b/operator/Cargo.toml index 0db4174e..03a04272 100644 --- a/operator/Cargo.toml +++ b/operator/Cargo.toml @@ -16,7 +16,7 @@ trusted-cluster-operator-lib = { path = "../lib" } compute-pcrs-lib.workspace = true env_logger.workspace = true futures-util = "0.3.31" -hex = "0.4.3" +hex.workspace = true json-patch = "4.1.0" jsonptr = "0.7.1" k8s-openapi.workspace = true From 406d4027bb62aebd6b6c0adf5f0db506420c5886 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Be=C3=B1at=20Gartzia=20Arruabarrena?= Date: Tue, 23 Dec 2025 10:30:14 +0100 Subject: [PATCH 04/11] bump: bring latest compute-pcrs in MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I broke some of the compute-pcrs' lib APIs, so apart from just pointing into a newer commit, we also need to tweak a couple of things here and there. Signed-off-by: Beñat Gartzia Arruabarrena --- Cargo.lock | 101 +++++++++++++++++++++++++---- operator/src/test_utils.rs | 10 +-- operator/src/trustee.rs | 6 +- tests/Cargo.toml | 1 + tests/trusted_execution_cluster.rs | 49 +++++++------- 5 files changed, 122 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 295482fd..e10978d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -445,16 +445,20 @@ dependencies = [ [[package]] name = "compute-pcrs-lib" version = "0.1.0" -source = "git+https://github.com/trusted-execution-clusters/compute-pcrs#1e7b9f74206e436d1426c335e30b2f1a6bd1681e" +source = "git+https://github.com/trusted-execution-clusters/compute-pcrs#f759749bcc00d4ee3a71992284bd39b6321deee5" dependencies = [ "anyhow", "glob", "hex", "hex-literal", + "itertools 0.14.0", "lief", + "log", "openssl", "serde", + "serde_with", "sha2", + "strum", "uuid", ] @@ -677,6 +681,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", + "serde_core", ] [[package]] @@ -881,7 +886,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -1652,6 +1657,8 @@ checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", "hashbrown 0.16.0", + "serde", + "serde_core", ] [[package]] @@ -1687,7 +1694,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -1720,6 +1727,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" @@ -1818,7 +1834,7 @@ checksum = "06d9e5e61dd037cdc51da0d7e2b2be10f497478ea7e120d85dad632adb99882b" dependencies = [ "base64 0.22.1", "chrono", - "schemars", + "schemars 1.1.0", "serde", "serde_json", ] @@ -1884,7 +1900,7 @@ dependencies = [ "http 1.4.0", "json-patch", "k8s-openapi", - "schemars", + "schemars 1.1.0", "serde", "serde-value", "serde_json", @@ -1955,9 +1971,9 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "lief" -version = "0.17.1" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18773f648622afc222330700726227739936a1fa0040b91f007c80605525c4ff" +checksum = "763b6760e243b6b2083e3abb6f84916050114a944c88d5e649356c81f2699804" dependencies = [ "bitflags 2.10.0", "cxx", @@ -1970,9 +1986,9 @@ dependencies = [ [[package]] name = "lief-build" -version = "0.17.1" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3abce5340e56c7f9191d699302b9496445b0438033b48a9937b41c506f677731" +checksum = "282503de62c5a17299c7406f90716ce7fc7977b4c5c60a33e60f762432510851" dependencies = [ "git-version", "miette", @@ -1983,9 +1999,9 @@ dependencies = [ [[package]] name = "lief-ffi" -version = "0.17.1" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fba2ccbe972b26212f69f7b938fc198214f4eaa7f548a8d724f1a04968137f96" +checksum = "d85732d1f35911b3d4c88ea8a2a8bfd82dd0fa9bd79679e01aa6f020e85aa899" dependencies = [ "autocxx", "cxx", @@ -2961,7 +2977,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -3025,6 +3041,18 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "schemars" version = "1.1.0" @@ -3210,6 +3238,37 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.12.0", + "schemars 0.9.0", + "schemars 1.1.0", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +dependencies = [ + "darling 0.21.3", + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "serde_yaml" version = "0.9.34+deprecated" @@ -3389,6 +3448,9 @@ name = "strum" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] [[package]] name = "strum_macros" @@ -3515,7 +3577,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -3586,10 +3648,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", + "itoa", "num-conv", "powerfmt", "serde", "time-core", + "time-macros", ] [[package]] @@ -3598,6 +3662,16 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +[[package]] +name = "time-macros" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" version = "0.8.2" @@ -3823,6 +3897,7 @@ version = "0.1.0" dependencies = [ "anyhow", "compute-pcrs-lib", + "hex", "k8s-openapi", "kube", "regex", diff --git a/operator/src/test_utils.rs b/operator/src/test_utils.rs index 6d8e5717..6b7c9381 100644 --- a/operator/src/test_utils.rs +++ b/operator/src/test_utils.rs @@ -19,16 +19,16 @@ pub fn dummy_pcrs() -> ImagePcrs { pcrs: vec![ Pcr { id: 0, - value: "pcr0_val".to_string(), - parts: vec![], + value: "pcr0_val".into(), + events: vec![], }, Pcr { id: 1, - value: "pcr1_val".to_string(), - parts: vec![], + value: "pcr1_val".into(), + events: vec![], }, ], - reference: "ref".to_string(), + reference: "".to_string(), }, )])) } diff --git a/operator/src/trustee.rs b/operator/src/trustee.rs index 65df147a..a68a3625 100644 --- a/operator/src/trustee.rs +++ b/operator/src/trustee.rs @@ -77,7 +77,7 @@ fn recompute_reference_values(image_pcrs: ImagePcrs) -> Vec { reference_values_in .entry(format!("pcr{}", pcr.id)) .or_default() - .push(JsonString(pcr.value.clone())); + .push(JsonString(hex::encode(pcr.value.clone()))); } reference_values_in .iter() @@ -552,7 +552,7 @@ mod tests { let config_map = dummy_pcrs_map(); let image_pcrs = get_image_pcrs(config_map).unwrap(); assert_eq!(image_pcrs.0["cos"].pcrs.len(), 2); - assert_eq!(image_pcrs.0["cos"].pcrs[0].value, "pcr0_val"); + assert_eq!(image_pcrs.0["cos"].pcrs[0].value, "pcr0_val".as_bytes()); } #[test] @@ -589,7 +589,7 @@ mod tests { let rv = result.iter().find(|rv| rv.name == "tpm_pcr0").unwrap(); let val_arr = rv.value.as_array().unwrap(); let vals: Vec<_> = val_arr.iter().map(|v| v.as_str().unwrap()).collect(); - assert_eq!(vals, vec!["pcr0_val".to_string()]); + assert_eq!(vals, vec![hex::encode("pcr0_val")]); } #[tokio::test] diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 8d6bd795..251918c4 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -17,6 +17,7 @@ anyhow.workspace = true trusted-cluster-operator-lib = { path = "../lib" } trusted-cluster-operator-test-utils = { path = "../test_utils" } compute-pcrs-lib.workspace = true +hex.workspace = true k8s-openapi.workspace = true kube = { workspace = true } regex = "1" diff --git a/tests/trusted_execution_cluster.rs b/tests/trusted_execution_cluster.rs index abd0fc9c..ad6923fa 100644 --- a/tests/trusted_execution_cluster.rs +++ b/tests/trusted_execution_cluster.rs @@ -2,7 +2,8 @@ // // SPDX-License-Identifier: MIT -use compute_pcrs_lib::{Part, Pcr}; +use compute_pcrs_lib::Pcr; +use compute_pcrs_lib::tpmevents::{TPMEvent, TPMEventID}; use k8s_openapi::api::apps::v1::Deployment; use k8s_openapi::api::core::v1::ConfigMap; use kube::{Api, api::DeleteParams}; @@ -95,37 +96,37 @@ async fn test_image_pcrs_configmap_updates() -> anyhow::Result<()> { let expected_pcrs = vec![ Pcr { id: 4, - value: EXPECTED_PCR4.to_string(), - parts: vec![ - Part { name: "EV_EFI_ACTION".to_string(), hash: "3d6772b4f84ed47595d72a2c4c5ffd15f5bb72c7507fe26f2aaee2c69d5633ba".to_string() }, - Part { name: "EV_SEPARATOR".to_string(), hash: "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119".to_string() }, - Part { name: "EV_EFI_BOOT_SERVICES_APPLICATION".to_string(), hash: "94896c17d49fc8c8df0cc2836611586edab1615ce7cb58cf13fc5798de56b367".to_string() }, - Part { name: "EV_EFI_BOOT_SERVICES_APPLICATION".to_string(), hash: "bc6844fc7b59b4f0c7da70a307fc578465411d7a2c34b0f4dc2cc154c873b644".to_string() }, - Part { name: "EV_EFI_BOOT_SERVICES_APPLICATION".to_string(), hash: "72c613f1b4d60dcf51f82f3458cca246580d23150130ec6751ac6fa62c867364".to_string() }, + value: hex::decode(EXPECTED_PCR4).unwrap(), + events: vec![ + TPMEvent { pcr: 4, name: "EV_EFI_ACTION".to_string(), hash: hex::decode("3d6772b4f84ed47595d72a2c4c5ffd15f5bb72c7507fe26f2aaee2c69d5633ba").unwrap(), id: TPMEventID::Pcr4EfiCall }, + TPMEvent { pcr: 4, name: "EV_SEPARATOR".to_string(), hash: hex::decode("df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119").unwrap(), id: TPMEventID::Pcr4Separator }, + TPMEvent { pcr: 4, name: "EV_EFI_BOOT_SERVICES_APPLICATION".to_string(), hash: hex::decode("94896c17d49fc8c8df0cc2836611586edab1615ce7cb58cf13fc5798de56b367").unwrap(), id: TPMEventID::Pcr4Shim }, + TPMEvent { pcr: 4, name: "EV_EFI_BOOT_SERVICES_APPLICATION".to_string(), hash: hex::decode("bc6844fc7b59b4f0c7da70a307fc578465411d7a2c34b0f4dc2cc154c873b644").unwrap(), id: TPMEventID::Pcr4Grub }, + TPMEvent { pcr: 4, name: "EV_EFI_BOOT_SERVICES_APPLICATION".to_string(), hash: hex::decode("2b1dc59bc61dbbc3db11a6f3b0708c948efd46cceb7f6c8ea2024b8d1b8c829a").unwrap(), id: TPMEventID::Pcr4Vmlinuz }, ], }, Pcr { id: 7, - value: "b3a56a06c03a65277d0a787fcabc1e293eaa5d6dd79398f2dda741f7b874c65d".to_string(), - parts: vec![ - Part { name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), hash: "ccfc4bb32888a345bc8aeadaba552b627d99348c767681ab3141f5b01e40a40e".to_string() }, - Part { name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), hash: "adb6fc232943e39c374bf4782b6c697f43c39fca1f4b51dfceda21164e19a893".to_string() }, - Part { name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), hash: "b5432fe20c624811cb0296391bfdf948ebd02f0705ab8229bea09774023f0ebf".to_string() }, - Part { name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), hash: "4313e43de720194a0eabf4d6415d42b5a03a34fdc47bb1fc924cc4e665e6893d".to_string() }, - Part { name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), hash: "001004ba58a184f09be6c1f4ec75a246cc2eefa9637b48ee428b6aa9bce48c55".to_string() }, - Part { name: "EV_SEPARATOR".to_string(), hash: "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119".to_string() }, - Part { name: "EV_EFI_VARIABLE_AUTHORITY".to_string(), hash: "4d4a8e2c74133bbdc01a16eaf2dbb5d575afeb36f5d8dfcf609ae043909e2ee9".to_string() }, - Part { name: "EV_EFI_VARIABLE_AUTHORITY".to_string(), hash: "e8e9578f5951ef16b1c1aa18ef02944b8375ec45ed4b5d8cdb30428db4a31016".to_string() }, - Part { name: "EV_EFI_VARIABLE_AUTHORITY".to_string(), hash: "ad5901fd581e6640c742c488083b9ac2c48255bd28a16c106c6f9df52702ee3f".to_string() }, + value: hex::decode("b3a56a06c03a65277d0a787fcabc1e293eaa5d6dd79398f2dda741f7b874c65d").unwrap(), + events: vec![ + TPMEvent { pcr: 7, name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), hash: hex::decode("ccfc4bb32888a345bc8aeadaba552b627d99348c767681ab3141f5b01e40a40e").unwrap(), id: TPMEventID::Pcr7SecureBoot }, + TPMEvent { pcr: 7, name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), hash: hex::decode("adb6fc232943e39c374bf4782b6c697f43c39fca1f4b51dfceda21164e19a893").unwrap(), id: TPMEventID::Pcr7Pk }, + TPMEvent { pcr: 7, name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), hash: hex::decode("b5432fe20c624811cb0296391bfdf948ebd02f0705ab8229bea09774023f0ebf").unwrap(), id: TPMEventID::Pcr7Kek }, + TPMEvent { pcr: 7, name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), hash: hex::decode("4313e43de720194a0eabf4d6415d42b5a03a34fdc47bb1fc924cc4e665e6893d").unwrap(), id: TPMEventID::Pcr7Db }, + TPMEvent { pcr: 7, name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), hash: hex::decode("001004ba58a184f09be6c1f4ec75a246cc2eefa9637b48ee428b6aa9bce48c55").unwrap(), id: TPMEventID::Pcr7Dbx }, + TPMEvent { pcr: 7, name: "EV_SEPARATOR".to_string(), hash: hex::decode("df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119").unwrap(), id: TPMEventID::Pcr7Separator }, + TPMEvent { pcr: 7, name: "EV_EFI_VARIABLE_AUTHORITY".to_string(), hash: hex::decode("4d4a8e2c74133bbdc01a16eaf2dbb5d575afeb36f5d8dfcf609ae043909e2ee9").unwrap(), id: TPMEventID::Pcr7ShimCert }, + TPMEvent { pcr: 7, name: "EV_EFI_VARIABLE_AUTHORITY".to_string(), hash: hex::decode("e8e9578f5951ef16b1c1aa18ef02944b8375ec45ed4b5d8cdb30428db4a31016").unwrap(), id: TPMEventID::Pcr7SbatLevel }, + TPMEvent { pcr: 7, name: "EV_EFI_VARIABLE_AUTHORITY".to_string(), hash: hex::decode("ad5901fd581e6640c742c488083b9ac2c48255bd28a16c106c6f9df52702ee3f").unwrap(), id: TPMEventID::Pcr7GrubMokListCert }, ], }, Pcr { id: 14, - value: "17cdefd9548f4383b67a37a901673bf3c8ded6f619d36c8007562de1d93c81cc".to_string(), - parts: vec![ - Part { name: "EV_IPL".to_string(), hash: "e8e48e3ad10bc243341b4663c0057aef0ec7894ccc9ecb0598f0830fa57f7220".to_string() }, - Part { name: "EV_IPL".to_string(), hash: "8d8a3aae50d5d25838c95c034aadce7b548c9a952eb7925e366eda537c59c3b0".to_string() }, - Part { name: "EV_IPL".to_string(), hash: "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a".to_string() }, + value: hex::decode("17cdefd9548f4383b67a37a901673bf3c8ded6f619d36c8007562de1d93c81cc").unwrap(), + events: vec![ + TPMEvent { pcr: 14, name: "EV_IPL".to_string(), hash: hex::decode("e8e48e3ad10bc243341b4663c0057aef0ec7894ccc9ecb0598f0830fa57f7220").unwrap(), id: TPMEventID::Pcr14MokList }, + TPMEvent { pcr: 14, name: "EV_IPL".to_string(), hash: hex::decode("8d8a3aae50d5d25838c95c034aadce7b548c9a952eb7925e366eda537c59c3b0").unwrap(), id: TPMEventID::Pcr14MokListX }, + TPMEvent { pcr: 14, name: "EV_IPL".to_string(), hash: hex::decode("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a").unwrap(), id: TPMEventID::Pcr14MokListTrusted }, ], }, ]; From d324763ef242df5d026fdca54851a6f074e2d6cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Be=C3=B1at=20Gartzia=20Arruabarrena?= Date: Mon, 22 Dec 2025 16:41:22 +0100 Subject: [PATCH 05/11] trustee.tests: Make dummy_pcrs a bit less dummy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit trustee tests were relying on some values that didn't hold any TPMEvent or any PCR value that related to them. This commit turns those dummy values into something that are closer to something we could expect in reality. The main reason to do this is that the compute-pcrs logic does not like empty event PCRs, as it's based on that to reconstruct them. This goes against the purpose of a unit test, as we should actually mock that external part so as not to rely on it, but decided this was simpler at least as an initial proposal. Signed-off-by: Beñat Gartzia Arruabarrena --- operator/src/test_utils.rs | 34 ++++++++++++++++++++++++++++------ operator/src/trustee.rs | 26 +++++++++++++++++++++----- 2 files changed, 49 insertions(+), 11 deletions(-) diff --git a/operator/src/test_utils.rs b/operator/src/test_utils.rs index 6b7c9381..95f0f572 100644 --- a/operator/src/test_utils.rs +++ b/operator/src/test_utils.rs @@ -3,6 +3,7 @@ // SPDX-License-Identifier: MIT use compute_pcrs_lib::Pcr; +use compute_pcrs_lib::tpmevents::{TPMEvent, TPMEventID}; use k8s_openapi::{api::core::v1::ConfigMap, chrono::Utc}; use kube::Client; use operator::RvContextData; @@ -11,6 +12,11 @@ use std::collections::BTreeMap; use crate::trustee; use trusted_cluster_operator_lib::reference_values::{ImagePcr, ImagePcrs, PCR_CONFIG_FILE}; +pub const DUMMY_PCR_4_VALUE: &str = + "3f263b96ccbc33bb53d808771f9ab1e02d4dec8854f9530f749cde853a723273"; +pub const DUMMY_PCR_7_VALUE: &str = + "e58ada1ba75f2e4722b539824598ad5e10c55f2e4aeab2033f3b0a8ee3f3eca6"; + pub fn dummy_pcrs() -> ImagePcrs { ImagePcrs(BTreeMap::from([( "cos".to_string(), @@ -18,14 +24,30 @@ pub fn dummy_pcrs() -> ImagePcrs { first_seen: Utc::now(), pcrs: vec![ Pcr { - id: 0, - value: "pcr0_val".into(), - events: vec![], + id: 4, + value: hex::decode(DUMMY_PCR_4_VALUE).unwrap(), + events: vec![TPMEvent { + name: "EV_EFI_ACTION".into(), + pcr: 4, + hash: hex::decode( + "3d6772b4f84ed47595d72a2c4c5ffd15f5bb72c7507fe26f2aaee2c69d5633ba", + ) + .unwrap(), + id: TPMEventID::Pcr4EfiCall, + }], }, Pcr { - id: 1, - value: "pcr1_val".into(), - events: vec![], + id: 7, + value: hex::decode(DUMMY_PCR_7_VALUE).unwrap(), + events: vec![TPMEvent { + name: "EV_EFI_VARIABLE_DRIVER_CONFIG".into(), + pcr: 7, + hash: hex::decode( + "ccfc4bb32888a345bc8aeadaba552b627d99348c767681ab3141f5b01e40a40e", + ) + .unwrap(), + id: TPMEventID::Pcr7SecureBoot, + }], }, ], reference: "".to_string(), diff --git a/operator/src/trustee.rs b/operator/src/trustee.rs index a68a3625..cf6283a8 100644 --- a/operator/src/trustee.rs +++ b/operator/src/trustee.rs @@ -547,12 +547,28 @@ mod tests { use kube::client::Body; use trusted_cluster_operator_test_utils::mock_client::*; + fn reference_values_from(reference_values: &[ReferenceValue], rv_name: &str) -> Vec { + let rv = reference_values + .iter() + .find(|rv| rv.name == rv_name) + .unwrap(); + let val_arr = rv.value.as_array().unwrap(); + val_arr.iter().map(|v| v.as_str().unwrap().into()).collect() + } + #[test] fn test_get_image_pcrs_success() { let config_map = dummy_pcrs_map(); let image_pcrs = get_image_pcrs(config_map).unwrap(); assert_eq!(image_pcrs.0["cos"].pcrs.len(), 2); - assert_eq!(image_pcrs.0["cos"].pcrs[0].value, "pcr0_val".as_bytes()); + assert_eq!( + hex::encode(image_pcrs.0["cos"].pcrs[0].value.clone()), + DUMMY_PCR_4_VALUE + ); + assert_eq!( + hex::encode(image_pcrs.0["cos"].pcrs[1].value.clone()), + "e58ada1ba75f2e4722b539824598ad5e10c55f2e4aeab2033f3b0a8ee3f3eca6" + ); } #[test] @@ -586,10 +602,10 @@ mod tests { fn test_recompute_reference_values() { let result = recompute_reference_values(dummy_pcrs()); assert_eq!(result.len(), 3); - let rv = result.iter().find(|rv| rv.name == "tpm_pcr0").unwrap(); - let val_arr = rv.value.as_array().unwrap(); - let vals: Vec<_> = val_arr.iter().map(|v| v.as_str().unwrap()).collect(); - assert_eq!(vals, vec![hex::encode("pcr0_val")]); + let vals = reference_values_from(&result, "tpm_pcr4"); + assert_eq!(vals, vec![DUMMY_PCR_4_VALUE,]); + let vals = reference_values_from(&result, "tpm_pcr7"); + assert_eq!(vals, vec![DUMMY_PCR_7_VALUE,]); } #[tokio::test] From 151401823ff3585e140f243bde9f15228399cb81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Be=C3=B1at=20Gartzia=20Arruabarrena?= Date: Mon, 22 Dec 2025 17:11:27 +0100 Subject: [PATCH 06/11] trustee: Combine approved image PCR values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update trustee reference values with all possible combinations between approved PCR values, not just those that come directly from image PCRs. This computes reference values for possible stages during node updates in which a node could be booting some components from image A and some other from image B. This commit also adds a test to check that pcr4 is well covered in front of bootloader and kernel updates. The test is closer to an integration test than to an unit-test as it relies on values that are close to real values, and how the compute-pcrs lib's combine_images processes them. Signed-off-by: Beñat Gartzia Arruabarrena --- operator/src/trustee.rs | 168 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 162 insertions(+), 6 deletions(-) diff --git a/operator/src/trustee.rs b/operator/src/trustee.rs index cf6283a8..0a1a6f5a 100644 --- a/operator/src/trustee.rs +++ b/operator/src/trustee.rs @@ -7,6 +7,8 @@ use anyhow::{Context, Result}; use base64::{Engine as _, engine::general_purpose}; use clevis_pin_trustee_lib::Key as ClevisKey; +use compute_pcrs_lib::tpmevents::TPMEvent; +use compute_pcrs_lib::tpmevents::combine::combine_images; use k8s_openapi::api::apps::v1::{Deployment, DeploymentSpec}; use k8s_openapi::api::core::v1::{ ConfigMap, ConfigMapVolumeSource, Container, ContainerPort, EmptyDirVolumeSource, EnvVar, @@ -26,7 +28,7 @@ use log::info; use operator::{RvContextData, create_or_info_if_exists}; use serde::{Serialize, Serializer}; use serde_json::{Value::String as JsonString, json}; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use trusted_cluster_operator_lib::reference_values::*; const TRUSTEE_DATA_DIR: &str = "/opt/trustee"; @@ -70,14 +72,20 @@ pub fn get_image_pcrs(image_pcrs_map: ConfigMap) -> Result { } fn recompute_reference_values(image_pcrs: ImagePcrs) -> Vec { - // TODO many grub+shim:many OS image recompute once supported let mut reference_values_in = - BTreeMap::from([("svn".to_string(), vec![JsonString("1".to_string())])]); - for pcr in image_pcrs.0.values().flat_map(|v| &v.pcrs) { + BTreeMap::from([("svn".to_string(), BTreeSet::from(["1".to_string()]))]); + let tpm_events: Vec> = image_pcrs + .0 + .values() + .map(|v| v.pcrs.iter().flat_map(|p| p.events.clone()).collect()) + .collect(); + + let pcr_combinations = combine_images(&tpm_events); + for pcr in pcr_combinations.iter().flatten() { reference_values_in .entry(format!("pcr{}", pcr.id)) .or_default() - .push(JsonString(hex::encode(pcr.value.clone()))); + .insert(hex::encode(pcr.value.clone())); } reference_values_in .iter() @@ -85,7 +93,7 @@ fn recompute_reference_values(image_pcrs: ImagePcrs) -> Vec { version: "0.1.0".to_string(), name: format!("tpm_{name}"), expiration: Utc::now() + TimeDelta::days(365), - value: serde_json::Value::Array(values.to_vec()), + value: serde_json::Value::Array(values.iter().map(|v| JsonString(v.clone())).collect()), }) .collect() } @@ -543,6 +551,8 @@ pub async fn generate_kbs_deployment( mod tests { use super::*; use crate::test_utils::*; + use compute_pcrs_lib::Pcr; + use compute_pcrs_lib::tpmevents::TPMEventID; use http::{Method, Request, StatusCode}; use kube::client::Body; use trusted_cluster_operator_test_utils::mock_client::*; @@ -866,4 +876,150 @@ mod tests { let clos = |client| generate_kbs_deployment(client, Default::default(), "image"); test_create_error(clos).await; } + + #[test] + fn test_recompute_reference_values_pcr4() { + let image_pcrs = ImagePcrs(BTreeMap::from([ + ( + "cos1".to_string(), + ImagePcr { + first_seen: Utc::now(), + pcrs: vec![Pcr { + id: 4, + value: hex::decode("852718920421131081032051205110114719423559841238794129122376912159784392212168").unwrap(), + events: vec![ + TPMEvent { + name: "EV_EFI_ACTION".into(), + pcr: 4, + hash: hex::decode("3d6772b4f84ed47595d72a2c4c5ffd15f5bb72c7507fe26f2aaee2c69d5633ba") + .unwrap(), + id: TPMEventID::Pcr4EfiCall, + }, + TPMEvent { + name: "EV_SEPARATOR".into(), + pcr: 4, + hash: hex::decode("df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119") + .unwrap(), + id: TPMEventID::Pcr4Separator, + }, + TPMEvent { + name: "EV_EFI_BOOT_SERVICES_APPLICATION".into(), + pcr: 4, + hash: hex::decode("94896c17d49fc8c8df0cc2836611586edab1615ce7cb58cf13fc5798de56b367") + .unwrap(), + id: TPMEventID::Pcr4Shim, + }, + TPMEvent { + name: "EV_EFI_BOOT_SERVICES_APPLICATION".into(), + pcr: 4, + hash: hex::decode("bc6844fc7b59b4f0c7da70a307fc578465411d7a2c34b0f4dc2cc154c873b644") + .unwrap(), + id: TPMEventID::Pcr4Grub, + }, + TPMEvent { + name: "EV_EFI_BOOT_SERVICES_APPLICATION".into(), + pcr: 4, + hash: hex::decode("2b1dc59bc61dbbc3db11a6f3b0708c948efd46cceb7f6c8ea2024b8d1b8c829a") + .unwrap(), + id: TPMEventID::Pcr4Vmlinuz, + }, + ], + }, + Pcr { + id: 7, + value: hex::decode("3f263b96ccbc33bb53d808771f9ab1e02d4dec8854f9530f749cde853a723273").unwrap(), + events: vec![ + TPMEvent { + name: "EV_EFI_ACTION".into(), + pcr: 7, + hash: hex::decode("3d6772b4f84ed47595d72a2c4c5ffd15f5bb72c7507fe26f2aaee2c69d5633ba") + .unwrap(), + id: TPMEventID::Pcr7SecureBoot, + }, + ] + }], + reference: "".to_string(), + }, + ), + ( + "cos2".to_string(), + ImagePcr { + first_seen: Utc::now(), + pcrs: vec![Pcr { + id: 4, + value: hex::decode("19925299236966772216371371471692276818611442625320115173412649113251558526237189").unwrap(), + events: vec![ + TPMEvent { + name: "EV_EFI_ACTION".into(), + pcr: 4, + hash: hex::decode("3d6772b4f84ed47595d72a2c4c5ffd15f5bb72c7507fe26f2aaee2c69d5633ba") + .unwrap(), + id: TPMEventID::Pcr4EfiCall, + }, + TPMEvent { + name: "EV_SEPARATOR".into(), + pcr: 4, + hash: hex::decode("df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119") + .unwrap(), + id: TPMEventID::Pcr4Separator, + }, + TPMEvent { + name: "EV_EFI_BOOT_SERVICES_APPLICATION".into(), + pcr: 4, + hash: hex::decode("1fed6fad5ca735adc80615d2a7e795e2f17f84e407b07979498c9edb1e04383f") + .unwrap(), + id: TPMEventID::Pcr4Shim, + }, + TPMEvent { + name: "EV_EFI_BOOT_SERVICES_APPLICATION".into(), + pcr: 4, + hash: hex::decode("8f3adc6b42da2defa6d5ef3202badc39a5a22ceec068f106760592163a505a0e") + .unwrap(), + id: TPMEventID::Pcr4Grub, + }, + TPMEvent { + name: "EV_EFI_BOOT_SERVICES_APPLICATION".into(), + pcr: 4, + hash: hex::decode("772c3a90820e4a76944d3715e6f700bc41e846b0049b7817f9feb3289a56d3f8") + .unwrap(), + id: TPMEventID::Pcr4Vmlinuz, + }, + ], + }, + Pcr { + id: 7, + value: hex::decode("3f263b96ccbc33bb53d808771f9ab1e02d4dec8854f9530f749cde853a723273").unwrap(), + events: vec![ + TPMEvent { + name: "EV_EFI_ACTION".into(), + pcr: 7, + hash: hex::decode("3d6772b4f84ed47595d72a2c4c5ffd15f5bb72c7507fe26f2aaee2c69d5633ba") + .unwrap(), + id: TPMEventID::Pcr7SecureBoot, + }, + ] + }], + reference: "".to_string(), + }, + ), + ])); + + let result = recompute_reference_values(image_pcrs); + assert_eq!(result.len(), 3); + let vals_pcr4 = reference_values_from(&result, "tpm_pcr4"); + assert_eq!( + vals_pcr4, + vec![ + "47b742b3a2244cc7249ff3221ec640198044aac533a95abafded7921237508c1", + "551bbd142a716c67cd78336593c2eb3b547b575e810ced4501d761082b5cd4a8", + "c7fc63ec604348d8258993a9e344ba72041afd1473ad291a3171199b551aedbd", + "c9c3add791efc98f59977c89e673a34ad0b357872e9eb2c43d14607488e5d9e2", + ] + ); + let vals_pcr7 = reference_values_from(&result, "tpm_pcr7"); + assert_eq!( + vals_pcr7, + vec!["3f263b96ccbc33bb53d808771f9ab1e02d4dec8854f9530f749cde853a723273"] + ); + } } From c5dedf7777d9bf24dd81001d2070caf08d9b532f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Be=C3=B1at=20Gartzia=20Arruabarrena?= Date: Mon, 12 Jan 2026 15:53:00 +0100 Subject: [PATCH 07/11] trusted-cluster-gen: Support multiple images MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Support the possibility of generating CRs for multiple approved images. These are stored sepparately in different yaml files. This changes the way trusted-cluster-gen names approved_image_cr.yaml files, as it's not just one, but many of them that can be marshalled. That's why test dependencies are updated to also be able to handle multiple approved image CRs. Signed-off-by: Beñat Gartzia Arruabarrena --- Cargo.lock | 1 + Makefile | 2 +- api/trusted-cluster-gen.go | 62 +++++++++++++++++------------- test_utils/Cargo.toml | 1 + test_utils/src/lib.rs | 26 ++++++++----- tests/trusted_execution_cluster.rs | 2 +- 6 files changed, 57 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e10978d3..cd3ec38a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3874,6 +3874,7 @@ dependencies = [ "clevis-pin-trustee-lib", "compute-pcrs-lib", "env_logger", + "glob", "http 1.4.0", "ignition-config", "k8s-openapi", diff --git a/Makefile b/Makefile index a434f41b..bcf2d4d9 100644 --- a/Makefile +++ b/Makefile @@ -155,7 +155,7 @@ endif sed 's//$(NAMESPACE)/g' kind/kbs-forward.yaml | $(KUBECTL) apply -f -; \ fi $(KUBECTL) apply -f $(DEPLOY_PATH)/trusted_execution_cluster_cr.yaml - $(KUBECTL) apply -f $(DEPLOY_PATH)/approved_image_cr.yaml + $(KUBECTL) apply -f '$(DEPLOY_PATH)/approved_image_cr_*.yaml' install-kubevirt: scripts/install-kubevirt.sh diff --git a/api/trusted-cluster-gen.go b/api/trusted-cluster-gen.go index 2b590b51..5859343a 100644 --- a/api/trusted-cluster-gen.go +++ b/api/trusted-cluster-gen.go @@ -21,6 +21,17 @@ import ( "sigs.k8s.io/yaml" ) +type stringSlice []string + +func (s *stringSlice) String() string { + return strings.Join(*s, ", ") +} + +func (s *stringSlice) Set(value string) error { + *s = append(*s, value) + return nil +} + type Args struct { outputDir string image string @@ -29,7 +40,7 @@ type Args struct { pcrsComputeImage string registerServerImage string attestationKeyRegisterImage string - approvedImage string + approvedImages stringSlice } func main() { @@ -41,7 +52,7 @@ func main() { flag.StringVar(&args.pcrsComputeImage, "pcrs-compute-image", "quay.io/trusted-execution-clusters/compute-pcrs:latest", "Container image with the Trusted Execution Clusters compute-pcrs binary") flag.StringVar(&args.registerServerImage, "register-server-image", "quay.io/trusted-execution-clusters/register-server:latest", "Register server image to use in the deployment") flag.StringVar(&args.attestationKeyRegisterImage, "attestation-key-register-image", "quay.io/trusted-execution-clusters/attestation-key-register:latest", "Attestation key register image to use in the deployment") - flag.StringVar(&args.approvedImage, "approved-image", "", "When set, defines an initial approved image. Must be a bootable container image with SHA reference.") + flag.Var(&args.approvedImages, "approved-image", "When set, defines an initial approved image. Must be a bootable container image with SHA reference. Can be set multiple times.") flag.Parse() log.SetFlags(log.LstdFlags) @@ -166,34 +177,33 @@ func generateTrustedExecutionClusterCR(args *Args) error { } func generateApprovedImageCR(args *Args) error { - if args.approvedImage == "" { - return nil - } + for i, approvedImage := range args.approvedImages { + approvedImage := &v1alpha1.ApprovedImage{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1alpha1.GroupVersion.String(), + Kind: "ApprovedImage", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("coreos-%d", i), + Namespace: args.namespace, + }, + Spec: v1alpha1.ApprovedImageSpec{ + Reference: approvedImage, + }, + } - approvedImage := &v1alpha1.ApprovedImage{ - TypeMeta: metav1.TypeMeta{ - APIVersion: v1alpha1.GroupVersion.String(), - Kind: "ApprovedImage", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "coreos", - Namespace: args.namespace, - }, - Spec: v1alpha1.ApprovedImageSpec{ - Reference: args.approvedImage, - }, - } + approvedImageYAML, err := yaml.Marshal(approvedImage) + if err != nil { + return fmt.Errorf("failed to marshal ApprovedImage CR %d: %v", i, err) + } - approvedImageYAML, err := yaml.Marshal(approvedImage) - if err != nil { - return fmt.Errorf("failed to marshal ApprovedImage CR: %v", err) + outputPath := filepath.Join(args.outputDir, fmt.Sprintf("approved_image_cr_%d.yaml", i)) + if err := writeResources(outputPath, []string{string(approvedImageYAML)}); err != nil { + return fmt.Errorf("failed to write %s: %v", outputPath, err) + } + log.Printf("Generated ApprovedImage CR at %s", outputPath) } - outputPath := filepath.Join(args.outputDir, "approved_image_cr.yaml") - if err := writeResources(outputPath, []string{string(approvedImageYAML)}); err != nil { - return fmt.Errorf("failed to write %s: %v", outputPath, err) - } - log.Printf("Generated ApprovedImage CR at %s", outputPath) return nil } diff --git a/test_utils/Cargo.toml b/test_utils/Cargo.toml index 8aaf4164..183816c7 100644 --- a/test_utils/Cargo.toml +++ b/test_utils/Cargo.toml @@ -31,3 +31,4 @@ tokio = { workspace = true, features = ["process"] } tower = { version = "0.5.2", features = ["full"] } uuid.workspace = true which = "8.0" +glob = "0.3.3" diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index ee7fd4d9..c22a7f00 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: MIT +use glob::glob; use k8s_openapi::api::apps::v1::Deployment; use k8s_openapi::api::core::v1::{ConfigMap, Namespace}; use kube::api::DeleteParams; @@ -463,15 +464,22 @@ resources: .ok_or_else(|| anyhow::anyhow!("Invalid CR manifest path"))?; kube_apply!(cr_manifest_str, &self.test_name, "Applying CR manifest"); - let approved_image_path = manifests_path.join("approved_image_cr.yaml"); - let approved_image_str = approved_image_path - .to_str() - .ok_or_else(|| anyhow::anyhow!("Invalid ApprovedImage manifest path"))?; - kube_apply!( - approved_image_str, - &self.test_name, - "Applying ApprovedImage manifest" - ); + let approved_image_paths = glob( + manifests_path + .join("approved_image_cr_*.yaml") + .to_str() + .ok_or_else(|| anyhow::anyhow!("Invalid ApprovedImage manifest path"))?, + )?; + for approved_image_path in approved_image_paths.filter_map(Result::ok) { + let approved_image_str = approved_image_path + .to_str() + .ok_or_else(|| anyhow::anyhow!("Invalid ApprovedImage manifest path"))?; + kube_apply!( + approved_image_str, + &self.test_name, + "Applying ApprovedImage manifest" + ); + } let deployments_api: Api = Api::namespaced(self.client.clone(), &ns); diff --git a/tests/trusted_execution_cluster.rs b/tests/trusted_execution_cluster.rs index ad6923fa..986ee64b 100644 --- a/tests/trusted_execution_cluster.rs +++ b/tests/trusted_execution_cluster.rs @@ -155,7 +155,7 @@ async fn test_image_disallow() -> anyhow::Result<()> { let namespace = test_ctx.namespace(); let images: Api = Api::namespaced(client.clone(), namespace); - images.delete("coreos", &DeleteParams::default()).await?; + images.delete("coreos-0", &DeleteParams::default()).await?; let configmap_api: Api = Api::namespaced(client.clone(), namespace); let poller = Poller::new() From 0df86b4942465b2e9e3c6cf0451668a57a24dc1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Be=C3=B1at=20Gartzia=20Arruabarrena?= Date: Tue, 13 Jan 2026 08:49:43 +0100 Subject: [PATCH 08/11] test_utils: Configurable amount of approved images for tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Modify the new TestContext function (and those below) to accept an arbitrary number of approved images for tests. In the setup! macro, hardcode the approved image that was used by tests until now. Also, add an alternate macro interface so it can accept an arbitrary numer of images. Signed-off-by: Beñat Gartzia Arruabarrena --- test_utils/src/lib.rs | 54 +++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index c22a7f00..927d4948 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -22,6 +22,8 @@ pub mod virt; use compute_pcrs_lib::Pcr; +pub const DEFAULT_TEST_FCOS_IMAGE: &str = "quay.io/trusted-execution-clusters/fedora-coreos@sha256:79a0657399e6c67c7c95b8a09193d18e5675b5aa3cfb4d75ea5c8d4d53b2af74"; + pub fn compare_pcrs(actual: &[Pcr], expected: &[Pcr]) -> bool { if actual.len() != expected.len() { return false; @@ -83,7 +85,7 @@ pub struct TestContext { } impl TestContext { - pub async fn new(test_name: &str) -> anyhow::Result { + pub async fn new(test_name: &str, approved_images: &[&str]) -> anyhow::Result { INIT.call_once(|| { let _ = env_logger::builder().is_test(true).try_init(); }); @@ -103,7 +105,7 @@ impl TestContext { ctx.manifests_dir = manifests_dir; ctx.create_namespace().await?; - ctx.apply_operator_manifests().await?; + ctx.apply_operator_manifests(approved_images).await?; test_info!( &ctx.test_name, @@ -242,7 +244,7 @@ impl TestContext { .await } - async fn apply_operator_manifests(&self) -> anyhow::Result<()> { + async fn apply_operator_manifests(&self, approved_images: &[&str]) -> anyhow::Result<()> { test_info!( &self.test_name, "Generating manifests in {}", @@ -301,25 +303,31 @@ impl TestContext { )); } + let mut trusted_cluster_gen_args = vec![ + "-namespace", + &ns, + "-output-dir", + &self.manifests_dir, + "-image", + "localhost:5000/trusted-execution-clusters/trusted-cluster-operator:latest", + "-pcrs-compute-image", + "localhost:5000/trusted-execution-clusters/compute-pcrs:latest", + "-trustee-image", + "quay.io/trusted-execution-clusters/key-broker-service:20260106", + "-register-server-image", + "localhost:5000/trusted-execution-clusters/registration-server:latest", + "-attestation-key-register-image", + "localhost:5000/trusted-execution-clusters/attestation-key-register:latest", + ]; + + trusted_cluster_gen_args.extend( + approved_images + .iter() + .flat_map(|&i| vec!["-approved-image", i]), + ); + let manifest_gen_output = Command::new(&trusted_cluster_gen_path) - .args([ - "-namespace", - &ns, - "-output-dir", - &self.manifests_dir, - "-image", - "localhost:5000/trusted-execution-clusters/trusted-cluster-operator:latest", - "-pcrs-compute-image", - "localhost:5000/trusted-execution-clusters/compute-pcrs:latest", - "-trustee-image", - "quay.io/trusted-execution-clusters/key-broker-service:20260106", - "-register-server-image", - "localhost:5000/trusted-execution-clusters/registration-server:latest", - "-attestation-key-register-image", - "localhost:5000/trusted-execution-clusters/attestation-key-register:latest", - "-approved-image", - "quay.io/trusted-execution-clusters/fedora-coreos@sha256:79a0657399e6c67c7c95b8a09193d18e5675b5aa3cfb4d75ea5c8d4d53b2af74" - ]) + .args(trusted_cluster_gen_args) .output() .await?; @@ -548,7 +556,9 @@ macro_rules! virt_test { #[macro_export] macro_rules! setup { - () => {{ $crate::TestContext::new(TEST_NAME) }}; + () => {{ $crate::TestContext::new(TEST_NAME, &[DEFAULT_TEST_FCOS_IMAGE]) }}; + + ($images:expr) => {{ $crate::TestContext::new(TEST_NAME, &$images) }}; } async fn setup_test_client() -> anyhow::Result { From fdd81f8d877d26a0d8ccbe954937647a57d19188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Be=C3=B1at=20Gartzia=20Arruabarrena?= Date: Thu, 15 Jan 2026 11:40:26 +0100 Subject: [PATCH 09/11] test_utils: Verify expected pcrs based on TestContext MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor the logic that was verifying expected image pcrs in test_image_pcrs_configmap_updates as a method of TestContext. It also slightly changes the logic of the poller to stop when the length of pcrs equals the one expected. Signed-off-by: Beñat Gartzia Arruabarrena --- test_utils/src/lib.rs | 90 ++++++++++ tests/trusted_execution_cluster.rs | 277 +++++++++++++++++++---------- 2 files changed, 271 insertions(+), 96 deletions(-) diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index 927d4948..961cce95 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -12,6 +12,7 @@ use std::path::Path; use std::sync::Once; use std::time::Duration; use tokio::process::Command; +use trusted_cluster_operator_lib::reference_values::ImagePcrs; pub mod timer; pub use timer::Poller; @@ -528,6 +529,95 @@ resources: Ok(()) } + + pub async fn verify_expected_pcrs(&self, expected_pcrs: &[&[Pcr]]) -> anyhow::Result<()> { + let client = self.client(); + let namespace = self.namespace(); + + let configmap_api: Api = Api::namespaced(client.clone(), namespace); + + let poller = Poller::new() + .with_timeout(Duration::from_secs(180)) + .with_interval(Duration::from_secs(5)) + .with_error_message("image-pcrs ConfigMap not populated with data".to_string()); + + poller + .poll_async(|| { + let api = configmap_api.clone(); + async move { + let cm = api.get("image-pcrs").await?; + + if let Some(data) = &cm.data + && let Some(image_pcrs_json) = data.get("image-pcrs.json") + && let Ok(image_pcrs) = serde_json::from_str::(image_pcrs_json) + && image_pcrs.0.len() == expected_pcrs.len() + { + return Ok(()); + } + + Err(anyhow::anyhow!( + "image-pcrs ConfigMap not yet populated with image-pcrs.json data" + )) + } + }) + .await?; + + let image_pcrs_cm = configmap_api.get("image-pcrs").await?; + assert_eq!(image_pcrs_cm.metadata.name.as_deref(), Some("image-pcrs")); + + let data = image_pcrs_cm + .data + .as_ref() + .expect("image-pcrs ConfigMap should have data field"); + + assert!(!data.is_empty(), "image-pcrs ConfigMap should have data"); + + let image_pcrs_json = data + .get("image-pcrs.json") + .expect("image-pcrs ConfigMap should have image-pcrs.json key"); + + assert!( + !image_pcrs_json.is_empty(), + "image-pcrs.json should not be empty" + ); + + // Parse the image-pcrs.json using the ImagePcrs structure + let image_pcrs: ImagePcrs = serde_json::from_str(image_pcrs_json) + .expect("image-pcrs.json should be valid ImagePcrs JSON"); + + assert!( + !image_pcrs.0.is_empty(), + "image-pcrs.json should contain at least one image entry" + ); + + test_info!( + &self.test_name, + "Checking into {} image results:", + image_pcrs.0.len() + ); + let mut found_expected_pcrs = false; + + assert_eq!( + image_pcrs.0.len(), + expected_pcrs.len(), + "image-pcrs.json should contain {} image entries", + expected_pcrs.len() + ); + + for (i, (_image_ref, image_data)) in image_pcrs.0.iter().enumerate() { + if compare_pcrs(&image_data.pcrs, expected_pcrs[i]) { + found_expected_pcrs = true; + break; + } + } + + assert!( + found_expected_pcrs, + "At least one image should have the expected PCR values" + ); + + Ok(()) + } } #[macro_export] diff --git a/tests/trusted_execution_cluster.rs b/tests/trusted_execution_cluster.rs index 986ee64b..63d50d05 100644 --- a/tests/trusted_execution_cluster.rs +++ b/tests/trusted_execution_cluster.rs @@ -8,7 +8,6 @@ use k8s_openapi::api::apps::v1::Deployment; use k8s_openapi::api::core::v1::ConfigMap; use kube::{Api, api::DeleteParams}; use std::time::Duration; -use trusted_cluster_operator_lib::reference_values::ImagePcrs; use trusted_cluster_operator_lib::{ApprovedImage, TrustedExecutionCluster}; use trusted_cluster_operator_test_utils::*; @@ -45,102 +44,188 @@ named_test!( named_test! { async fn test_image_pcrs_configmap_updates() -> anyhow::Result<()> { let test_ctx = setup!().await?; - let client = test_ctx.client(); - let namespace = test_ctx.namespace(); - - let configmap_api: Api = Api::namespaced(client.clone(), namespace); - - let poller = Poller::new() - .with_timeout(Duration::from_secs(180)) - .with_interval(Duration::from_secs(5)) - .with_error_message("image-pcrs ConfigMap not populated with data".to_string()); - - poller - .poll_async(|| { - let api = configmap_api.clone(); - async move { - let cm = api.get("image-pcrs").await?; - - if let Some(data) = &cm.data - && let Some(image_pcrs_json) = data.get("image-pcrs.json") - && let Ok(image_pcrs) = serde_json::from_str::(image_pcrs_json) - && !image_pcrs.0.is_empty() - { - return Ok(()); - } - - Err(anyhow::anyhow!("image-pcrs ConfigMap not yet populated with image-pcrs.json data")) - } - }) - .await?; - - let image_pcrs_cm = configmap_api.get("image-pcrs").await?; - assert_eq!(image_pcrs_cm.metadata.name.as_deref(), Some("image-pcrs")); - - let data = image_pcrs_cm.data.as_ref() - .expect("image-pcrs ConfigMap should have data field"); - - assert!(!data.is_empty(), "image-pcrs ConfigMap should have data"); - - let image_pcrs_json = data.get("image-pcrs.json") - .expect("image-pcrs ConfigMap should have image-pcrs.json key"); - - assert!(!image_pcrs_json.is_empty(), "image-pcrs.json should not be empty"); - - // Parse the image-pcrs.json using the ImagePcrs structure - let image_pcrs: ImagePcrs = serde_json::from_str(image_pcrs_json) - .expect("image-pcrs.json should be valid ImagePcrs JSON"); - - assert!(!image_pcrs.0.is_empty(), "image-pcrs.json should contain at least one image entry"); - - let expected_pcrs = vec![ - Pcr { - id: 4, - value: hex::decode(EXPECTED_PCR4).unwrap(), - events: vec![ - TPMEvent { pcr: 4, name: "EV_EFI_ACTION".to_string(), hash: hex::decode("3d6772b4f84ed47595d72a2c4c5ffd15f5bb72c7507fe26f2aaee2c69d5633ba").unwrap(), id: TPMEventID::Pcr4EfiCall }, - TPMEvent { pcr: 4, name: "EV_SEPARATOR".to_string(), hash: hex::decode("df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119").unwrap(), id: TPMEventID::Pcr4Separator }, - TPMEvent { pcr: 4, name: "EV_EFI_BOOT_SERVICES_APPLICATION".to_string(), hash: hex::decode("94896c17d49fc8c8df0cc2836611586edab1615ce7cb58cf13fc5798de56b367").unwrap(), id: TPMEventID::Pcr4Shim }, - TPMEvent { pcr: 4, name: "EV_EFI_BOOT_SERVICES_APPLICATION".to_string(), hash: hex::decode("bc6844fc7b59b4f0c7da70a307fc578465411d7a2c34b0f4dc2cc154c873b644").unwrap(), id: TPMEventID::Pcr4Grub }, - TPMEvent { pcr: 4, name: "EV_EFI_BOOT_SERVICES_APPLICATION".to_string(), hash: hex::decode("2b1dc59bc61dbbc3db11a6f3b0708c948efd46cceb7f6c8ea2024b8d1b8c829a").unwrap(), id: TPMEventID::Pcr4Vmlinuz }, - ], - }, - Pcr { - id: 7, - value: hex::decode("b3a56a06c03a65277d0a787fcabc1e293eaa5d6dd79398f2dda741f7b874c65d").unwrap(), - events: vec![ - TPMEvent { pcr: 7, name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), hash: hex::decode("ccfc4bb32888a345bc8aeadaba552b627d99348c767681ab3141f5b01e40a40e").unwrap(), id: TPMEventID::Pcr7SecureBoot }, - TPMEvent { pcr: 7, name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), hash: hex::decode("adb6fc232943e39c374bf4782b6c697f43c39fca1f4b51dfceda21164e19a893").unwrap(), id: TPMEventID::Pcr7Pk }, - TPMEvent { pcr: 7, name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), hash: hex::decode("b5432fe20c624811cb0296391bfdf948ebd02f0705ab8229bea09774023f0ebf").unwrap(), id: TPMEventID::Pcr7Kek }, - TPMEvent { pcr: 7, name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), hash: hex::decode("4313e43de720194a0eabf4d6415d42b5a03a34fdc47bb1fc924cc4e665e6893d").unwrap(), id: TPMEventID::Pcr7Db }, - TPMEvent { pcr: 7, name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), hash: hex::decode("001004ba58a184f09be6c1f4ec75a246cc2eefa9637b48ee428b6aa9bce48c55").unwrap(), id: TPMEventID::Pcr7Dbx }, - TPMEvent { pcr: 7, name: "EV_SEPARATOR".to_string(), hash: hex::decode("df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119").unwrap(), id: TPMEventID::Pcr7Separator }, - TPMEvent { pcr: 7, name: "EV_EFI_VARIABLE_AUTHORITY".to_string(), hash: hex::decode("4d4a8e2c74133bbdc01a16eaf2dbb5d575afeb36f5d8dfcf609ae043909e2ee9").unwrap(), id: TPMEventID::Pcr7ShimCert }, - TPMEvent { pcr: 7, name: "EV_EFI_VARIABLE_AUTHORITY".to_string(), hash: hex::decode("e8e9578f5951ef16b1c1aa18ef02944b8375ec45ed4b5d8cdb30428db4a31016").unwrap(), id: TPMEventID::Pcr7SbatLevel }, - TPMEvent { pcr: 7, name: "EV_EFI_VARIABLE_AUTHORITY".to_string(), hash: hex::decode("ad5901fd581e6640c742c488083b9ac2c48255bd28a16c106c6f9df52702ee3f").unwrap(), id: TPMEventID::Pcr7GrubMokListCert }, - ], - }, - Pcr { - id: 14, - value: hex::decode("17cdefd9548f4383b67a37a901673bf3c8ded6f619d36c8007562de1d93c81cc").unwrap(), - events: vec![ - TPMEvent { pcr: 14, name: "EV_IPL".to_string(), hash: hex::decode("e8e48e3ad10bc243341b4663c0057aef0ec7894ccc9ecb0598f0830fa57f7220").unwrap(), id: TPMEventID::Pcr14MokList }, - TPMEvent { pcr: 14, name: "EV_IPL".to_string(), hash: hex::decode("8d8a3aae50d5d25838c95c034aadce7b548c9a952eb7925e366eda537c59c3b0").unwrap(), id: TPMEventID::Pcr14MokListX }, - TPMEvent { pcr: 14, name: "EV_IPL".to_string(), hash: hex::decode("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a").unwrap(), id: TPMEventID::Pcr14MokListTrusted }, - ], - }, - ]; - - let mut found_expected_pcrs = false; - for (_image_ref, image_data) in image_pcrs.0.iter() { - if compare_pcrs(&image_data.pcrs, &expected_pcrs) { - found_expected_pcrs = true; - break; - } - } - assert!(found_expected_pcrs, - "At least one image should have the expected PCR values"); + test_ctx.verify_expected_pcrs( + &[&[ + Pcr { + id: 4, + value: hex::decode(EXPECTED_PCR4).unwrap(), + events: vec![ + TPMEvent { + pcr: 4, + name: "EV_EFI_ACTION".to_string(), + hash: hex::decode( + "3d6772b4f84ed47595d72a2c4c5ffd15f5bb72c7507fe26f2aaee2c69d5633ba", + ) + .unwrap(), + id: TPMEventID::Pcr4EfiCall, + }, + TPMEvent { + pcr: 4, + name: "EV_SEPARATOR".to_string(), + hash: hex::decode( + "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119", + ) + .unwrap(), + id: TPMEventID::Pcr4Separator, + }, + TPMEvent { + pcr: 4, + name: "EV_EFI_BOOT_SERVICES_APPLICATION".to_string(), + hash: hex::decode( + "94896c17d49fc8c8df0cc2836611586edab1615ce7cb58cf13fc5798de56b367", + ) + .unwrap(), + id: TPMEventID::Pcr4Shim, + }, + TPMEvent { + pcr: 4, + name: "EV_EFI_BOOT_SERVICES_APPLICATION".to_string(), + hash: hex::decode( + "bc6844fc7b59b4f0c7da70a307fc578465411d7a2c34b0f4dc2cc154c873b644", + ) + .unwrap(), + id: TPMEventID::Pcr4Grub, + }, + TPMEvent { + pcr: 4, + name: "EV_EFI_BOOT_SERVICES_APPLICATION".to_string(), + hash: hex::decode( + "2b1dc59bc61dbbc3db11a6f3b0708c948efd46cceb7f6c8ea2024b8d1b8c829a", + ) + .unwrap(), + id: TPMEventID::Pcr4Vmlinuz, + }, + ], + }, + Pcr { + id: 7, + value: hex::decode( + "b3a56a06c03a65277d0a787fcabc1e293eaa5d6dd79398f2dda741f7b874c65d", + ) + .unwrap(), + events: vec![ + TPMEvent { + pcr: 7, + name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), + hash: hex::decode( + "ccfc4bb32888a345bc8aeadaba552b627d99348c767681ab3141f5b01e40a40e", + ) + .unwrap(), + id: TPMEventID::Pcr7SecureBoot, + }, + TPMEvent { + pcr: 7, + name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), + hash: hex::decode( + "adb6fc232943e39c374bf4782b6c697f43c39fca1f4b51dfceda21164e19a893", + ) + .unwrap(), + id: TPMEventID::Pcr7Pk, + }, + TPMEvent { + pcr: 7, + name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), + hash: hex::decode( + "b5432fe20c624811cb0296391bfdf948ebd02f0705ab8229bea09774023f0ebf", + ) + .unwrap(), + id: TPMEventID::Pcr7Kek, + }, + TPMEvent { + pcr: 7, + name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), + hash: hex::decode( + "4313e43de720194a0eabf4d6415d42b5a03a34fdc47bb1fc924cc4e665e6893d", + ) + .unwrap(), + id: TPMEventID::Pcr7Db, + }, + TPMEvent { + pcr: 7, + name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), + hash: hex::decode( + "001004ba58a184f09be6c1f4ec75a246cc2eefa9637b48ee428b6aa9bce48c55", + ) + .unwrap(), + id: TPMEventID::Pcr7Dbx, + }, + TPMEvent { + pcr: 7, + name: "EV_SEPARATOR".to_string(), + hash: hex::decode( + "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119", + ) + .unwrap(), + id: TPMEventID::Pcr7Separator, + }, + TPMEvent { + pcr: 7, + name: "EV_EFI_VARIABLE_AUTHORITY".to_string(), + hash: hex::decode( + "4d4a8e2c74133bbdc01a16eaf2dbb5d575afeb36f5d8dfcf609ae043909e2ee9", + ) + .unwrap(), + id: TPMEventID::Pcr7ShimCert, + }, + TPMEvent { + pcr: 7, + name: "EV_EFI_VARIABLE_AUTHORITY".to_string(), + hash: hex::decode( + "e8e9578f5951ef16b1c1aa18ef02944b8375ec45ed4b5d8cdb30428db4a31016", + ) + .unwrap(), + id: TPMEventID::Pcr7SbatLevel, + }, + TPMEvent { + pcr: 7, + name: "EV_EFI_VARIABLE_AUTHORITY".to_string(), + hash: hex::decode( + "ad5901fd581e6640c742c488083b9ac2c48255bd28a16c106c6f9df52702ee3f", + ) + .unwrap(), + id: TPMEventID::Pcr7GrubMokListCert, + }, + ], + }, + Pcr { + id: 14, + value: hex::decode( + "17cdefd9548f4383b67a37a901673bf3c8ded6f619d36c8007562de1d93c81cc", + ) + .unwrap(), + events: vec![ + TPMEvent { + pcr: 14, + name: "EV_IPL".to_string(), + hash: hex::decode( + "e8e48e3ad10bc243341b4663c0057aef0ec7894ccc9ecb0598f0830fa57f7220", + ) + .unwrap(), + id: TPMEventID::Pcr14MokList, + }, + TPMEvent { + pcr: 14, + name: "EV_IPL".to_string(), + hash: hex::decode( + "8d8a3aae50d5d25838c95c034aadce7b548c9a952eb7925e366eda537c59c3b0", + ) + .unwrap(), + id: TPMEventID::Pcr14MokListX, + }, + TPMEvent { + pcr: 14, + name: "EV_IPL".to_string(), + hash: hex::decode( + "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a", + ) + .unwrap(), + id: TPMEventID::Pcr14MokListTrusted, + }, + ], + }, + ]] + ).await?; test_ctx.cleanup().await?; From 4a580e903ae55ced84bfa3c903256a9d8e4651f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Be=C3=B1at=20Gartzia=20Arruabarrena?= Date: Thu, 15 Jan 2026 12:20:16 +0100 Subject: [PATCH 10/11] tests, trusted_execution_cluster: Refactor expected base PCR values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These could be used by other tests too. Make it a macro so it can be reused, and define a few more macros for standard TPMEvents and PCR values that are constant across integration tests at the moment, such as PCR7 and PCR14. Signed-off-by: Beñat Gartzia Arruabarrena --- tests/trusted_execution_cluster.rs | 381 +++++++++++++++-------------- 1 file changed, 200 insertions(+), 181 deletions(-) diff --git a/tests/trusted_execution_cluster.rs b/tests/trusted_execution_cluster.rs index 63d50d05..17aebbf4 100644 --- a/tests/trusted_execution_cluster.rs +++ b/tests/trusted_execution_cluster.rs @@ -12,6 +12,205 @@ use trusted_cluster_operator_lib::{ApprovedImage, TrustedExecutionCluster}; use trusted_cluster_operator_test_utils::*; const EXPECTED_PCR4: &str = "ff2b357be4a4bc66be796d4e7b2f1f27077dc89b96220aae60b443bcf4672525"; +const EXPECTED_PCR7: &str = "b3a56a06c03a65277d0a787fcabc1e293eaa5d6dd79398f2dda741f7b874c65d"; +const EXPECTED_PCR14: &str = "17cdefd9548f4383b67a37a901673bf3c8ded6f619d36c8007562de1d93c81cc"; +const TPMEVENT_PCR4EFICALL_HASH: &str = + "3d6772b4f84ed47595d72a2c4c5ffd15f5bb72c7507fe26f2aaee2c69d5633ba"; +const TPMEVENT_SEPARATOR_HASH: &str = + "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119"; + +macro_rules! pcr4_ev_efi_action_event { + () => {{ + TPMEvent { + pcr: 4, + name: "EV_EFI_ACTION".to_string(), + hash: hex::decode(TPMEVENT_PCR4EFICALL_HASH).unwrap(), + id: TPMEventID::Pcr4EfiCall, + } + }}; +} + +macro_rules! pcr_separator_event { + ($pcr:expr, $event_id:expr) => {{ + TPMEvent { + pcr: $pcr, + name: "EV_SEPARATOR".to_string(), + hash: hex::decode(TPMEVENT_SEPARATOR_HASH).unwrap(), + id: $event_id, + } + }}; +} + +macro_rules! expected_pcr7 { + () => {{ + Pcr { + id: 7, + value: hex::decode(EXPECTED_PCR7).unwrap(), + events: vec![ + TPMEvent { + pcr: 7, + name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), + hash: hex::decode( + "ccfc4bb32888a345bc8aeadaba552b627d99348c767681ab3141f5b01e40a40e", + ) + .unwrap(), + id: TPMEventID::Pcr7SecureBoot, + }, + TPMEvent { + pcr: 7, + name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), + hash: hex::decode( + "adb6fc232943e39c374bf4782b6c697f43c39fca1f4b51dfceda21164e19a893", + ) + .unwrap(), + id: TPMEventID::Pcr7Pk, + }, + TPMEvent { + pcr: 7, + name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), + hash: hex::decode( + "b5432fe20c624811cb0296391bfdf948ebd02f0705ab8229bea09774023f0ebf", + ) + .unwrap(), + id: TPMEventID::Pcr7Kek, + }, + TPMEvent { + pcr: 7, + name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), + hash: hex::decode( + "4313e43de720194a0eabf4d6415d42b5a03a34fdc47bb1fc924cc4e665e6893d", + ) + .unwrap(), + id: TPMEventID::Pcr7Db, + }, + TPMEvent { + pcr: 7, + name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), + hash: hex::decode( + "001004ba58a184f09be6c1f4ec75a246cc2eefa9637b48ee428b6aa9bce48c55", + ) + .unwrap(), + id: TPMEventID::Pcr7Dbx, + }, + pcr_separator_event!(7, TPMEventID::Pcr7Separator), + TPMEvent { + pcr: 7, + name: "EV_EFI_VARIABLE_AUTHORITY".to_string(), + hash: hex::decode( + "4d4a8e2c74133bbdc01a16eaf2dbb5d575afeb36f5d8dfcf609ae043909e2ee9", + ) + .unwrap(), + id: TPMEventID::Pcr7ShimCert, + }, + TPMEvent { + pcr: 7, + name: "EV_EFI_VARIABLE_AUTHORITY".to_string(), + hash: hex::decode( + "e8e9578f5951ef16b1c1aa18ef02944b8375ec45ed4b5d8cdb30428db4a31016", + ) + .unwrap(), + id: TPMEventID::Pcr7SbatLevel, + }, + TPMEvent { + pcr: 7, + name: "EV_EFI_VARIABLE_AUTHORITY".to_string(), + hash: hex::decode( + "ad5901fd581e6640c742c488083b9ac2c48255bd28a16c106c6f9df52702ee3f", + ) + .unwrap(), + id: TPMEventID::Pcr7GrubMokListCert, + }, + ], + } + }}; +} + +macro_rules! expected_pcr14 { + () => {{ + Pcr { + id: 14, + value: hex::decode("17cdefd9548f4383b67a37a901673bf3c8ded6f619d36c8007562de1d93c81cc") + .unwrap(), + events: vec![ + TPMEvent { + pcr: 14, + name: "EV_IPL".to_string(), + hash: hex::decode( + "e8e48e3ad10bc243341b4663c0057aef0ec7894ccc9ecb0598f0830fa57f7220", + ) + .unwrap(), + id: TPMEventID::Pcr14MokList, + }, + TPMEvent { + pcr: 14, + name: "EV_IPL".to_string(), + hash: hex::decode( + "8d8a3aae50d5d25838c95c034aadce7b548c9a952eb7925e366eda537c59c3b0", + ) + .unwrap(), + id: TPMEventID::Pcr14MokListX, + }, + TPMEvent { + pcr: 14, + name: "EV_IPL".to_string(), + hash: hex::decode( + "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a", + ) + .unwrap(), + id: TPMEventID::Pcr14MokListTrusted, + }, + ], + } + }}; +} + +macro_rules! pcr4_shim_event { + () => {{ + TPMEvent { + pcr: 4, + name: "EV_EFI_BOOT_SERVICES_APPLICATION".to_string(), + hash: hex::decode("94896c17d49fc8c8df0cc2836611586edab1615ce7cb58cf13fc5798de56b367") + .unwrap(), + id: TPMEventID::Pcr4Shim, + } + }}; +} + +macro_rules! expected_base_pcrs { + () => {{ + [ + Pcr { + id: 4, + value: hex::decode(EXPECTED_PCR4).unwrap(), + events: vec![ + pcr4_ev_efi_action_event!(), + pcr_separator_event!(4, TPMEventID::Pcr4Separator), + pcr4_shim_event!(), + TPMEvent { + pcr: 4, + name: "EV_EFI_BOOT_SERVICES_APPLICATION".to_string(), + hash: hex::decode( + "bc6844fc7b59b4f0c7da70a307fc578465411d7a2c34b0f4dc2cc154c873b644", + ) + .unwrap(), + id: TPMEventID::Pcr4Grub, + }, + TPMEvent { + pcr: 4, + name: "EV_EFI_BOOT_SERVICES_APPLICATION".to_string(), + hash: hex::decode( + "2b1dc59bc61dbbc3db11a6f3b0708c948efd46cceb7f6c8ea2024b8d1b8c829a", + ) + .unwrap(), + id: TPMEventID::Pcr4Vmlinuz, + }, + ], + }, + expected_pcr7!(), + expected_pcr14!(), + ] + }}; +} named_test!( async fn test_trusted_execution_cluster_uninstall() -> anyhow::Result<()> { @@ -45,187 +244,7 @@ named_test! { async fn test_image_pcrs_configmap_updates() -> anyhow::Result<()> { let test_ctx = setup!().await?; - test_ctx.verify_expected_pcrs( - &[&[ - Pcr { - id: 4, - value: hex::decode(EXPECTED_PCR4).unwrap(), - events: vec![ - TPMEvent { - pcr: 4, - name: "EV_EFI_ACTION".to_string(), - hash: hex::decode( - "3d6772b4f84ed47595d72a2c4c5ffd15f5bb72c7507fe26f2aaee2c69d5633ba", - ) - .unwrap(), - id: TPMEventID::Pcr4EfiCall, - }, - TPMEvent { - pcr: 4, - name: "EV_SEPARATOR".to_string(), - hash: hex::decode( - "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119", - ) - .unwrap(), - id: TPMEventID::Pcr4Separator, - }, - TPMEvent { - pcr: 4, - name: "EV_EFI_BOOT_SERVICES_APPLICATION".to_string(), - hash: hex::decode( - "94896c17d49fc8c8df0cc2836611586edab1615ce7cb58cf13fc5798de56b367", - ) - .unwrap(), - id: TPMEventID::Pcr4Shim, - }, - TPMEvent { - pcr: 4, - name: "EV_EFI_BOOT_SERVICES_APPLICATION".to_string(), - hash: hex::decode( - "bc6844fc7b59b4f0c7da70a307fc578465411d7a2c34b0f4dc2cc154c873b644", - ) - .unwrap(), - id: TPMEventID::Pcr4Grub, - }, - TPMEvent { - pcr: 4, - name: "EV_EFI_BOOT_SERVICES_APPLICATION".to_string(), - hash: hex::decode( - "2b1dc59bc61dbbc3db11a6f3b0708c948efd46cceb7f6c8ea2024b8d1b8c829a", - ) - .unwrap(), - id: TPMEventID::Pcr4Vmlinuz, - }, - ], - }, - Pcr { - id: 7, - value: hex::decode( - "b3a56a06c03a65277d0a787fcabc1e293eaa5d6dd79398f2dda741f7b874c65d", - ) - .unwrap(), - events: vec![ - TPMEvent { - pcr: 7, - name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), - hash: hex::decode( - "ccfc4bb32888a345bc8aeadaba552b627d99348c767681ab3141f5b01e40a40e", - ) - .unwrap(), - id: TPMEventID::Pcr7SecureBoot, - }, - TPMEvent { - pcr: 7, - name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), - hash: hex::decode( - "adb6fc232943e39c374bf4782b6c697f43c39fca1f4b51dfceda21164e19a893", - ) - .unwrap(), - id: TPMEventID::Pcr7Pk, - }, - TPMEvent { - pcr: 7, - name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), - hash: hex::decode( - "b5432fe20c624811cb0296391bfdf948ebd02f0705ab8229bea09774023f0ebf", - ) - .unwrap(), - id: TPMEventID::Pcr7Kek, - }, - TPMEvent { - pcr: 7, - name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), - hash: hex::decode( - "4313e43de720194a0eabf4d6415d42b5a03a34fdc47bb1fc924cc4e665e6893d", - ) - .unwrap(), - id: TPMEventID::Pcr7Db, - }, - TPMEvent { - pcr: 7, - name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), - hash: hex::decode( - "001004ba58a184f09be6c1f4ec75a246cc2eefa9637b48ee428b6aa9bce48c55", - ) - .unwrap(), - id: TPMEventID::Pcr7Dbx, - }, - TPMEvent { - pcr: 7, - name: "EV_SEPARATOR".to_string(), - hash: hex::decode( - "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119", - ) - .unwrap(), - id: TPMEventID::Pcr7Separator, - }, - TPMEvent { - pcr: 7, - name: "EV_EFI_VARIABLE_AUTHORITY".to_string(), - hash: hex::decode( - "4d4a8e2c74133bbdc01a16eaf2dbb5d575afeb36f5d8dfcf609ae043909e2ee9", - ) - .unwrap(), - id: TPMEventID::Pcr7ShimCert, - }, - TPMEvent { - pcr: 7, - name: "EV_EFI_VARIABLE_AUTHORITY".to_string(), - hash: hex::decode( - "e8e9578f5951ef16b1c1aa18ef02944b8375ec45ed4b5d8cdb30428db4a31016", - ) - .unwrap(), - id: TPMEventID::Pcr7SbatLevel, - }, - TPMEvent { - pcr: 7, - name: "EV_EFI_VARIABLE_AUTHORITY".to_string(), - hash: hex::decode( - "ad5901fd581e6640c742c488083b9ac2c48255bd28a16c106c6f9df52702ee3f", - ) - .unwrap(), - id: TPMEventID::Pcr7GrubMokListCert, - }, - ], - }, - Pcr { - id: 14, - value: hex::decode( - "17cdefd9548f4383b67a37a901673bf3c8ded6f619d36c8007562de1d93c81cc", - ) - .unwrap(), - events: vec![ - TPMEvent { - pcr: 14, - name: "EV_IPL".to_string(), - hash: hex::decode( - "e8e48e3ad10bc243341b4663c0057aef0ec7894ccc9ecb0598f0830fa57f7220", - ) - .unwrap(), - id: TPMEventID::Pcr14MokList, - }, - TPMEvent { - pcr: 14, - name: "EV_IPL".to_string(), - hash: hex::decode( - "8d8a3aae50d5d25838c95c034aadce7b548c9a952eb7925e366eda537c59c3b0", - ) - .unwrap(), - id: TPMEventID::Pcr14MokListX, - }, - TPMEvent { - pcr: 14, - name: "EV_IPL".to_string(), - hash: hex::decode( - "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a", - ) - .unwrap(), - id: TPMEventID::Pcr14MokListTrusted, - }, - ], - }, - ]] - ).await?; + test_ctx.verify_expected_pcrs(&[&expected_base_pcrs!()]).await?; test_ctx.cleanup().await?; From fb1321dce5092cbe5339b9df5d99ed30699c63f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Be=C3=B1at=20Gartzia=20Arruabarrena?= Date: Thu, 15 Jan 2026 18:16:46 +0100 Subject: [PATCH 11/11] tests, integration: PCR combination on bootloader+kernel update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add an integration test in which 2 approved images with different bootloader and kernel are added to the cluster. This emulates the situation in which a coreos image could be undergoing a bootloader and kernel update. The test checks that 2 images are added to the image pcr config map, and then checks that the reference values contain all possible pcr4 combinations. pcr7 and pcr14 are constant in this case, so there are not combinations possible (apart from the original value). Signed-off-by: Beñat Gartzia Arruabarrena --- tests/trusted_execution_cluster.rs | 71 ++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/tests/trusted_execution_cluster.rs b/tests/trusted_execution_cluster.rs index 17aebbf4..39c2e05c 100644 --- a/tests/trusted_execution_cluster.rs +++ b/tests/trusted_execution_cluster.rs @@ -283,3 +283,74 @@ async fn test_image_disallow() -> anyhow::Result<()> { Ok(()) } } + +named_test! { +async fn test_combined_image_pcrs_configmap_updates() -> anyhow::Result<()> { + let test_ctx = setup!([ + DEFAULT_TEST_FCOS_IMAGE, + "quay.io/trusted-execution-clusters/fedora-coreos@sha256:372a5db90a8695fafc2869d438bacd7f0ef7fd84f63746a450bfcd4b8b64ae83", + ]).await?; + let client = test_ctx.client(); + let namespace = test_ctx.namespace(); + + let secondary_expected_pcr4_hash = "37517a1f76c4d5cf615f4690921c732ad31359aac55f3aaf66d65a8ed38655a9"; + + test_ctx.verify_expected_pcrs( + &[&expected_base_pcrs!(), + // In practical terms it emulates a grub + kernel upgrade + &[ + Pcr { + id: 4, + value: hex::decode(secondary_expected_pcr4_hash).unwrap(), + events: vec![ + pcr4_ev_efi_action_event!(), + pcr_separator_event!(4, TPMEventID::Pcr4Separator), + pcr4_shim_event!(), + TPMEvent { pcr: 4, name: "EV_EFI_BOOT_SERVICES_APPLICATION".to_string(), hash: hex::decode("f45c2c974192366a5391e077c3cbf91e735e86eba2037fd86a1f1501818f73f4").unwrap(), id: TPMEventID::Pcr4Grub }, + TPMEvent { pcr: 4, name: "EV_EFI_BOOT_SERVICES_APPLICATION".to_string(), hash: hex::decode("f31e645e5e9ed131eea5dca0a18893a21e5625b4a56314fa39587ddc33a7fa91").unwrap(), id: TPMEventID::Pcr4Vmlinuz }, + ], + }, + expected_pcr7!(), + expected_pcr14!(), + ]] + ).await?; + + let expected_ref_values = [ + // PCR4 + EXPECTED_PCR4, + "0c4e52c0bc5d2fedbf83b2fee82664dbe5347a79cfb2cbcb9a37f64211add6e8", + "cc5a5360e64b25718be370ca2056645a9ba9e9bae33df08308d6b8e05b8ebb87", + secondary_expected_pcr4_hash, + // PCR7 + EXPECTED_PCR7, + // PCR14 + EXPECTED_PCR14, + ]; + + let configmap_api: Api = Api::namespaced(client.clone(), namespace); + let poller = Poller::new() + .with_timeout(Duration::from_secs(180)) + .with_interval(Duration::from_secs(5)) + .with_error_message("Reference value expectations not met".to_string()); + poller.poll_async(|| { + let api = configmap_api.clone(); + async move { + let cm = api.get("trustee-data").await?; + if let Some(data) = &cm.data + && let Some(reference_values_json) = data.get("reference-values.json") + { + for value in expected_ref_values { + if !reference_values_json.contains(value) { + return Err(anyhow::anyhow!("Reference value expectations not met")); + } + } + } + Ok(()) + } + }).await?; + + test_ctx.cleanup().await?; + + Ok(()) +} +}