From 5b048f9ac1ce7bd0d7c203d7a72cbb71272be1d4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Jul 2026 12:59:35 +0200 Subject: [PATCH 01/14] build(deps): bump the rust group with 3 updates Bumps the rust group with 3 updates: [uuid](https://github.com/uuid-rs/uuid), [toml](https://github.com/toml-rs/toml) and [regex](https://github.com/rust-lang/regex). Updates `uuid` from 1.23.4 to 1.23.5 - [Release notes](https://github.com/uuid-rs/uuid/releases) - [Commits](https://github.com/uuid-rs/uuid/compare/v1.23.4...v1.23.5) Updates `toml` from 1.1.2+spec-1.1.0 to 1.1.3+spec-1.1.0 - [Commits](https://github.com/toml-rs/toml/compare/toml-v1.1.2...toml-v1.1.3) Updates `regex` from 1.12.4 to 1.13.0 - [Release notes](https://github.com/rust-lang/regex/releases) - [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-lang/regex/compare/1.12.4...1.13.0) --- updated-dependencies: - dependency-name: uuid dependency-version: 1.23.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: rust - dependency-name: toml dependency-version: 1.1.3+spec-1.1.0 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: rust - dependency-name: regex dependency-version: 1.13.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: rust ... Signed-off-by: dependabot[bot] --- Cargo.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 606d4225..17dd1a25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3228,9 +3228,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.4" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +checksum = "2a0e75113e14dc5acb068cd0786884f214f1312650a3d36d269f5c4f3cdee8a2" dependencies = [ "aho-corasick", "memchr", @@ -4280,9 +4280,9 @@ dependencies = [ [[package]] name = "toml" -version = "1.1.2+spec-1.1.0" +version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +checksum = "53c96ecdfa941c8fc4fcaed14f99ada8ebed502eef533015095a07e3301d4c3c" dependencies = [ "indexmap 2.14.0", "serde_core", @@ -4313,9 +4313,9 @@ dependencies = [ [[package]] name = "toml_writer" -version = "1.1.1+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" [[package]] name = "tower" @@ -4584,9 +4584,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.4" +version = "1.23.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" +checksum = "ea5fab0d6c3c01ae70085a09cb03d4c7a1d6314e2b3e075392783396d724ca0a" dependencies = [ "getrandom 0.4.1", "js-sys", From 2ea319d88777f7c4f82a11e8c3ae11c1d3b566f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Be=C3=B1at=20Gartzia=20Arruabarrena?= Date: Wed, 15 Jul 2026 12:59:35 +0200 Subject: [PATCH 02/14] 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 02db0a13..cae6e2ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,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.10", default-features = false } http = "1.4.2" +hex = "0.4.3" ignition-config = "0.6.1" k8s-openapi = { version = "0.28.0", features = ["v1_35", "schemars"] } kube = { version = "4.0.0", default-features = false, features = ["derive", "runtime", "openssl-tls"] } diff --git a/operator/Cargo.toml b/operator/Cargo.toml index 1496094a..83e5451a 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.32" -hex = "0.4.3" +hex.workspace = true json-patch = "4.2.0" jsonptr = "0.7.1" k8s-openapi.workspace = true From ff9f4e3eddd38b1a9340d26d05ecbe1eb04e7256 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Be=C3=B1at=20Gartzia=20Arruabarrena?= Date: Wed, 15 Jul 2026 12:59:35 +0200 Subject: [PATCH 03/14] 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 | 128 ++++++++++++++++++++++++++--- operator/src/test_utils.rs | 10 +-- operator/src/trustee.rs | 6 +- tests/Cargo.toml | 1 + tests/trusted_execution_cluster.rs | 49 +++++------ 5 files changed, 152 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 17dd1a25..4d6b9007 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -577,16 +577,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 0.10.9", + "strum 0.27.2", "uuid", ] @@ -787,6 +791,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" @@ -811,6 +825,20 @@ dependencies = [ "syn 2.0.117", ] +[[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.117", +] + [[package]] name = "darling_core" version = "0.23.0" @@ -835,6 +863,17 @@ dependencies = [ "syn 2.0.117", ] +[[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.117", +] + [[package]] name = "darling_macro" version = "0.23.0" @@ -864,6 +903,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", + "serde_core", ] [[package]] @@ -2030,6 +2070,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" @@ -2146,7 +2195,7 @@ checksum = "d9c6922f6afe80418dd6019818af5d0d34584c371780ff09b9752370c25b4abb" dependencies = [ "base64 0.22.1", "jiff", - "schemars", + "schemars 1.1.0", "serde", "serde_json", ] @@ -2283,7 +2332,7 @@ dependencies = [ "http 1.4.2", "jiff", "k8s-openapi 0.27.1", - "schemars", + "schemars 1.1.0", "serde", "serde-value", "serde_json", @@ -2302,7 +2351,7 @@ dependencies = [ "jiff", "json-patch", "k8s-openapi 0.28.0", - "schemars", + "schemars 1.1.0", "serde", "serde-value", "serde_json", @@ -2393,9 +2442,9 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "lief" -version = "0.17.1" +version = "0.17.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18773f648622afc222330700726227739936a1fa0040b91f007c80605525c4ff" +checksum = "4a90e3c2a97875731396a088732fe6a3736c51462ff1d3a1d179f296d539a5f7" dependencies = [ "bitflags 2.10.0", "cxx", @@ -2408,9 +2457,9 @@ dependencies = [ [[package]] name = "lief-build" -version = "0.17.1" +version = "0.17.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3abce5340e56c7f9191d699302b9496445b0438033b48a9937b41c506f677731" +checksum = "d8093a7e3cb03708149414d595e3c0df57b79e5136fee630e3c415d06e6c876e" dependencies = [ "git-version", "miette", @@ -2421,9 +2470,9 @@ dependencies = [ [[package]] name = "lief-ffi" -version = "0.17.1" +version = "0.17.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fba2ccbe972b26212f69f7b938fc198214f4eaa7f548a8d724f1a04968137f96" +checksum = "60589ba353bc6e4db8cc4273afbfac2ab5e089583174275450618f4ed1cf51c4" dependencies = [ "autocxx", "cxx", @@ -3530,6 +3579,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" @@ -3761,6 +3822,37 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "381b283ce7bc6b476d903296fb59d0d36633652b633b27f64db4fb46dcbfc3b9" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.1.0", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6d4e30573c8cb306ed6ab1dca8423eec9a463ea0e155f45399455e0368b27e0" +dependencies = [ + "darling 0.21.3", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "serde_yaml" version = "0.9.34+deprecated" @@ -3940,6 +4032,9 @@ name = "strum" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros 0.27.2", +] [[package]] name = "strum" @@ -4158,10 +4253,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]] @@ -4170,6 +4267,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" @@ -4453,6 +4560,7 @@ dependencies = [ "anyhow", "cfg-if", "compute-pcrs-lib", + "hex", "k8s-openapi 0.28.0", "kube 4.0.0", "regex", diff --git a/operator/src/test_utils.rs b/operator/src/test_utils.rs index af4ed28c..41e5e92e 100644 --- a/operator/src/test_utils.rs +++ b/operator/src/test_utils.rs @@ -17,16 +17,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 d8d63d16..b90f3137 100644 --- a/operator/src/trustee.rs +++ b/operator/src/trustee.rs @@ -79,7 +79,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() @@ -599,7 +599,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] @@ -636,7 +636,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 01d434c0..64dd9087 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 fb36dcef..2d1d47b4 100644 --- a/tests/trusted_execution_cluster.rs +++ b/tests/trusted_execution_cluster.rs @@ -4,7 +4,8 @@ // SPDX-License-Identifier: MIT use anyhow::Context; -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 k8s_openapi::apimachinery::pkg::apis::meta::v1::{Condition, OwnerReference}; @@ -163,37 +164,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 d9569fddcca58ed2569214b4e193a924ab761632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Be=C3=B1at=20Gartzia=20Arruabarrena?= Date: Wed, 15 Jul 2026 12:59:35 +0200 Subject: [PATCH 04/14] 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 41e5e92e..f21fecb7 100644 --- a/operator/src/test_utils.rs +++ b/operator/src/test_utils.rs @@ -3,12 +3,18 @@ // 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 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(), @@ -16,14 +22,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 b90f3137..696b50f1 100644 --- a/operator/src/trustee.rs +++ b/operator/src/trustee.rs @@ -594,12 +594,28 @@ mod tests { use trusted_cluster_operator_test_utils::mock_client::*; use trusted_cluster_operator_test_utils::test_error_method; + 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), + DUMMY_PCR_4_VALUE + ); + assert_eq!( + hex::encode(&image_pcrs.0["cos"].pcrs[1].value), + "e58ada1ba75f2e4722b539824598ad5e10c55f2e4aeab2033f3b0a8ee3f3eca6" + ); } #[test] @@ -633,10 +649,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 4c8d360bb66ce339abeebdac64cde547772d5b32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Be=C3=B1at=20Gartzia=20Arruabarrena?= Date: Wed, 15 Jul 2026 12:59:35 +0200 Subject: [PATCH 05/14] 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 696b50f1..84566503 100644 --- a/operator/src/trustee.rs +++ b/operator/src/trustee.rs @@ -9,6 +9,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, @@ -28,7 +30,7 @@ use log::info; use operator::{TLS_DIR, create_or_info_if_exists, read_certificate}; 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::*; @@ -72,14 +74,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)); } reference_values_in .iter() @@ -87,7 +95,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() } @@ -589,7 +597,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::*; use trusted_cluster_operator_test_utils::test_error_method; @@ -909,4 +920,150 @@ mod tests { let clos = |client| generate_kbs_deployment(client, Default::default(), "image", &None); test_error_method!(clos, Method::POST); } + + #[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 b86717829832eee172621c31503e3b70aebfd485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Be=C3=B1at=20Gartzia=20Arruabarrena?= Date: Wed, 15 Jul 2026 12:59:36 +0200 Subject: [PATCH 06/14] 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. As we now deal with multiple images, and "coreos" is a bit to vague in that case, approved-images now accept both the image reference and a name, so we can identify them easily. Instead of calling "coreos" to the existing one, we now call it "coreos-approved-primary". Signed-off-by: Beñat Gartzia Arruabarrena --- Makefile | 2 +- api/trusted-cluster-gen.go | 88 +++++++++++++++++++++++++++----------- test_utils/src/lib.rs | 59 ++++++++++++++++++------- 3 files changed, 106 insertions(+), 43 deletions(-) diff --git a/Makefile b/Makefile index e29e750b..314824d8 100644 --- a/Makefile +++ b/Makefile @@ -170,7 +170,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 5c9eba97..914d3885 100644 --- a/api/trusted-cluster-gen.go +++ b/api/trusted-cluster-gen.go @@ -21,6 +21,43 @@ 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 approvedImage struct { + Name string + Reference string +} + +type approvedImageSlice []approvedImage + +func (i *approvedImageSlice) String() string { + var res []string + for _, image := range *i { + res = append(res, fmt.Sprintf("%s,%s", image.Name, image.Reference)) + } + + return strings.Join(res, " ") +} + +func (i *approvedImageSlice) Set(value string) error { + split := strings.SplitN(value, ",", 2) + if len(split) != 2 { + return fmt.Errorf("approved images are expected to be comma-separated ',' pairs") + } + + *i = append(*i, approvedImage{Name: split[0], Reference: split[1]}) + return nil +} + type Args struct { outputDir string image string @@ -29,7 +66,7 @@ type Args struct { pcrsComputeImage string registerServerImage string attestationKeyRegisterImage string - approvedImage string + approvedImages approvedImageSlice } func main() { @@ -41,7 +78,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. It must be a comma-separated name,image-ref pair. Must be a bootable container image with SHA reference. Can be set multiple times.") flag.Parse() log.SetFlags(log.LstdFlags) @@ -184,34 +221,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: approvedImage.Name, + Namespace: args.namespace, + }, + Spec: v1alpha1.ApprovedImageSpec{ + Reference: approvedImage.Reference, + }, + } - 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/src/lib.rs b/test_utils/src/lib.rs index a9899fd2..b586a558 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -5,6 +5,7 @@ use anyhow::{Context, Result, anyhow}; use fs_extra::dir; +use glob::glob; use k8s_openapi::api::apps::v1::{Deployment, DeploymentCondition, DeploymentStatus}; use k8s_openapi::api::core::v1::{ ConfigMap, LoadBalancerStatus, Namespace, Secret, Service, ServicePort, ServiceSpec, @@ -57,7 +58,7 @@ const ATT_REG_SECRET: &str = "att-reg-secret"; const REG_CERT: &str = "reg-srv-cert"; const TRUSTEE_CERT: &str = "trustee-cert"; const ATT_REG_CERT: &str = "att-reg-cert"; -pub const APPROVED_IMAGE_NAME: &str = "coreos"; +pub const APPROVED_IMAGE_NAME: &str = "coreos-approved-primary"; pub fn compare_pcrs(actual: &[Pcr], expected: &[Pcr]) -> bool { if actual.len() != expected.len() { @@ -461,7 +462,11 @@ pub struct TestContext { } impl TestContext { - pub async fn new(test_name: &str, delayed_approved_image: bool) -> Result { + pub async fn new( + test_name: &str, + delayed_approved_image: bool, + approved_images: &[(&str, &str)], + ) -> Result { INIT.call_once(|| { let _ = env_logger::builder().is_test(true).try_init(); }); @@ -482,7 +487,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, @@ -731,7 +736,11 @@ impl TestContext { Ok(()) } - async fn generate_manifests(&self, workspace_root: &PathBuf) -> Result<(PathBuf, PathBuf)> { + async fn generate_manifests( + &self, + workspace_root: &PathBuf, + approved_images: &[(&str, &str)], + ) -> Result<(PathBuf, PathBuf)> { let ns = self.test_namespace.clone(); let controller_gen_pattern = workspace_root.join("bin/controller-gen-*"); let pattern = controller_gen_pattern.to_str().unwrap(); @@ -796,7 +805,15 @@ impl TestContext { args.extend(&["-trustee-image", &trustee_image]); args.extend(&["-register-server-image", ®_srv_img]); args.extend(&["-attestation-key-register-image", &att_reg_img]); - args.extend(&["-approved-image", &approved_image]); + let primary_approved_arg = format!("{},{approved_image}", APPROVED_IMAGE_NAME); + args.extend(&["-approved-image", &primary_approved_arg]); + let approved_args: Vec = approved_images + .iter() + .map(|&(n, r)| format!("{n},{r}")) + .collect(); + for arg in &approved_args { + args.extend(&["-approved-image", arg]); + } 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() { @@ -806,11 +823,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, &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"); self.set_certificates().await?; @@ -955,13 +974,20 @@ impl TestContext { if self.delayed_approved_image { tokio::time::sleep(Duration::from_secs(5)).await; } - 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 depl_ready = |depl: Option<&Deployment>| { let chk_cond = |c: &DeploymentCondition| c.type_ == "Available" && c.status == "True"; @@ -1069,8 +1095,9 @@ macro_rules! virt_test { #[macro_export] macro_rules! setup { - () => {{ $crate::TestContext::new(TEST_NAME, false) }}; - (delayed_approved_image) => {{ $crate::TestContext::new(TEST_NAME, true) }}; + () => {{ $crate::TestContext::new(TEST_NAME, false, &[]) }}; + (delayed_approved_image) => {{ $crate::TestContext::new(TEST_NAME, true, &[]) }}; + ($images:expr) => {{ $crate::TestContext::new(TEST_NAME, false, &$images) }}; } async fn setup_test_client() -> Result { From f27465a9fb15fe5b1462c92038b4f93450c3f48e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Be=C3=B1at=20Gartzia=20Arruabarrena?= Date: Wed, 15 Jul 2026 12:59:36 +0200 Subject: [PATCH 07/14] 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 | 73 ++++++++ tests/trusted_execution_cluster.rs | 262 ++++++++++++++++++++--------- 2 files changed, 254 insertions(+), 81 deletions(-) diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index b586a558..e65bb594 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -25,6 +25,7 @@ use trusted_cluster_operator_lib::certificates::{ }; use trusted_cluster_operator_lib::conditions::COMMITTED_CONDITION; use trusted_cluster_operator_lib::issuers::{Issuer, IssuerCa, IssuerSpec}; +use trusted_cluster_operator_lib::reference_values::ImagePcrs; use trusted_cluster_operator_lib::{ApprovedImage, ApprovedImageStatus, AttestationKey, Machine}; use trusted_cluster_operator_lib::{TrustedExecutionCluster, endpoints::*, images::*}; @@ -1067,6 +1068,78 @@ impl TestContext { timeout(scaled_duration(300), done).await.context(ctx)??; 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 populated = |cm: Option<&ConfigMap>| { + let data = cm.and_then(|cm| cm.data.as_ref()); + let json = data.and_then(|data| data.get("image-pcrs.json")); + let pcrs = json.and_then(|json| serde_json::from_str::(json).ok()); + pcrs.map(|pcrs| !pcrs.0.is_empty()).unwrap_or(false) + }; + let done = await_condition(configmap_api.clone(), "image-pcrs", populated); + let ctx = "waiting for ConfigMap image-pcrs to be populated"; + timeout(scaled_duration(180), done).await.context(ctx)??; + + 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 2d1d47b4..1275320a 100644 --- a/tests/trusted_execution_cluster.rs +++ b/tests/trusted_execution_cluster.rs @@ -16,7 +16,6 @@ use std::time::Duration; use tokio::time::timeout; use trusted_cluster_operator_lib::conditions::NOT_COMMITTED_REASON_PENDING; use trusted_cluster_operator_lib::endpoints::{REGISTER_SERVER_DEPLOYMENT, TRUSTEE_DEPLOYMENT}; -use trusted_cluster_operator_lib::reference_values::ImagePcrs; use trusted_cluster_operator_lib::{ ApprovedImage, AttestationKey, Machine, TrustedExecutionCluster, generate_owner_reference, }; @@ -128,87 +127,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 populated = |cm: Option<&ConfigMap>| { - let data = cm.and_then(|cm| cm.data.as_ref()); - let json = data.and_then(|data| data.get("image-pcrs.json")); - let pcrs = json.and_then(|json| serde_json::from_str::(json).ok()); - pcrs.map(|pcrs| !pcrs.0.is_empty()).unwrap_or(false) - }; - let done = await_condition(configmap_api.clone(), "image-pcrs", populated); - let ctx = "waiting for ConfigMap image-pcrs to be populated"; - timeout(scaled_duration(180), done).await.context(ctx)??; - - 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 e5127f020367dd47ae3b6d605305294c766dac29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Be=C3=B1at=20Gartzia=20Arruabarrena?= Date: Wed, 15 Jul 2026 12:59:36 +0200 Subject: [PATCH 08/14] compute-pcrs: Don't compute PCR7 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The compute pcrs job was computing pcr7 based on the guest OS information. However, PCR7 is mainly affected by the EFI variables, which are configured by the platform, or virtualization host. As we lack a proper way to retrieve the EFI vars of the platform that nodes are running on, we don't attest pcr7, so we should just not compute it. This commit removes the bits that computed pcr7 in the compute-pcrs job and those that checked it during integration tests. It leaves the test logic covering pcr7 in operator/src/trustee.rs as it should be agnostic to whatever pcr prediction comes in. Signed-off-by: Beñat Gartzia Arruabarrena --- compute-pcrs/src/main.rs | 3 +- tests/trusted_execution_cluster.rs | 90 ------------------------------ 2 files changed, 1 insertion(+), 92 deletions(-) diff --git a/compute-pcrs/src/main.rs b/compute-pcrs/src/main.rs index 0c1bfacf..0241f37f 100644 --- a/compute-pcrs/src/main.rs +++ b/compute-pcrs/src/main.rs @@ -43,12 +43,11 @@ async fn main() -> Result<()> { let os_id = get_val("ID")?; let os_version_id = get_val("VERSION_ID")?; - let efivars = format!("/reference-values/efivars/qemu-ovmf/{os_id}-{os_version_id}"); let mokvars = format!("/reference-values/mok-variables/{os_id}-{os_version_id}"); let pcrs = vec![ compute_pcr4(&kernels, &esp, false, true), - compute_pcr7(Some(&efivars), &esp, true), + // TODO: compute pcr7 when platform EFI vars can be elucidated compute_pcr14(&mokvars), ]; diff --git a/tests/trusted_execution_cluster.rs b/tests/trusted_execution_cluster.rs index 1275320a..61ad53d9 100644 --- a/tests/trusted_execution_cluster.rs +++ b/tests/trusted_execution_cluster.rs @@ -181,96 +181,6 @@ async fn test_image_pcrs_configmap_updates() -> anyhow::Result<()> { }, ], }, - 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( From 1580844914d33a7ee6ffbd584c5f157b334aba69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Be=C3=B1at=20Gartzia=20Arruabarrena?= Date: Wed, 15 Jul 2026 12:59:36 +0200 Subject: [PATCH 09/14] 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 | 101 ++---------- test_utils/src/constants.rs | 252 +++++++++++++++++++++++++++++ test_utils/src/lib.rs | 16 +- tests/trusted_execution_cluster.rs | 103 +----------- 4 files changed, 279 insertions(+), 193 deletions(-) create mode 100644 test_utils/src/constants.rs diff --git a/operator/src/trustee.rs b/operator/src/trustee.rs index 84566503..1000af65 100644 --- a/operator/src/trustee.rs +++ b/operator/src/trustee.rs @@ -602,8 +602,10 @@ mod tests { use http::{Method, Request, StatusCode}; use k8s_openapi::jiff::Timestamp; use kube::client::Body; + use trusted_cluster_operator_test_utils::constants::*; use trusted_cluster_operator_test_utils::mock_client::*; use trusted_cluster_operator_test_utils::test_error_method; + use trusted_cluster_operator_test_utils::*; fn reference_values_from(reference_values: &[ReferenceValue], rv_name: &str) -> Vec { let rv = reference_values @@ -923,65 +925,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![primary_pcr4!(), expected_pcr7!()], reference: "".to_string(), }, ), @@ -991,22 +941,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, @@ -1030,19 +968,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(), }, ), @@ -1054,16 +980,13 @@ mod tests { assert_eq!( vals_pcr4, vec![ - "47b742b3a2244cc7249ff3221ec640198044aac533a95abafded7921237508c1", - "551bbd142a716c67cd78336593c2eb3b547b575e810ced4501d761082b5cd4a8", - "c7fc63ec604348d8258993a9e344ba72041afd1473ad291a3171199b551aedbd", + "514259b499f88d74cce9ff4763bb95d5c4e9a6703df48467a99dbcae02c3d974", + cos2_pcr4_hash, "c9c3add791efc98f59977c89e673a34ad0b357872e9eb2c43d14607488e5d9e2", + PRIMARY_PCR4_HASH ] ); let vals_pcr7 = reference_values_from(&result, "tpm_pcr7"); - assert_eq!( - vals_pcr7, - vec!["3f263b96ccbc33bb53d808771f9ab1e02d4dec8854f9530f749cde853a723273"] - ); + assert_eq!(vals_pcr7, vec![PCR7_HASH]); } } diff --git a/test_utils/src/constants.rs b/test_utils/src/constants.rs new file mode 100644 index 00000000..1a37acdc --- /dev/null +++ b/test_utils/src/constants.rs @@ -0,0 +1,252 @@ +// SPDX-FileCopyrightText: Beñat Gartzia Arruabarrena +// +// SPDX-License-Identifier: MIT + +pub const TEC_NAME: &str = "trusted-execution-cluster"; +pub const TRUSTEE_CONFIG_MAP: &str = "trustee-data"; +pub const RV_JSON_KEY: &str = "reference-values.json"; + +pub const APPROVED_IMAGE_NAME: &str = "coreos-approved-primary"; + +pub const PRIMARY_PCR4_HASH: &str = + "ff2b357be4a4bc66be796d4e7b2f1f27077dc89b96220aae60b443bcf4672525"; +// PCR7 is not used for attestation yet. This constant is only consumed by +// unit tests. +pub const PCR7_HASH: &str = "b3a56a06c03a65277d0a787fcabc1e293eaa5d6dd79398f2dda741f7b874c65d"; +pub const PCR14_HASH: &str = "17cdefd9548f4383b67a37a901673bf3c8ded6f619d36c8007562de1d93c81cc"; + +pub const SEPARATOR_HASH: &str = "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119"; +pub const EFICALL_HASH: &str = "3d6772b4f84ed47595d72a2c4c5ffd15f5bb72c7507fe26f2aaee2c69d5633ba"; +pub const PRIMARY_SHIM_HASH: &str = + "94896c17d49fc8c8df0cc2836611586edab1615ce7cb58cf13fc5798de56b367"; +pub const PRIMARY_GRUB_HASH: &str = + "bc6844fc7b59b4f0c7da70a307fc578465411d7a2c34b0f4dc2cc154c873b644"; +pub const PRIMARY_KERNEL_HASH: &str = + "72c613f1b4d60dcf51f82f3458cca246580d23150130ec6751ac6fa62c867364"; + +#[macro_export] +macro_rules! pcr4_ev_efi_action_event { + () => {{ + TPMEvent { + pcr: 4, + name: "EV_EFI_ACTION".to_string(), + hash: hex::decode(EFICALL_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(SEPARATOR_HASH).unwrap(), + id: $event_id, + } + }}; +} + +#[macro_export] +macro_rules! expected_pcr7 { + // PCR7 is not used for attestation yet. This constant is only consumed by + // unit tests. + () => {{ + Pcr { + id: 7, + value: hex::decode(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(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 { + ($hash:expr) => {{ + TPMEvent { + pcr: 4, + name: "EV_EFI_BOOT_SERVICES_APPLICATION".to_string(), + hash: hex::decode($hash).unwrap(), + id: TPMEventID::Pcr4Shim, + } + }}; +} + +#[macro_export] +macro_rules! pcr4_grub_event { + ($hash:expr) => {{ + TPMEvent { + pcr: 4, + name: "EV_EFI_BOOT_SERVICES_APPLICATION".to_string(), + hash: hex::decode($hash).unwrap(), + id: TPMEventID::Pcr4Grub, + } + }}; +} + +#[macro_export] +macro_rules! pcr4_kernel_event { + ($hash:expr) => {{ + TPMEvent { + pcr: 4, + name: "EV_EFI_BOOT_SERVICES_APPLICATION".to_string(), + hash: hex::decode($hash).unwrap(), + id: TPMEventID::Pcr4Vmlinuz, + } + }}; +} + +#[macro_export] +macro_rules! pcr4 { + ($value:expr, $shim:expr, $grub:expr, $kernel:expr) => {{ + Pcr { + id: 4, + value: hex::decode($value).unwrap(), + events: vec![ + pcr4_ev_efi_action_event!(), + pcr_separator_event!(4, TPMEventID::Pcr4Separator), + pcr4_shim_event!($shim), + pcr4_grub_event!($grub), + pcr4_kernel_event!($kernel), + ], + } + }}; +} + +#[macro_export] +macro_rules! primary_pcr4 { + () => {{ + pcr4!( + PRIMARY_PCR4_HASH, + PRIMARY_SHIM_HASH, + PRIMARY_GRUB_HASH, + PRIMARY_KERNEL_HASH + ) + }}; +} + +// TODO: This should also contain expected pcr7, as soon as it can be +// predicted and used for attestation +#[macro_export] +macro_rules! pcrs { + ($pcr4:expr) => {{ [$pcr4, expected_pcr14!()] }}; +} + +#[macro_export] +macro_rules! primary_pcrs { + () => {{ pcrs!(primary_pcr4!()) }}; +} diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index e65bb594..0648788a 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -32,6 +32,7 @@ use trusted_cluster_operator_lib::{TrustedExecutionCluster, endpoints::*, images pub mod timer; pub use timer::Poller; +pub mod constants; pub mod mock_client; #[cfg(feature = "virtualization")] @@ -59,7 +60,6 @@ const ATT_REG_SECRET: &str = "att-reg-secret"; const REG_CERT: &str = "reg-srv-cert"; const TRUSTEE_CERT: &str = "trustee-cert"; const ATT_REG_CERT: &str = "att-reg-cert"; -pub const APPROVED_IMAGE_NAME: &str = "coreos-approved-primary"; pub fn compare_pcrs(actual: &[Pcr], expected: &[Pcr]) -> bool { if actual.len() != expected.len() { @@ -806,7 +806,7 @@ impl TestContext { args.extend(&["-trustee-image", &trustee_image]); args.extend(&["-register-server-image", ®_srv_img]); args.extend(&["-attestation-key-register-image", &att_reg_img]); - let primary_approved_arg = format!("{},{approved_image}", APPROVED_IMAGE_NAME); + let primary_approved_arg = format!("{},{approved_image}", constants::APPROVED_IMAGE_NAME); args.extend(&["-approved-image", &primary_approved_arg]); let approved_args: Vec = approved_images .iter() @@ -1053,7 +1053,10 @@ impl TestContext { let configmap_api: Api = Api::namespaced(self.client.clone(), ns); wait_for_resource_created(&configmap_api, "image-pcrs", scaled_timeout(60)).await?; - let info = format!("Waiting for ApprovedImage {APPROVED_IMAGE_NAME} to be Committed"); + let info = format!( + "Waiting for ApprovedImage {} to be Committed", + constants::APPROVED_IMAGE_NAME + ); test_info!(&self.test_name, "{info}"); let images: Api = Api::namespaced(self.client.clone(), ns); let image_ready = |img: Option<&ApprovedImage>| { @@ -1063,8 +1066,11 @@ impl TestContext { let chk = |img: &ApprovedImage| img.status.as_ref().and_then(chk_status); img.and_then(chk).unwrap_or(false) }; - let done = await_condition(images.clone(), APPROVED_IMAGE_NAME, image_ready); - let ctx = format!("waiting for ApprovedImage {APPROVED_IMAGE_NAME} to be Committed"); + let done = await_condition(images.clone(), constants::APPROVED_IMAGE_NAME, image_ready); + let ctx = format!( + "waiting for ApprovedImage {} to be Committed", + constants::APPROVED_IMAGE_NAME + ); timeout(scaled_duration(300), done).await.context(ctx)??; Ok(()) } diff --git a/tests/trusted_execution_cluster.rs b/tests/trusted_execution_cluster.rs index 61ad53d9..d915f49e 100644 --- a/tests/trusted_execution_cluster.rs +++ b/tests/trusted_execution_cluster.rs @@ -19,13 +19,9 @@ use trusted_cluster_operator_lib::endpoints::{REGISTER_SERVER_DEPLOYMENT, TRUSTE use trusted_cluster_operator_lib::{ ApprovedImage, AttestationKey, Machine, TrustedExecutionCluster, generate_owner_reference, }; +use trusted_cluster_operator_test_utils::constants::*; use trusted_cluster_operator_test_utils::*; -const EXPECTED_PCR4: &str = "ff2b357be4a4bc66be796d4e7b2f1f27077dc89b96220aae60b443bcf4672525"; -const TEC_NAME: &str = "trusted-execution-cluster"; -const TRUSTEE_CONFIG_MAP: &str = "trustee-data"; -const RV_JSON_KEY: &str = "reference-values.json"; - fn ak_approved(ak: Option<&AttestationKey>) -> bool { let is_approved = |c: &Condition| c.type_ == "Approved" && c.status == "True"; let cs = ak.and_then(|ak| ak.status.as_ref().and_then(|s| s.conditions.as_ref())); @@ -128,98 +124,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: 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(&[&primary_pcrs!()]).await?; test_ctx.cleanup().await?; Ok(()) @@ -239,7 +144,7 @@ async fn test_image_disallow() -> anyhow::Result<()> { let chk_removed = |cm: Option<&ConfigMap>| { let data = cm.and_then(|cm| cm.data.as_ref()); let json = data.and_then(|data| data.get(RV_JSON_KEY)); - json.map(|json| !json.contains(EXPECTED_PCR4)).unwrap_or(false) + json.map(|json| !json.contains(PRIMARY_PCR4_HASH)).unwrap_or(false) }; let rv_removed = await_condition(configmap_api, TRUSTEE_CONFIG_MAP, chk_removed); let ctx = format!("waiting for ConfigMap {TRUSTEE_CONFIG_MAP} to not contain PCR value"); @@ -436,7 +341,7 @@ async fn test_approved_image_readoption() -> anyhow::Result<()> { let chk_added = |cm: Option<&ConfigMap>| { let data = cm.and_then(|cm| cm.data.as_ref()); let json = data.and_then(|data| data.get(RV_JSON_KEY)); - json.map(|json| json.contains(EXPECTED_PCR4)).unwrap_or(false) + json.map(|json| json.contains(PRIMARY_PCR4_HASH)).unwrap_or(false) }; let rv_added = await_condition(configmaps, TRUSTEE_CONFIG_MAP, chk_added); let ctx = format!("waiting for ConfigMap {TRUSTEE_CONFIG_MAP} to contain PCR value"); From bc9ed67d27fb0e85107b14eabfe360648f4cfcfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Be=C3=B1at=20Gartzia=20Arruabarrena?= Date: Wed, 15 Jul 2026 12:59:36 +0200 Subject: [PATCH 10/14] 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 --- test_utils/src/constants.rs | 31 +++++++++++++++++++++ tests/trusted_execution_cluster.rs | 43 ++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/test_utils/src/constants.rs b/test_utils/src/constants.rs index 1a37acdc..4ac2daea 100644 --- a/test_utils/src/constants.rs +++ b/test_utils/src/constants.rs @@ -7,9 +7,23 @@ pub const TRUSTEE_CONFIG_MAP: &str = "trustee-data"; pub const RV_JSON_KEY: &str = "reference-values.json"; pub const APPROVED_IMAGE_NAME: &str = "coreos-approved-primary"; +pub const COMBINE_PCRS_UPDATE_TEST_IMAGE_NAME: &str = "coreos-test-update-combination"; +// TODO: This image does not contain a custom ignition, neither a clevis pin. +// It is just right to check that the operator fills the expected trustee +// reference values in the integration test covering PCR combination. +// However, it is not an image that supports attestation and so it will +// always boot, even if reference values or policies were not configured +// properly. +pub const COMBINE_PCRS_UPDATE_TEST_IMAGE_REF: &str = "quay.io/trusted-execution-clusters/fedora-coreos@sha256:372a5db90a8695fafc2869d438bacd7f0ef7fd84f63746a450bfcd4b8b64ae83"; pub const PRIMARY_PCR4_HASH: &str = "ff2b357be4a4bc66be796d4e7b2f1f27077dc89b96220aae60b443bcf4672525"; +pub const SECONDARY_PCR4_HASH: &str = + "37517a1f76c4d5cf615f4690921c732ad31359aac55f3aaf66d65a8ed38655a9"; +pub const MIX_PRIMARY_BOOT_SECONDARY_KERNEL_PCR4_HASH: &str = + "0c4e52c0bc5d2fedbf83b2fee82664dbe5347a79cfb2cbcb9a37f64211add6e8"; +pub const MIX_SECONDARY_BOOT_PRIMARY_KERNEL_PCR4_HASH: &str = + "cc5a5360e64b25718be370ca2056645a9ba9e9bae33df08308d6b8e05b8ebb87"; // PCR7 is not used for attestation yet. This constant is only consumed by // unit tests. pub const PCR7_HASH: &str = "b3a56a06c03a65277d0a787fcabc1e293eaa5d6dd79398f2dda741f7b874c65d"; @@ -23,6 +37,11 @@ pub const PRIMARY_GRUB_HASH: &str = "bc6844fc7b59b4f0c7da70a307fc578465411d7a2c34b0f4dc2cc154c873b644"; pub const PRIMARY_KERNEL_HASH: &str = "72c613f1b4d60dcf51f82f3458cca246580d23150130ec6751ac6fa62c867364"; +pub const SECONDARY_SHIM_HASH: &str = PRIMARY_SHIM_HASH; +pub const SECONDARY_GRUB_HASH: &str = + "f45c2c974192366a5391e077c3cbf91e735e86eba2037fd86a1f1501818f73f4"; +pub const SECONDARY_KERNEL_HASH: &str = + "f31e645e5e9ed131eea5dca0a18893a21e5625b4a56314fa39587ddc33a7fa91"; #[macro_export] macro_rules! pcr4_ev_efi_action_event { @@ -250,3 +269,15 @@ macro_rules! pcrs { macro_rules! primary_pcrs { () => {{ pcrs!(primary_pcr4!()) }}; } + +#[macro_export] +macro_rules! secondary_pcrs { + () => {{ + pcrs!(pcr4!( + SECONDARY_PCR4_HASH, + SECONDARY_SHIM_HASH, + SECONDARY_GRUB_HASH, + SECONDARY_KERNEL_HASH + )) + }}; +} diff --git a/tests/trusted_execution_cluster.rs b/tests/trusted_execution_cluster.rs index d915f49e..62584f58 100644 --- a/tests/trusted_execution_cluster.rs +++ b/tests/trusted_execution_cluster.rs @@ -352,3 +352,46 @@ async fn test_approved_image_readoption() -> anyhow::Result<()> { Ok(()) } } + +named_test! { +async fn test_combined_image_pcrs_configmap_updates() -> anyhow::Result<()> { + let test_ctx = setup!([(COMBINE_PCRS_UPDATE_TEST_IMAGE_NAME, COMBINE_PCRS_UPDATE_TEST_IMAGE_REF)]).await?; + let client = test_ctx.client(); + let namespace = test_ctx.namespace(); + + // In practical terms it emulates a grub + kernel upgrade + test_ctx.verify_expected_pcrs(&[&primary_pcrs!(), &secondary_pcrs!()]).await?; + + let expected_ref_values = [ + // PCR4 + PRIMARY_PCR4_HASH, + MIX_PRIMARY_BOOT_SECONDARY_KERNEL_PCR4_HASH, + MIX_SECONDARY_BOOT_PRIMARY_KERNEL_PCR4_HASH, + SECONDARY_PCR4_HASH, + // PCR14 + PCR14_HASH, + ]; + + let configmaps: Api = Api::namespaced(client.clone(), namespace); + let all_expected_pcrs = |cm: Option<&ConfigMap>| { + let data = cm.and_then(|cm| cm.data.as_ref()); + let rv_json = data.and_then(|data| data.get("reference-values.json")); + if let Some(reference_values) = rv_json { + for value in expected_ref_values { + if !reference_values.contains(value) { + return false; + } + } + } else { + return false; + } + true + }; + let done = await_condition(configmaps, "trustee-data", all_expected_pcrs); + let ctx = "waiting for ConfigMap trustee-data to contain all expected pcrs"; + timeout(scaled_duration(180), done).await.context(ctx)??; + + test_ctx.cleanup().await?; + Ok(()) +} +} From 719e7672be724a2b2d4aad6dd1515a6cd08c1a70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Be=C3=B1at=20Gartzia=20Arruabarrena?= Date: Wed, 15 Jul 2026 12:59:36 +0200 Subject: [PATCH 11/14] test_utils: Remove redundancy in verify_expected_pcrs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The logic checking that the image-pcrs configmap was populated moved into using await_condition instead of a poller. Some conditions were checked twice inside of the actual condition and subsequent assertions. This commit removes those assertions in favor of the await call. Moreover, it makes more sense from a stability point of view to wait for the whole condition to happen, as it could be the case that a Nth image is approved, and we run all the assertions (and fail) because only one of the N images was found in the configmap. This way, the tests will wait until the expected images of all approved images are present. Signed-off-by: Beñat Gartzia Arruabarrena --- test_utils/src/lib.rs | 66 +++++++------------------------------------ 1 file changed, 10 insertions(+), 56 deletions(-) diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index 0648788a..3bffd2da 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -1084,66 +1084,20 @@ impl TestContext { let data = cm.and_then(|cm| cm.data.as_ref()); let json = data.and_then(|data| data.get("image-pcrs.json")); let pcrs = json.and_then(|json| serde_json::from_str::(json).ok()); - pcrs.map(|pcrs| !pcrs.0.is_empty()).unwrap_or(false) + pcrs.map(|pcrs| { + pcrs.0.len() == expected_pcrs.len() + && pcrs.0.values().all(|image_data| { + expected_pcrs + .iter() + .any(|exp| compare_pcrs(&image_data.pcrs, exp)) + }) + }) + .unwrap_or(false) }; let done = await_condition(configmap_api.clone(), "image-pcrs", populated); - let ctx = "waiting for ConfigMap image-pcrs to be populated"; + let ctx = "waiting for ConfigMap image-pcrs to be populated with expected PCR values"; timeout(scaled_duration(180), done).await.context(ctx)??; - 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(()) } } From 651291eed66f3a24cea9906c3b670aeeb9399355 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Be=C3=B1at=20Gartzia=20Arruabarrena?= Date: Wed, 15 Jul 2026 12:59:36 +0200 Subject: [PATCH 12/14] scripts: Pre-pull extra approved images MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some integration tests might run on more approved images. Find which it is based on the test file then pre-pull it. Signed-off-by: Beñat Gartzia Arruabarrena Assisted-By: Claude Opus 4.6 --- scripts/pre-pull-images.sh | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/scripts/pre-pull-images.sh b/scripts/pre-pull-images.sh index 5c002c9f..91209f0f 100755 --- a/scripts/pre-pull-images.sh +++ b/scripts/pre-pull-images.sh @@ -4,15 +4,27 @@ # # SPDX-License-Identifier: CC0-1.0 +secondary_approved_image() { + sed -nE 's/pub const COMBINE_PCRS_UPDATE_TEST_IMAGE_REF: &str = "(.*)";/\1/p' test_utils/src/constants.rs +} ## Special handling for the approved image because of the kind issue with ## loading images used by image volumes. See: ## https://github.com/kubernetes-sigs/kind/issues/4099 -LOCAL_APPROVED_IMAGE=localhost:5000/approved-image:latest -docker pull $APPROVED_IMAGE -docker tag $APPROVED_IMAGE $LOCAL_APPROVED_IMAGE -docker push $LOCAL_APPROVED_IMAGE -docker exec -ti kind-control-plane crictl pull $LOCAL_APPROVED_IMAGE +pull_approved_image() { + local image=$1 + local index=$2 + local local_image="localhost:5000/approved-image-${index}:latest" + echo "Pulling approved image: $image" + docker pull "$image" + docker tag "$image" "$local_image" + docker push "$local_image" + docker exec -ti kind-control-plane crictl pull "$local_image" + echo "-------------------------------" +} + +pull_approved_image "$APPROVED_IMAGE" 0 +pull_approved_image "$(secondary_approved_image)" 1 KV_VERSION=v1.7.0 IMAGES=( From f6894031a68d2a434981786d5a9f9f9a1b053ce3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Be=C3=B1at=20Gartzia=20Arruabarrena?= Date: Wed, 15 Jul 2026 12:59:36 +0200 Subject: [PATCH 13/14] .github: Pre-pull images before running integration tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This help not hitting some test timeouts due to images not being pulled quickly enough. Signed-off-by: Beñat Gartzia Arruabarrena --- .github/workflows/integration-tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index f415dabc..e8f7d56e 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -72,6 +72,8 @@ jobs: run: make push - name: "Install KubeVirt" run: make install-kubevirt + - name: "Pre-pull images" + run: make pre-pull-images - name: "Run integration tests" run: | eval $(ssh-agent -s) From c5c9f13366b1b5b147ffe568f250f3fb55d2cc1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Be=C3=B1at=20Gartzia=20Arruabarrena?= Date: Wed, 15 Jul 2026 12:59:37 +0200 Subject: [PATCH 14/14] docs: Updating approved images and expected PCR values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Covering the primary and secondary approved images and also possible combinations between the two of them. Signed-off-by: Beñat Gartzia Arruabarrena --- docs/dev/test_approved_images.md | 130 +++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 docs/dev/test_approved_images.md diff --git a/docs/dev/test_approved_images.md b/docs/dev/test_approved_images.md new file mode 100644 index 00000000..0b601995 --- /dev/null +++ b/docs/dev/test_approved_images.md @@ -0,0 +1,130 @@ +# Approved images for integration tests + +There are two approved images that are used to run operator's +integration tests: The "primary", held by the `APPROVED_IMAGE` +environment variable in the [`Makefile`](/Makefile) and the "secondary", +defined in the `COMBINE_PCRS_UPDATE_TEST_IMAGE_REF` constant in +[`test_utils/src/constants.rs`](/tests/trusted_execution_cluster.rs). + +Integration tests assume those are the images that the VMs joining the +test cluster are booting. Based on that assumption, a set of PCRs are +defined as constants in +[`test_utils/src/constants.rs`](/test_utils/src/constants.rs). +Constant variable names try to be self-explanatory. + +In case any of the images need to be updated, the constant PCR values +that integration tests rely on might very likely need to be updated too. + +## Updating approved images for integration tests +Approved images used in integration tests are usually Fedora CoreOS +images that are pinned in the trusted-execution-clusters quay.io. That +is, basically pulled from a CoreOS mirror and pushed into this +organization's. This is done so images are not garbage collected in the +original mirror and the team is not kept busy dealing with test approved +image updates so frequently. + +Between the pull and the push, some image modifications shall be applied +to prepare the images to behave under the test cases. Mainly, to inject +the clevis-pin and a custom ignition to them. There is a helpful +[Containerfile][approved-image-containerfile] available in the investigations repository that does +exactly that. + +[approved-image-containerfile]: https://github.com/trusted-execution-clusters/investigations/blob/3321d58394131b8c56430cf11a29dc0076c00f37/coreos/Containerfile + +## Updating reference PCR values for integration tests + +### When to update reference PCR value constants +In the event of an approved image pointer update, integration tests will +very likely break. Updating the reference PCR values should be enough to +fix them back. + +In case the primary approved image was updated, `PRIMARY_*_HASH` consts +will need an update, as well as the `MIX_*_KERNEL_PCR4_HASH` consts. + +For secondary approved image updates, `SECONDARY_*_HASH` and +`MIX_*_KERNEL_PCR4_HASH` constants will need to be updated. + +In case both primary and secondary images are updated, all hashes will +need to be updated. + +### How to compute reference PCR value constants +The easiest way to compute the new PCR values is certainly to use the +compute-pcrs library against the new image. However, that might not be +the most desirable way to do it, as it would implicitly bypass some of +the purpose of the integration tests. + +The better way to do it is to boot the new image in a disposable VM and +check tpm event log. + +In this document section we are going to focus just in PCR4. + +First, a qcow2 image is needed. The bootable container image can be +"turned" into a qcow2 image. Another way, when it comes to CoreOS is to +find the image in the [CoreOS builds browser][coreos-build-browser] and +obtain its already built qcow2 relative. + +To boot the VM, the investigations [`install_vm.sh`][installvm] script +might come in handy. + +#### Boot stack event hashes +Once booted, log into the VM and run: +```bash +$ sudo tpm2_eventlog /sys/kernel/security/tpm0/binary_bios_measurements | \ + yq '.events[] | + select(.PCRIndex == 4 and + .EventType == "EV_EFI_BOOT_SERVICES_APPLICATION") | + .Digests[] | + select(.AlgorithmId == "sha256") | + .Digest' +``` +This should print 3 sha256 hash values. The ones relative to (in order +of appearance) shim, grub and the vmlinuz hashes. + +If 3 values are not printed, it might be that secure boot was not +enabled in the VM, and the hash values should be just shim and grub's. +Make sure secure boot is enabled and try again. + +Take those values and update the values of +`{PRIMARY|SECONDARY}_{SHIM,GRUB,KERNEL}_HASH` constants accordingly. + +#### PCR4 hashes +Then again, run the following command in the VM: +```bash +$ sudo tpm2_eventlog /sys/kernel/security/tpm0/binary_bios_measurements | \ + yq '.pcrs.sha256.4' | \ + sed 's/^0x//' +``` +This will print the resulting PCR4 hash of the new approved image. +Update the `PRIMARY_PCR4_HASH` or `SECONDARY_PCR4_HASH` value. + +#### Combined PCR4 hashes +The tricky part comes with the combination (or mix) PCR4 hashes. These +model possible intermediate stages that the VM goes through during an +update. That includes the VM booting a new kernel through the old +bootloader, or booting the old kernel with a new bootloader in case of a +rollback being needed. + +Set the shim and grub hashes of the primary image and the kernel hash of +the secondary image and run the following bash script: +```bash +hashes=( + "3d6772b4f84ed47595d72a2c4c5ffd15f5bb72c7507fe26f2aaee2c69d5633ba" + "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119" + "${SHIM_HASH}" + "${GRUB_HASH}" + "${KERNEL_HASH}" +) +current_pcr="0000000000000000000000000000000000000000000000000000000000000000" +for event_hash in "${hashes[@]}"; do + current_pcr=$(echo -n "${current_pcr}${event_hash}" | xxd -r -p | sha256sum | awk '{print $1}') +done +echo $current_pcr +``` + +The output is `MIX_PRIMARY_BOOT_SECONDARY_KERNEL_PCR4_HASH`. Then repeat +again with shim and grub hashes from the secondary image and the kernel +hash of the primary image to obtain +`MIX_SECONDARY_BOOT_PRIMARY_KERNEL_PCR4_HASH`. + +[coreos-build-browser]: https://builds.coreos.fedoraproject.org/browser?stream=stable&arch=x86_64 +[installvm]: https://github.com/trusted-execution-clusters/investigations/blob/f3bcaa95fff7c39092fae5cc63f9b31a2aacb221/scripts/install_vm.sh