From 4ff9f9b4119c7a67c8a576d2b8cf247a74c5100b 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 1/8] 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 50df09aa..c56faabe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,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 = { version = "0.11.9", default-features = false } http = "1.4.0" +hex = "0.4.3" ignition-config = "0.5.0" k8s-openapi = { version = "0.27.0", features = ["v1_33", "schemars"] } kube = { version = "3.0.1", default-features = false, features = ["derive", "runtime", "openssl-tls"] } diff --git a/operator/Cargo.toml b/operator/Cargo.toml index 97fcf308..917d405e 100644 --- a/operator/Cargo.toml +++ b/operator/Cargo.toml @@ -17,7 +17,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 1fb033bf5e0c617a5dfcaac94684582a2280ce51 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 2/8] 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 | 136 ++++++++++++++++++++++++++--- operator/src/test_utils.rs | 10 +-- operator/src/trustee.rs | 6 +- tests/Cargo.toml | 1 + tests/trusted_execution_cluster.rs | 49 ++++++----- 5 files changed, 157 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b90e8696..652011bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -461,16 +461,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#12a1d01e437a166b29331ad817375167d7bbfb5e" dependencies = [ "anyhow", "glob", "hex", "hex-literal", + "itertools 0.14.0", "lief", + "log", "openssl", "serde", + "serde_with", "sha2", + "strum", "uuid", ] @@ -637,6 +641,16 @@ dependencies = [ "darling_macro 0.20.11", ] +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", +] + [[package]] name = "darling" version = "0.23.0" @@ -661,6 +675,20 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.110", +] + [[package]] name = "darling_core" version = "0.23.0" @@ -685,6 +713,17 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", + "quote", + "syn 2.0.110", +] + [[package]] name = "darling_macro" version = "0.23.0" @@ -714,6 +753,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", + "serde_core", ] [[package]] @@ -940,7 +980,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -1707,6 +1747,8 @@ checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", "hashbrown 0.16.0", + "serde", + "serde_core", ] [[package]] @@ -1742,7 +1784,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -1775,6 +1817,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" @@ -1879,7 +1930,7 @@ checksum = "05a6d6f3611ad1d21732adbd7a2e921f598af6c92d71ae6e2620da4b67ee1f0d" dependencies = [ "base64 0.22.1", "jiff", - "schemars", + "schemars 1.1.0", "serde", "serde_json", ] @@ -1944,7 +1995,7 @@ dependencies = [ "jiff", "json-patch", "k8s-openapi", - "schemars", + "schemars 1.1.0", "serde", "serde-value", "serde_json", @@ -2015,9 +2066,9 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "lief" -version = "0.17.1" +version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18773f648622afc222330700726227739936a1fa0040b91f007c80605525c4ff" +checksum = "6446b5d7b31c03d9c99321515294004f1b27bbbd5265a6533321e1c4dfef2e75" dependencies = [ "bitflags 2.10.0", "cxx", @@ -2030,9 +2081,9 @@ dependencies = [ [[package]] name = "lief-build" -version = "0.17.1" +version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3abce5340e56c7f9191d699302b9496445b0438033b48a9937b41c506f677731" +checksum = "73e010d2fc798ca2b420e0c483163b77571e13843a96cc61933b30ef4db2cdb6" dependencies = [ "git-version", "miette", @@ -2043,9 +2094,9 @@ dependencies = [ [[package]] name = "lief-ffi" -version = "0.17.1" +version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fba2ccbe972b26212f69f7b938fc198214f4eaa7f548a8d724f1a04968137f96" +checksum = "4af183cc1edf58fdfffce46f28685b43efa57736b218e9d341a6e9c68df61ac9" dependencies = [ "autocxx", "cxx", @@ -3013,7 +3064,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -3077,6 +3128,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" @@ -3262,6 +3325,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" @@ -3441,6 +3535,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" @@ -3567,7 +3664,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -3638,10 +3735,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]] @@ -3650,6 +3749,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" @@ -3866,6 +3975,7 @@ dependencies = [ "anyhow", "cfg-if", "compute-pcrs-lib", + "hex", "k8s-openapi", "kube", "regex", diff --git a/operator/src/test_utils.rs b/operator/src/test_utils.rs index a9c0241d..0627cc8e 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 849a0cf6..87e7fa2d 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() @@ -555,7 +555,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] @@ -592,7 +592,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 6d78d3b3..5c0e190a 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -16,6 +16,7 @@ virtualization = [] anyhow.workspace = true cfg-if = "1.0.4" 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 4bdeeaf0..a2feaad2 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, Secret}; use kube::{Api, api::DeleteParams}; @@ -197,37 +198,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 240c2e162687034a4009e292fca6b9b4eb9009a8 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 3/8] 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 0627cc8e..88dc2de5 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, jiff::Timestamp}; 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: Timestamp::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 87e7fa2d..63253595 100644 --- a/operator/src/trustee.rs +++ b/operator/src/trustee.rs @@ -550,12 +550,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] @@ -589,10 +605,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 f0e7eabe357199e7c61ca1f4e2c2a13ebacf34c7 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 4/8] 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 | 169 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 163 insertions(+), 6 deletions(-) diff --git a/operator/src/trustee.rs b/operator/src/trustee.rs index 63253595..b65e21cd 100644 --- a/operator/src/trustee.rs +++ b/operator/src/trustee.rs @@ -8,6 +8,8 @@ use anyhow::{Context, Result}; use base64::{Engine as _, engine::general_purpose}; use chrono::{DateTime, Utc}; 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::endpoints::*; use trusted_cluster_operator_lib::reference_values::*; @@ -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() + chrono::Duration::days(365), - value: serde_json::Value::Array(values.to_vec()), + value: serde_json::Value::Array(values.iter().map(|v| JsonString(v.clone())).collect()), }) .collect() } @@ -546,7 +554,10 @@ 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 k8s_openapi::jiff::Timestamp; use kube::client::Body; use trusted_cluster_operator_test_utils::mock_client::*; @@ -869,4 +880,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: Timestamp::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: Timestamp::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 98f53f7a861b1a6af6147b0761b2af3f86302dbd 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 5/8] 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 | 47 +++++++++++++++------- tests/trusted_execution_cluster.rs | 2 +- 6 files changed, 74 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 652011bd..26958469 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3951,6 +3951,7 @@ dependencies = [ "compute-pcrs-lib", "env_logger", "fs_extra", + "glob", "http 1.4.0", "ignition-config", "k8s-openapi", diff --git a/Makefile b/Makefile index 472ffb17..f29157e4 100644 --- a/Makefile +++ b/Makefile @@ -159,7 +159,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 ac8addba..a1d14915 100644 --- a/test_utils/Cargo.toml +++ b/test_utils/Cargo.toml @@ -35,3 +35,4 @@ tower = "0.5.2" trusted-cluster-operator-lib = { path = "../lib" } 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 df8879d7..be80b604 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -5,6 +5,7 @@ use anyhow::{Result, anyhow}; use fs_extra::dir; +use glob::glob; use k8s_openapi::api::apps::v1::Deployment; use k8s_openapi::api::core::v1::{ConfigMap, Namespace}; use kube::api::DeleteParams; @@ -228,7 +229,7 @@ pub struct TestContext { } impl TestContext { - pub async fn new(test_name: &str) -> Result { + pub async fn new(test_name: &str, approved_images: &[&str]) -> Result { INIT.call_once(|| { let _ = env_logger::builder().is_test(true).try_init(); }); @@ -248,7 +249,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, @@ -417,7 +418,11 @@ impl TestContext { .await } - async fn generate_manifests(&self, workspace_root: &PathBuf) -> Result<(PathBuf, PathBuf)> { + async fn generate_manifests( + &self, + workspace_root: &PathBuf, + approved_images: &[&str], + ) -> Result<(PathBuf, PathBuf)> { let ns = self.test_namespace.clone(); let controller_gen_path = workspace_root.join("bin/controller-gen-v0.19.0"); @@ -476,6 +481,11 @@ impl TestContext { args.extend(&["-register-server-image", ®_srv_img]); args.extend(&["-attestation-key-register-image", &att_reg_img]); args.extend(&["-approved-image", &approved_image]); + args.extend( + approved_images + .iter() + .flat_map(|&i| vec!["-approved-image", i]), + ); let manifest_gen = Command::new(&trusted_cluster_gen_path).args(args).output(); let manifest_gen_output = manifest_gen.await?; if !manifest_gen_output.status.success() { @@ -485,11 +495,13 @@ impl TestContext { Ok((crd_temp_dir, rbac_temp_dir)) } - async fn apply_operator_manifests(&self) -> Result<()> { + async fn apply_operator_manifests(&self, approved_images: &[&str]) -> Result<()> { let manifests_dir = &self.manifests_dir; test_info!(&self.test_name, "Generating manifests in {manifests_dir}"); let workspace_root = env::current_dir()?.join(".."); - let (crd_temp_dir, rbac_temp_dir) = self.generate_manifests(&workspace_root).await?; + let (crd_temp_dir, rbac_temp_dir) = self + .generate_manifests(&workspace_root, approved_images) + .await?; test_info!(&self.test_name, "Manifests generated successfully"); let tec = "trustedexecutionclusters.trusted-execution-clusters.io"; @@ -620,13 +632,20 @@ impl TestContext { let cr_manifest_str = cr_manifest_path.to_str().unwrap(); 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().unwrap(); - 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().unwrap(); + kube_apply!( + approved_image_str, + &self.test_name, + "Applying ApprovedImage manifest" + ); + } let deployments_api: Api = Api::namespaced(self.client.clone(), &ns); @@ -700,7 +719,9 @@ macro_rules! virt_test { #[macro_export] macro_rules! setup { - () => {{ $crate::TestContext::new(TEST_NAME) }}; + () => {{ $crate::TestContext::new(TEST_NAME, &[]) }}; + + ($images:expr) => {{ $crate::TestContext::new(TEST_NAME, &$images) }}; } async fn setup_test_client() -> Result { diff --git a/tests/trusted_execution_cluster.rs b/tests/trusted_execution_cluster.rs index a2feaad2..69d84950 100644 --- a/tests/trusted_execution_cluster.rs +++ b/tests/trusted_execution_cluster.rs @@ -257,7 +257,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 a0966e642b320b61e2378aad94033fef889f6f9d 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 6/8] 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 be80b604..175c1e4c 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -13,6 +13,7 @@ use kube::{Api, Client}; use std::path::{Path, PathBuf}; use std::{collections::BTreeMap, env, sync::Once, time::Duration}; use tokio::process::Command; +use trusted_cluster_operator_lib::reference_values::ImagePcrs; use trusted_cluster_operator_lib::TrustedExecutionCluster; use trusted_cluster_operator_lib::endpoints::*; @@ -691,6 +692,95 @@ impl TestContext { 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 69d84950..38cdda48 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, Secret}; use kube::{Api, api::DeleteParams}; use std::time::Duration; -use trusted_cluster_operator_lib::reference_values::ImagePcrs; use trusted_cluster_operator_lib::{ ApprovedImage, AttestationKey, Machine, TrustedExecutionCluster, generate_owner_reference, }; @@ -147,102 +146,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 bea9fc50f0c290e9571e3fea13203ec93dcf72e2 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 7/8] 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 --- operator/src/trustee.rs | 100 ++----------- test_utils/src/constants.rs | 232 +++++++++++++++++++++++++++++ test_utils/src/lib.rs | 1 + tests/trusted_execution_cluster.rs | 186 +---------------------- 4 files changed, 246 insertions(+), 273 deletions(-) create mode 100644 test_utils/src/constants.rs diff --git a/operator/src/trustee.rs b/operator/src/trustee.rs index b65e21cd..0b32f3f6 100644 --- a/operator/src/trustee.rs +++ b/operator/src/trustee.rs @@ -560,6 +560,7 @@ mod tests { use k8s_openapi::jiff::Timestamp; use kube::client::Body; use trusted_cluster_operator_test_utils::mock_client::*; + use trusted_cluster_operator_test_utils::*; fn reference_values_from(reference_values: &[ReferenceValue], rv_name: &str) -> Vec { let rv = reference_values @@ -883,65 +884,13 @@ mod tests { #[test] fn test_recompute_reference_values_pcr4() { + let cos2_pcr4_hash = "c7fc63ec604348d8258993a9e344ba72041afd1473ad291a3171199b551aedbd"; let image_pcrs = ImagePcrs(BTreeMap::from([ ( "cos1".to_string(), ImagePcr { first_seen: Timestamp::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, - }, - ] - }], + pcrs: vec![expected_pcr4!(), expected_pcr7!()], reference: "".to_string(), }, ), @@ -951,22 +900,10 @@ mod tests { first_seen: Timestamp::now(), pcrs: vec![Pcr { id: 4, - value: hex::decode("19925299236966772216371371471692276818611442625320115173412649113251558526237189").unwrap(), + value: hex::decode(cos2_pcr4_hash).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, - }, + pcr4_ev_efi_action_event!(), + pcr_separator_event!(4, TPMEventID::Pcr4Separator), TPMEvent { name: "EV_EFI_BOOT_SERVICES_APPLICATION".into(), pcr: 4, @@ -990,19 +927,7 @@ mod tests { }, ], }, - 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, - }, - ] - }], + expected_pcr7!()], reference: "".to_string(), }, ), @@ -1014,16 +939,13 @@ mod tests { assert_eq!( vals_pcr4, vec![ - "47b742b3a2244cc7249ff3221ec640198044aac533a95abafded7921237508c1", - "551bbd142a716c67cd78336593c2eb3b547b575e810ced4501d761082b5cd4a8", - "c7fc63ec604348d8258993a9e344ba72041afd1473ad291a3171199b551aedbd", + "514259b499f88d74cce9ff4763bb95d5c4e9a6703df48467a99dbcae02c3d974", + cos2_pcr4_hash, "c9c3add791efc98f59977c89e673a34ad0b357872e9eb2c43d14607488e5d9e2", + expected_pcr4_hash!() ] ); let vals_pcr7 = reference_values_from(&result, "tpm_pcr7"); - assert_eq!( - vals_pcr7, - vec!["3f263b96ccbc33bb53d808771f9ab1e02d4dec8854f9530f749cde853a723273"] - ); + assert_eq!(vals_pcr7, vec![expected_pcr7_hash!()]); } } diff --git a/test_utils/src/constants.rs b/test_utils/src/constants.rs new file mode 100644 index 00000000..27fa2cf0 --- /dev/null +++ b/test_utils/src/constants.rs @@ -0,0 +1,232 @@ +// SPDX-FileCopyrightText: Beñat Gartzia Arruabarrena +// +// SPDX-License-Identifier: MIT + +#[macro_export] +macro_rules! expected_pcr4_hash { + () => {{ "ff2b357be4a4bc66be796d4e7b2f1f27077dc89b96220aae60b443bcf4672525" }}; +} + +#[macro_export] +macro_rules! expected_pcr7_hash { + () => {{ "b3a56a06c03a65277d0a787fcabc1e293eaa5d6dd79398f2dda741f7b874c65d" }}; +} + +#[macro_export] +macro_rules! expected_pcr14_hash { + () => {{ "17cdefd9548f4383b67a37a901673bf3c8ded6f619d36c8007562de1d93c81cc" }}; +} + +#[macro_export] +macro_rules! tpmevent_pcr4eficall_hash { + () => {{ "3d6772b4f84ed47595d72a2c4c5ffd15f5bb72c7507fe26f2aaee2c69d5633ba" }}; +} + +#[macro_export] +macro_rules! tpmevent_separator_hash { + () => {{ "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119" }}; +} + +#[macro_export] +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_export] +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_export] +macro_rules! expected_pcr7 { + () => {{ + Pcr { + id: 7, + value: hex::decode(expected_pcr7_hash!()).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_export] +macro_rules! expected_pcr14 { + () => {{ + Pcr { + id: 14, + value: hex::decode(expected_pcr14_hash!()).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_export] +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_export] +macro_rules! pcr4_grub_event { + () => {{ + TPMEvent { + pcr: 4, + name: "EV_EFI_BOOT_SERVICES_APPLICATION".to_string(), + hash: hex::decode("bc6844fc7b59b4f0c7da70a307fc578465411d7a2c34b0f4dc2cc154c873b644") + .unwrap(), + id: TPMEventID::Pcr4Grub, + } + }}; +} + +#[macro_export] +macro_rules! expected_pcr4 { + () => {{ + Pcr { + id: 4, + value: hex::decode(expected_pcr4_hash!()).unwrap(), + events: vec![ + pcr4_ev_efi_action_event!(), + pcr_separator_event!(4, TPMEventID::Pcr4Separator), + pcr4_shim_event!(), + pcr4_grub_event!(), + TPMEvent { + pcr: 4, + name: "EV_EFI_BOOT_SERVICES_APPLICATION".to_string(), + hash: hex::decode( + "72c613f1b4d60dcf51f82f3458cca246580d23150130ec6751ac6fa62c867364", + ) + .unwrap(), + id: TPMEventID::Pcr4Vmlinuz, + }, + ], + } + }}; +} + +#[macro_export] +macro_rules! expected_base_pcrs { + () => {{ [expected_pcr4!(), expected_pcr7!(), expected_pcr14!()] }}; +} diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index 175c1e4c..86f3d2b2 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -22,6 +22,7 @@ use trusted_cluster_operator_lib::routes::Route; pub mod timer; pub use timer::Poller; +pub mod constants; pub mod mock_client; #[cfg(feature = "virtualization")] diff --git a/tests/trusted_execution_cluster.rs b/tests/trusted_execution_cluster.rs index 38cdda48..036d3d8f 100644 --- a/tests/trusted_execution_cluster.rs +++ b/tests/trusted_execution_cluster.rs @@ -13,8 +13,6 @@ use trusted_cluster_operator_lib::{ }; use trusted_cluster_operator_test_utils::*; -const EXPECTED_PCR4: &str = "ff2b357be4a4bc66be796d4e7b2f1f27077dc89b96220aae60b443bcf4672525"; - named_test!( async fn test_trusted_execution_cluster_uninstall() -> anyhow::Result<()> { let test_ctx = setup!().await?; @@ -147,187 +145,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?; @@ -355,7 +173,7 @@ async fn test_image_disallow() -> anyhow::Result<()> { let cm = api.get("trustee-data").await?; if let Some(data) = &cm.data && let Some(reference_values_json) = data.get("reference-values.json") - && !reference_values_json.contains(EXPECTED_PCR4) + && !reference_values_json.contains(expected_pcr4_hash!()) { return Ok(()); } From 61f9b4e4ad1c3f9fd1c72b47343f8373e96f6406 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 8/8] 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 | 69 ++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/tests/trusted_execution_cluster.rs b/tests/trusted_execution_cluster.rs index 036d3d8f..be1216b4 100644 --- a/tests/trusted_execution_cluster.rs +++ b/tests/trusted_execution_cluster.rs @@ -342,3 +342,72 @@ async fn test_attestation_key_lifecycle() -> anyhow::Result<()> { Ok(()) } } + +named_test! { +async fn test_combined_image_pcrs_configmap_updates() -> anyhow::Result<()> { + let test_ctx = setup!([ + "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_hash!(), + "0c4e52c0bc5d2fedbf83b2fee82664dbe5347a79cfb2cbcb9a37f64211add6e8", + "cc5a5360e64b25718be370ca2056645a9ba9e9bae33df08308d6b8e05b8ebb87", + secondary_expected_pcr4_hash, + // PCR7 + expected_pcr7_hash!(), + // PCR14 + expected_pcr14_hash!(), + ]; + + 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(()) +} +}