diff --git a/Cargo.lock b/Cargo.lock index b90e8696..26958469 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -461,16 +461,20 @@ dependencies = [ [[package]] name = "compute-pcrs-lib" version = "0.1.0" -source = "git+https://github.com/trusted-execution-clusters/compute-pcrs#1e7b9f74206e436d1426c335e30b2f1a6bd1681e" +source = "git+https://github.com/trusted-execution-clusters/compute-pcrs#12a1d01e437a166b29331ad817375167d7bbfb5e" dependencies = [ "anyhow", "glob", "hex", "hex-literal", + "itertools 0.14.0", "lief", + "log", "openssl", "serde", + "serde_with", "sha2", + "strum", "uuid", ] @@ -637,6 +641,16 @@ dependencies = [ "darling_macro 0.20.11", ] +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", +] + [[package]] name = "darling" version = "0.23.0" @@ -661,6 +675,20 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.110", +] + [[package]] name = "darling_core" version = "0.23.0" @@ -685,6 +713,17 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", + "quote", + "syn 2.0.110", +] + [[package]] name = "darling_macro" version = "0.23.0" @@ -714,6 +753,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", + "serde_core", ] [[package]] @@ -940,7 +980,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -1707,6 +1747,8 @@ checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", "hashbrown 0.16.0", + "serde", + "serde_core", ] [[package]] @@ -1742,7 +1784,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -1775,6 +1817,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" @@ -1879,7 +1930,7 @@ checksum = "05a6d6f3611ad1d21732adbd7a2e921f598af6c92d71ae6e2620da4b67ee1f0d" dependencies = [ "base64 0.22.1", "jiff", - "schemars", + "schemars 1.1.0", "serde", "serde_json", ] @@ -1944,7 +1995,7 @@ dependencies = [ "jiff", "json-patch", "k8s-openapi", - "schemars", + "schemars 1.1.0", "serde", "serde-value", "serde_json", @@ -2015,9 +2066,9 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "lief" -version = "0.17.1" +version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18773f648622afc222330700726227739936a1fa0040b91f007c80605525c4ff" +checksum = "6446b5d7b31c03d9c99321515294004f1b27bbbd5265a6533321e1c4dfef2e75" dependencies = [ "bitflags 2.10.0", "cxx", @@ -2030,9 +2081,9 @@ dependencies = [ [[package]] name = "lief-build" -version = "0.17.1" +version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3abce5340e56c7f9191d699302b9496445b0438033b48a9937b41c506f677731" +checksum = "73e010d2fc798ca2b420e0c483163b77571e13843a96cc61933b30ef4db2cdb6" dependencies = [ "git-version", "miette", @@ -2043,9 +2094,9 @@ dependencies = [ [[package]] name = "lief-ffi" -version = "0.17.1" +version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fba2ccbe972b26212f69f7b938fc198214f4eaa7f548a8d724f1a04968137f96" +checksum = "4af183cc1edf58fdfffce46f28685b43efa57736b218e9d341a6e9c68df61ac9" dependencies = [ "autocxx", "cxx", @@ -3013,7 +3064,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -3077,6 +3128,18 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "schemars" version = "1.1.0" @@ -3262,6 +3325,37 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.12.0", + "schemars 0.9.0", + "schemars 1.1.0", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +dependencies = [ + "darling 0.21.3", + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "serde_yaml" version = "0.9.34+deprecated" @@ -3441,6 +3535,9 @@ name = "strum" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] [[package]] name = "strum_macros" @@ -3567,7 +3664,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -3638,10 +3735,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", + "itoa", "num-conv", "powerfmt", "serde", "time-core", + "time-macros", ] [[package]] @@ -3650,6 +3749,16 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +[[package]] +name = "time-macros" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" version = "0.8.2" @@ -3842,6 +3951,7 @@ dependencies = [ "compute-pcrs-lib", "env_logger", "fs_extra", + "glob", "http 1.4.0", "ignition-config", "k8s-openapi", @@ -3866,6 +3976,7 @@ dependencies = [ "anyhow", "cfg-if", "compute-pcrs-lib", + "hex", "k8s-openapi", "kube", "regex", diff --git a/Cargo.toml b/Cargo.toml index 50df09aa..c56faabe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ clevis-pin-trustee-lib = { git = "https://github.com/latchset/clevis-pin-trustee compute-pcrs-lib = { git = "https://github.com/trusted-execution-clusters/compute-pcrs" } env_logger = { version = "0.11.9", default-features = false } http = "1.4.0" +hex = "0.4.3" ignition-config = "0.5.0" k8s-openapi = { version = "0.27.0", features = ["v1_33", "schemars"] } kube = { version = "3.0.1", default-features = false, features = ["derive", "runtime", "openssl-tls"] } diff --git a/Makefile b/Makefile index 472ffb17..f29157e4 100644 --- a/Makefile +++ b/Makefile @@ -159,7 +159,7 @@ endif sed 's//$(NAMESPACE)/g' kind/kbs-forward.yaml | $(KUBECTL) apply -f -; \ fi $(KUBECTL) apply -f $(DEPLOY_PATH)/trusted_execution_cluster_cr.yaml - $(KUBECTL) apply -f $(DEPLOY_PATH)/approved_image_cr.yaml + $(KUBECTL) apply -f '$(DEPLOY_PATH)/approved_image_cr_*.yaml' install-kubevirt: scripts/install-kubevirt.sh diff --git a/api/trusted-cluster-gen.go b/api/trusted-cluster-gen.go index 2b590b51..5859343a 100644 --- a/api/trusted-cluster-gen.go +++ b/api/trusted-cluster-gen.go @@ -21,6 +21,17 @@ import ( "sigs.k8s.io/yaml" ) +type stringSlice []string + +func (s *stringSlice) String() string { + return strings.Join(*s, ", ") +} + +func (s *stringSlice) Set(value string) error { + *s = append(*s, value) + return nil +} + type Args struct { outputDir string image string @@ -29,7 +40,7 @@ type Args struct { pcrsComputeImage string registerServerImage string attestationKeyRegisterImage string - approvedImage string + approvedImages stringSlice } func main() { @@ -41,7 +52,7 @@ func main() { flag.StringVar(&args.pcrsComputeImage, "pcrs-compute-image", "quay.io/trusted-execution-clusters/compute-pcrs:latest", "Container image with the Trusted Execution Clusters compute-pcrs binary") flag.StringVar(&args.registerServerImage, "register-server-image", "quay.io/trusted-execution-clusters/register-server:latest", "Register server image to use in the deployment") flag.StringVar(&args.attestationKeyRegisterImage, "attestation-key-register-image", "quay.io/trusted-execution-clusters/attestation-key-register:latest", "Attestation key register image to use in the deployment") - flag.StringVar(&args.approvedImage, "approved-image", "", "When set, defines an initial approved image. Must be a bootable container image with SHA reference.") + flag.Var(&args.approvedImages, "approved-image", "When set, defines an initial approved image. Must be a bootable container image with SHA reference. Can be set multiple times.") flag.Parse() log.SetFlags(log.LstdFlags) @@ -166,34 +177,33 @@ func generateTrustedExecutionClusterCR(args *Args) error { } func generateApprovedImageCR(args *Args) error { - if args.approvedImage == "" { - return nil - } + for i, approvedImage := range args.approvedImages { + approvedImage := &v1alpha1.ApprovedImage{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1alpha1.GroupVersion.String(), + Kind: "ApprovedImage", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("coreos-%d", i), + Namespace: args.namespace, + }, + Spec: v1alpha1.ApprovedImageSpec{ + Reference: approvedImage, + }, + } - approvedImage := &v1alpha1.ApprovedImage{ - TypeMeta: metav1.TypeMeta{ - APIVersion: v1alpha1.GroupVersion.String(), - Kind: "ApprovedImage", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "coreos", - Namespace: args.namespace, - }, - Spec: v1alpha1.ApprovedImageSpec{ - Reference: args.approvedImage, - }, - } + approvedImageYAML, err := yaml.Marshal(approvedImage) + if err != nil { + return fmt.Errorf("failed to marshal ApprovedImage CR %d: %v", i, err) + } - approvedImageYAML, err := yaml.Marshal(approvedImage) - if err != nil { - return fmt.Errorf("failed to marshal ApprovedImage CR: %v", err) + outputPath := filepath.Join(args.outputDir, fmt.Sprintf("approved_image_cr_%d.yaml", i)) + if err := writeResources(outputPath, []string{string(approvedImageYAML)}); err != nil { + return fmt.Errorf("failed to write %s: %v", outputPath, err) + } + log.Printf("Generated ApprovedImage CR at %s", outputPath) } - outputPath := filepath.Join(args.outputDir, "approved_image_cr.yaml") - if err := writeResources(outputPath, []string{string(approvedImageYAML)}); err != nil { - return fmt.Errorf("failed to write %s: %v", outputPath, err) - } - log.Printf("Generated ApprovedImage CR at %s", outputPath) return nil } diff --git a/operator/Cargo.toml b/operator/Cargo.toml index 97fcf308..917d405e 100644 --- a/operator/Cargo.toml +++ b/operator/Cargo.toml @@ -17,7 +17,7 @@ trusted-cluster-operator-lib = { path = "../lib" } compute-pcrs-lib.workspace = true env_logger.workspace = true futures-util = "0.3.31" -hex = "0.4.3" +hex.workspace = true json-patch = "4.1.0" jsonptr = "0.7.1" k8s-openapi.workspace = true diff --git a/operator/src/test_utils.rs b/operator/src/test_utils.rs index a9c0241d..88dc2de5 100644 --- a/operator/src/test_utils.rs +++ b/operator/src/test_utils.rs @@ -3,6 +3,7 @@ // SPDX-License-Identifier: MIT use compute_pcrs_lib::Pcr; +use compute_pcrs_lib::tpmevents::{TPMEvent, TPMEventID}; use k8s_openapi::{api::core::v1::ConfigMap, jiff::Timestamp}; use kube::Client; use operator::RvContextData; @@ -11,6 +12,11 @@ use std::collections::BTreeMap; use crate::trustee; use trusted_cluster_operator_lib::reference_values::{ImagePcr, ImagePcrs, PCR_CONFIG_FILE}; +pub const DUMMY_PCR_4_VALUE: &str = + "3f263b96ccbc33bb53d808771f9ab1e02d4dec8854f9530f749cde853a723273"; +pub const DUMMY_PCR_7_VALUE: &str = + "e58ada1ba75f2e4722b539824598ad5e10c55f2e4aeab2033f3b0a8ee3f3eca6"; + pub fn dummy_pcrs() -> ImagePcrs { ImagePcrs(BTreeMap::from([( "cos".to_string(), @@ -18,17 +24,33 @@ pub fn dummy_pcrs() -> ImagePcrs { first_seen: Timestamp::now(), pcrs: vec![ Pcr { - id: 0, - value: "pcr0_val".to_string(), - parts: 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".to_string(), - parts: 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: "ref".to_string(), + reference: "".to_string(), }, )])) } diff --git a/operator/src/trustee.rs b/operator/src/trustee.rs index 849a0cf6..0b32f3f6 100644 --- a/operator/src/trustee.rs +++ b/operator/src/trustee.rs @@ -8,6 +8,8 @@ use anyhow::{Context, Result}; use base64::{Engine as _, engine::general_purpose}; use chrono::{DateTime, Utc}; use clevis_pin_trustee_lib::Key as ClevisKey; +use compute_pcrs_lib::tpmevents::TPMEvent; +use compute_pcrs_lib::tpmevents::combine::combine_images; use k8s_openapi::api::apps::v1::{Deployment, DeploymentSpec}; use k8s_openapi::api::core::v1::{ ConfigMap, ConfigMapVolumeSource, Container, ContainerPort, EmptyDirVolumeSource, EnvVar, @@ -26,7 +28,7 @@ use log::info; use operator::{RvContextData, create_or_info_if_exists}; use serde::{Serialize, Serializer}; use serde_json::{Value::String as JsonString, json}; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use trusted_cluster_operator_lib::endpoints::*; use trusted_cluster_operator_lib::reference_values::*; @@ -70,14 +72,20 @@ pub fn get_image_pcrs(image_pcrs_map: ConfigMap) -> Result { } fn recompute_reference_values(image_pcrs: ImagePcrs) -> Vec { - // TODO many grub+shim:many OS image recompute once supported let mut reference_values_in = - BTreeMap::from([("svn".to_string(), vec![JsonString("1".to_string())])]); - for pcr in image_pcrs.0.values().flat_map(|v| &v.pcrs) { + BTreeMap::from([("svn".to_string(), BTreeSet::from(["1".to_string()]))]); + let tpm_events: Vec> = image_pcrs + .0 + .values() + .map(|v| v.pcrs.iter().flat_map(|p| p.events.clone()).collect()) + .collect(); + + let pcr_combinations = combine_images(&tpm_events); + for pcr in pcr_combinations.iter().flatten() { reference_values_in .entry(format!("pcr{}", pcr.id)) .or_default() - .push(JsonString(pcr.value.clone())); + .insert(hex::encode(pcr.value.clone())); } reference_values_in .iter() @@ -85,7 +93,7 @@ fn recompute_reference_values(image_pcrs: ImagePcrs) -> Vec { version: "0.1.0".to_string(), name: format!("tpm_{name}"), expiration: Utc::now() + chrono::Duration::days(365), - value: serde_json::Value::Array(values.to_vec()), + value: serde_json::Value::Array(values.iter().map(|v| JsonString(v.clone())).collect()), }) .collect() } @@ -546,16 +554,36 @@ 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::*; + + 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"); + assert_eq!( + hex::encode(image_pcrs.0["cos"].pcrs[0].value.clone()), + DUMMY_PCR_4_VALUE + ); + assert_eq!( + hex::encode(image_pcrs.0["cos"].pcrs[1].value.clone()), + "e58ada1ba75f2e4722b539824598ad5e10c55f2e4aeab2033f3b0a8ee3f3eca6" + ); } #[test] @@ -589,10 +617,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!["pcr0_val".to_string()]); + 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] @@ -853,4 +881,71 @@ mod tests { let clos = |client| generate_kbs_deployment(client, Default::default(), "image"); test_create_error(clos).await; } + + #[test] + fn test_recompute_reference_values_pcr4() { + let cos2_pcr4_hash = "c7fc63ec604348d8258993a9e344ba72041afd1473ad291a3171199b551aedbd"; + let image_pcrs = ImagePcrs(BTreeMap::from([ + ( + "cos1".to_string(), + ImagePcr { + first_seen: Timestamp::now(), + pcrs: vec![expected_pcr4!(), expected_pcr7!()], + reference: "".to_string(), + }, + ), + ( + "cos2".to_string(), + ImagePcr { + first_seen: Timestamp::now(), + pcrs: vec![Pcr { + id: 4, + value: hex::decode(cos2_pcr4_hash).unwrap(), + events: vec![ + pcr4_ev_efi_action_event!(), + pcr_separator_event!(4, 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, + }, + ], + }, + expected_pcr7!()], + 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![ + "514259b499f88d74cce9ff4763bb95d5c4e9a6703df48467a99dbcae02c3d974", + cos2_pcr4_hash, + "c9c3add791efc98f59977c89e673a34ad0b357872e9eb2c43d14607488e5d9e2", + expected_pcr4_hash!() + ] + ); + let vals_pcr7 = reference_values_from(&result, "tpm_pcr7"); + assert_eq!(vals_pcr7, vec![expected_pcr7_hash!()]); + } } diff --git a/test_utils/Cargo.toml b/test_utils/Cargo.toml index ac8addba..a1d14915 100644 --- a/test_utils/Cargo.toml +++ b/test_utils/Cargo.toml @@ -35,3 +35,4 @@ tower = "0.5.2" trusted-cluster-operator-lib = { path = "../lib" } uuid.workspace = true which = "8.0" +glob = "0.3.3" diff --git a/test_utils/src/constants.rs b/test_utils/src/constants.rs new file mode 100644 index 00000000..27fa2cf0 --- /dev/null +++ b/test_utils/src/constants.rs @@ -0,0 +1,232 @@ +// SPDX-FileCopyrightText: BeƱat Gartzia Arruabarrena +// +// SPDX-License-Identifier: MIT + +#[macro_export] +macro_rules! expected_pcr4_hash { + () => {{ "ff2b357be4a4bc66be796d4e7b2f1f27077dc89b96220aae60b443bcf4672525" }}; +} + +#[macro_export] +macro_rules! expected_pcr7_hash { + () => {{ "b3a56a06c03a65277d0a787fcabc1e293eaa5d6dd79398f2dda741f7b874c65d" }}; +} + +#[macro_export] +macro_rules! expected_pcr14_hash { + () => {{ "17cdefd9548f4383b67a37a901673bf3c8ded6f619d36c8007562de1d93c81cc" }}; +} + +#[macro_export] +macro_rules! tpmevent_pcr4eficall_hash { + () => {{ "3d6772b4f84ed47595d72a2c4c5ffd15f5bb72c7507fe26f2aaee2c69d5633ba" }}; +} + +#[macro_export] +macro_rules! tpmevent_separator_hash { + () => {{ "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119" }}; +} + +#[macro_export] +macro_rules! pcr4_ev_efi_action_event { + () => {{ + TPMEvent { + pcr: 4, + name: "EV_EFI_ACTION".to_string(), + hash: hex::decode(tpmevent_pcr4eficall_hash!()).unwrap(), + id: TPMEventID::Pcr4EfiCall, + } + }}; +} + +#[macro_export] +macro_rules! pcr_separator_event { + ($pcr:expr, $event_id:expr) => {{ + TPMEvent { + pcr: $pcr, + name: "EV_SEPARATOR".to_string(), + hash: hex::decode(tpmevent_separator_hash!()).unwrap(), + id: $event_id, + } + }}; +} + +#[macro_export] +macro_rules! expected_pcr7 { + () => {{ + Pcr { + id: 7, + value: hex::decode(expected_pcr7_hash!()).unwrap(), + events: vec![ + TPMEvent { + pcr: 7, + name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), + hash: hex::decode( + "ccfc4bb32888a345bc8aeadaba552b627d99348c767681ab3141f5b01e40a40e", + ) + .unwrap(), + id: TPMEventID::Pcr7SecureBoot, + }, + TPMEvent { + pcr: 7, + name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), + hash: hex::decode( + "adb6fc232943e39c374bf4782b6c697f43c39fca1f4b51dfceda21164e19a893", + ) + .unwrap(), + id: TPMEventID::Pcr7Pk, + }, + TPMEvent { + pcr: 7, + name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), + hash: hex::decode( + "b5432fe20c624811cb0296391bfdf948ebd02f0705ab8229bea09774023f0ebf", + ) + .unwrap(), + id: TPMEventID::Pcr7Kek, + }, + TPMEvent { + pcr: 7, + name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), + hash: hex::decode( + "4313e43de720194a0eabf4d6415d42b5a03a34fdc47bb1fc924cc4e665e6893d", + ) + .unwrap(), + id: TPMEventID::Pcr7Db, + }, + TPMEvent { + pcr: 7, + name: "EV_EFI_VARIABLE_DRIVER_CONFIG".to_string(), + hash: hex::decode( + "001004ba58a184f09be6c1f4ec75a246cc2eefa9637b48ee428b6aa9bce48c55", + ) + .unwrap(), + id: TPMEventID::Pcr7Dbx, + }, + pcr_separator_event!(7, TPMEventID::Pcr7Separator), + TPMEvent { + pcr: 7, + name: "EV_EFI_VARIABLE_AUTHORITY".to_string(), + hash: hex::decode( + "4d4a8e2c74133bbdc01a16eaf2dbb5d575afeb36f5d8dfcf609ae043909e2ee9", + ) + .unwrap(), + id: TPMEventID::Pcr7ShimCert, + }, + TPMEvent { + pcr: 7, + name: "EV_EFI_VARIABLE_AUTHORITY".to_string(), + hash: hex::decode( + "e8e9578f5951ef16b1c1aa18ef02944b8375ec45ed4b5d8cdb30428db4a31016", + ) + .unwrap(), + id: TPMEventID::Pcr7SbatLevel, + }, + TPMEvent { + pcr: 7, + name: "EV_EFI_VARIABLE_AUTHORITY".to_string(), + hash: hex::decode( + "ad5901fd581e6640c742c488083b9ac2c48255bd28a16c106c6f9df52702ee3f", + ) + .unwrap(), + id: TPMEventID::Pcr7GrubMokListCert, + }, + ], + } + }}; +} + +#[macro_export] +macro_rules! expected_pcr14 { + () => {{ + Pcr { + id: 14, + value: hex::decode(expected_pcr14_hash!()).unwrap(), + events: vec![ + TPMEvent { + pcr: 14, + name: "EV_IPL".to_string(), + hash: hex::decode( + "e8e48e3ad10bc243341b4663c0057aef0ec7894ccc9ecb0598f0830fa57f7220", + ) + .unwrap(), + id: TPMEventID::Pcr14MokList, + }, + TPMEvent { + pcr: 14, + name: "EV_IPL".to_string(), + hash: hex::decode( + "8d8a3aae50d5d25838c95c034aadce7b548c9a952eb7925e366eda537c59c3b0", + ) + .unwrap(), + id: TPMEventID::Pcr14MokListX, + }, + TPMEvent { + pcr: 14, + name: "EV_IPL".to_string(), + hash: hex::decode( + "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a", + ) + .unwrap(), + id: TPMEventID::Pcr14MokListTrusted, + }, + ], + } + }}; +} + +#[macro_export] +macro_rules! pcr4_shim_event { + () => {{ + TPMEvent { + pcr: 4, + name: "EV_EFI_BOOT_SERVICES_APPLICATION".to_string(), + hash: hex::decode("94896c17d49fc8c8df0cc2836611586edab1615ce7cb58cf13fc5798de56b367") + .unwrap(), + id: TPMEventID::Pcr4Shim, + } + }}; +} + +#[macro_export] +macro_rules! pcr4_grub_event { + () => {{ + TPMEvent { + pcr: 4, + name: "EV_EFI_BOOT_SERVICES_APPLICATION".to_string(), + hash: hex::decode("bc6844fc7b59b4f0c7da70a307fc578465411d7a2c34b0f4dc2cc154c873b644") + .unwrap(), + id: TPMEventID::Pcr4Grub, + } + }}; +} + +#[macro_export] +macro_rules! expected_pcr4 { + () => {{ + Pcr { + id: 4, + value: hex::decode(expected_pcr4_hash!()).unwrap(), + events: vec![ + pcr4_ev_efi_action_event!(), + pcr_separator_event!(4, TPMEventID::Pcr4Separator), + pcr4_shim_event!(), + pcr4_grub_event!(), + TPMEvent { + pcr: 4, + name: "EV_EFI_BOOT_SERVICES_APPLICATION".to_string(), + hash: hex::decode( + "72c613f1b4d60dcf51f82f3458cca246580d23150130ec6751ac6fa62c867364", + ) + .unwrap(), + id: TPMEventID::Pcr4Vmlinuz, + }, + ], + } + }}; +} + +#[macro_export] +macro_rules! expected_base_pcrs { + () => {{ [expected_pcr4!(), expected_pcr7!(), expected_pcr14!()] }}; +} diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index df8879d7..86f3d2b2 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -5,6 +5,7 @@ use anyhow::{Result, anyhow}; use fs_extra::dir; +use glob::glob; use k8s_openapi::api::apps::v1::Deployment; use k8s_openapi::api::core::v1::{ConfigMap, Namespace}; use kube::api::DeleteParams; @@ -12,6 +13,7 @@ use kube::{Api, Client}; use std::path::{Path, PathBuf}; use std::{collections::BTreeMap, env, sync::Once, time::Duration}; use tokio::process::Command; +use trusted_cluster_operator_lib::reference_values::ImagePcrs; use trusted_cluster_operator_lib::TrustedExecutionCluster; use trusted_cluster_operator_lib::endpoints::*; @@ -20,6 +22,7 @@ use trusted_cluster_operator_lib::routes::Route; pub mod timer; pub use timer::Poller; +pub mod constants; pub mod mock_client; #[cfg(feature = "virtualization")] @@ -228,7 +231,7 @@ pub struct TestContext { } impl TestContext { - pub async fn new(test_name: &str) -> Result { + pub async fn new(test_name: &str, approved_images: &[&str]) -> Result { INIT.call_once(|| { let _ = env_logger::builder().is_test(true).try_init(); }); @@ -248,7 +251,7 @@ impl TestContext { ctx.manifests_dir = manifests_dir; ctx.create_namespace().await?; - ctx.apply_operator_manifests().await?; + ctx.apply_operator_manifests(approved_images).await?; test_info!( &ctx.test_name, @@ -417,7 +420,11 @@ impl TestContext { .await } - async fn generate_manifests(&self, workspace_root: &PathBuf) -> Result<(PathBuf, PathBuf)> { + async fn generate_manifests( + &self, + workspace_root: &PathBuf, + approved_images: &[&str], + ) -> Result<(PathBuf, PathBuf)> { let ns = self.test_namespace.clone(); let controller_gen_path = workspace_root.join("bin/controller-gen-v0.19.0"); @@ -476,6 +483,11 @@ impl TestContext { args.extend(&["-register-server-image", ®_srv_img]); args.extend(&["-attestation-key-register-image", &att_reg_img]); args.extend(&["-approved-image", &approved_image]); + args.extend( + approved_images + .iter() + .flat_map(|&i| vec!["-approved-image", i]), + ); let manifest_gen = Command::new(&trusted_cluster_gen_path).args(args).output(); let manifest_gen_output = manifest_gen.await?; if !manifest_gen_output.status.success() { @@ -485,11 +497,13 @@ impl TestContext { Ok((crd_temp_dir, rbac_temp_dir)) } - async fn apply_operator_manifests(&self) -> Result<()> { + async fn apply_operator_manifests(&self, approved_images: &[&str]) -> Result<()> { let manifests_dir = &self.manifests_dir; test_info!(&self.test_name, "Generating manifests in {manifests_dir}"); let workspace_root = env::current_dir()?.join(".."); - let (crd_temp_dir, rbac_temp_dir) = self.generate_manifests(&workspace_root).await?; + let (crd_temp_dir, rbac_temp_dir) = self + .generate_manifests(&workspace_root, approved_images) + .await?; test_info!(&self.test_name, "Manifests generated successfully"); let tec = "trustedexecutionclusters.trusted-execution-clusters.io"; @@ -620,13 +634,20 @@ impl TestContext { let cr_manifest_str = cr_manifest_path.to_str().unwrap(); kube_apply!(cr_manifest_str, &self.test_name, "Applying CR manifest"); - let approved_image_path = manifests_path.join("approved_image_cr.yaml"); - let approved_image_str = approved_image_path.to_str().unwrap(); - kube_apply!( - approved_image_str, - &self.test_name, - "Applying ApprovedImage manifest" - ); + let approved_image_paths = glob( + manifests_path + .join("approved_image_cr_*.yaml") + .to_str() + .ok_or_else(|| anyhow::anyhow!("Invalid ApprovedImage manifest path"))?, + )?; + for approved_image_path in approved_image_paths.filter_map(Result::ok) { + let approved_image_str = approved_image_path.to_str().unwrap(); + kube_apply!( + approved_image_str, + &self.test_name, + "Applying ApprovedImage manifest" + ); + } let deployments_api: Api = Api::namespaced(self.client.clone(), &ns); @@ -672,6 +693,95 @@ impl TestContext { Ok(()) } + + pub async fn verify_expected_pcrs(&self, expected_pcrs: &[&[Pcr]]) -> anyhow::Result<()> { + let client = self.client(); + let namespace = self.namespace(); + + let configmap_api: Api = Api::namespaced(client.clone(), namespace); + + let poller = Poller::new() + .with_timeout(Duration::from_secs(180)) + .with_interval(Duration::from_secs(5)) + .with_error_message("image-pcrs ConfigMap not populated with data".to_string()); + + poller + .poll_async(|| { + let api = configmap_api.clone(); + async move { + let cm = api.get("image-pcrs").await?; + + if let Some(data) = &cm.data + && let Some(image_pcrs_json) = data.get("image-pcrs.json") + && let Ok(image_pcrs) = serde_json::from_str::(image_pcrs_json) + && image_pcrs.0.len() == expected_pcrs.len() + { + return Ok(()); + } + + Err(anyhow::anyhow!( + "image-pcrs ConfigMap not yet populated with image-pcrs.json data" + )) + } + }) + .await?; + + let image_pcrs_cm = configmap_api.get("image-pcrs").await?; + assert_eq!(image_pcrs_cm.metadata.name.as_deref(), Some("image-pcrs")); + + let data = image_pcrs_cm + .data + .as_ref() + .expect("image-pcrs ConfigMap should have data field"); + + assert!(!data.is_empty(), "image-pcrs ConfigMap should have data"); + + let image_pcrs_json = data + .get("image-pcrs.json") + .expect("image-pcrs ConfigMap should have image-pcrs.json key"); + + assert!( + !image_pcrs_json.is_empty(), + "image-pcrs.json should not be empty" + ); + + // Parse the image-pcrs.json using the ImagePcrs structure + let image_pcrs: ImagePcrs = serde_json::from_str(image_pcrs_json) + .expect("image-pcrs.json should be valid ImagePcrs JSON"); + + assert!( + !image_pcrs.0.is_empty(), + "image-pcrs.json should contain at least one image entry" + ); + + test_info!( + &self.test_name, + "Checking into {} image results:", + image_pcrs.0.len() + ); + let mut found_expected_pcrs = false; + + assert_eq!( + image_pcrs.0.len(), + expected_pcrs.len(), + "image-pcrs.json should contain {} image entries", + expected_pcrs.len() + ); + + for (i, (_image_ref, image_data)) in image_pcrs.0.iter().enumerate() { + if compare_pcrs(&image_data.pcrs, expected_pcrs[i]) { + found_expected_pcrs = true; + break; + } + } + + assert!( + found_expected_pcrs, + "At least one image should have the expected PCR values" + ); + + Ok(()) + } } #[macro_export] @@ -700,7 +810,9 @@ macro_rules! virt_test { #[macro_export] macro_rules! setup { - () => {{ $crate::TestContext::new(TEST_NAME) }}; + () => {{ $crate::TestContext::new(TEST_NAME, &[]) }}; + + ($images:expr) => {{ $crate::TestContext::new(TEST_NAME, &$images) }}; } async fn setup_test_client() -> Result { diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 6d78d3b3..5c0e190a 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -16,6 +16,7 @@ virtualization = [] anyhow.workspace = true cfg-if = "1.0.4" compute-pcrs-lib.workspace = true +hex.workspace = true k8s-openapi.workspace = true kube = { workspace = true } regex = "1" diff --git a/tests/trusted_execution_cluster.rs b/tests/trusted_execution_cluster.rs index 4bdeeaf0..be1216b4 100644 --- a/tests/trusted_execution_cluster.rs +++ b/tests/trusted_execution_cluster.rs @@ -2,19 +2,17 @@ // // SPDX-License-Identifier: MIT -use compute_pcrs_lib::{Part, Pcr}; +use compute_pcrs_lib::Pcr; +use compute_pcrs_lib::tpmevents::{TPMEvent, TPMEventID}; use k8s_openapi::api::apps::v1::Deployment; use k8s_openapi::api::core::v1::{ConfigMap, Secret}; use kube::{Api, api::DeleteParams}; use std::time::Duration; -use trusted_cluster_operator_lib::reference_values::ImagePcrs; use trusted_cluster_operator_lib::{ ApprovedImage, AttestationKey, Machine, TrustedExecutionCluster, generate_owner_reference, }; use trusted_cluster_operator_test_utils::*; -const EXPECTED_PCR4: &str = "ff2b357be4a4bc66be796d4e7b2f1f27077dc89b96220aae60b443bcf4672525"; - named_test!( async fn test_trusted_execution_cluster_uninstall() -> anyhow::Result<()> { let test_ctx = setup!().await?; @@ -146,102 +144,8 @@ named_test!( named_test! { async fn test_image_pcrs_configmap_updates() -> anyhow::Result<()> { let test_ctx = setup!().await?; - let client = test_ctx.client(); - let namespace = test_ctx.namespace(); - - let configmap_api: Api = Api::namespaced(client.clone(), namespace); - - let poller = Poller::new() - .with_timeout(Duration::from_secs(180)) - .with_interval(Duration::from_secs(5)) - .with_error_message("image-pcrs ConfigMap not populated with data".to_string()); - poller - .poll_async(|| { - let api = configmap_api.clone(); - async move { - let cm = api.get("image-pcrs").await?; - - if let Some(data) = &cm.data - && let Some(image_pcrs_json) = data.get("image-pcrs.json") - && let Ok(image_pcrs) = serde_json::from_str::(image_pcrs_json) - && !image_pcrs.0.is_empty() - { - return Ok(()); - } - - Err(anyhow::anyhow!("image-pcrs ConfigMap not yet populated with image-pcrs.json data")) - } - }) - .await?; - - let image_pcrs_cm = configmap_api.get("image-pcrs").await?; - assert_eq!(image_pcrs_cm.metadata.name.as_deref(), Some("image-pcrs")); - - let data = image_pcrs_cm.data.as_ref() - .expect("image-pcrs ConfigMap should have data field"); - - assert!(!data.is_empty(), "image-pcrs ConfigMap should have data"); - - let image_pcrs_json = data.get("image-pcrs.json") - .expect("image-pcrs ConfigMap should have image-pcrs.json key"); - - assert!(!image_pcrs_json.is_empty(), "image-pcrs.json should not be empty"); - - // Parse the image-pcrs.json using the ImagePcrs structure - let image_pcrs: ImagePcrs = serde_json::from_str(image_pcrs_json) - .expect("image-pcrs.json should be valid ImagePcrs JSON"); - - assert!(!image_pcrs.0.is_empty(), "image-pcrs.json should contain at least one image entry"); - - let expected_pcrs = vec![ - Pcr { - id: 4, - value: 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() }, - ], - }, - 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() }, - ], - }, - 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() }, - ], - }, - ]; - - 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(&[&expected_base_pcrs!()]).await?; test_ctx.cleanup().await?; @@ -256,7 +160,7 @@ async fn test_image_disallow() -> anyhow::Result<()> { let namespace = test_ctx.namespace(); let images: Api = Api::namespaced(client.clone(), namespace); - images.delete("coreos", &DeleteParams::default()).await?; + images.delete("coreos-0", &DeleteParams::default()).await?; let configmap_api: Api = Api::namespaced(client.clone(), namespace); let poller = Poller::new() @@ -269,7 +173,7 @@ async fn test_image_disallow() -> anyhow::Result<()> { let cm = api.get("trustee-data").await?; if let Some(data) = &cm.data && let Some(reference_values_json) = data.get("reference-values.json") - && !reference_values_json.contains(EXPECTED_PCR4) + && !reference_values_json.contains(expected_pcr4_hash!()) { return Ok(()); } @@ -438,3 +342,72 @@ async fn test_attestation_key_lifecycle() -> anyhow::Result<()> { Ok(()) } } + +named_test! { +async fn test_combined_image_pcrs_configmap_updates() -> anyhow::Result<()> { + let test_ctx = setup!([ + "quay.io/trusted-execution-clusters/fedora-coreos@sha256:372a5db90a8695fafc2869d438bacd7f0ef7fd84f63746a450bfcd4b8b64ae83", + ]).await?; + let client = test_ctx.client(); + let namespace = test_ctx.namespace(); + + let secondary_expected_pcr4_hash = "37517a1f76c4d5cf615f4690921c732ad31359aac55f3aaf66d65a8ed38655a9"; + + test_ctx.verify_expected_pcrs( + &[&expected_base_pcrs!(), + // In practical terms it emulates a grub + kernel upgrade + &[ + Pcr { + id: 4, + value: hex::decode(secondary_expected_pcr4_hash).unwrap(), + events: vec![ + pcr4_ev_efi_action_event!(), + pcr_separator_event!(4, TPMEventID::Pcr4Separator), + pcr4_shim_event!(), + TPMEvent { pcr: 4, name: "EV_EFI_BOOT_SERVICES_APPLICATION".to_string(), hash: hex::decode("f45c2c974192366a5391e077c3cbf91e735e86eba2037fd86a1f1501818f73f4").unwrap(), id: TPMEventID::Pcr4Grub }, + TPMEvent { pcr: 4, name: "EV_EFI_BOOT_SERVICES_APPLICATION".to_string(), hash: hex::decode("f31e645e5e9ed131eea5dca0a18893a21e5625b4a56314fa39587ddc33a7fa91").unwrap(), id: TPMEventID::Pcr4Vmlinuz }, + ], + }, + expected_pcr7!(), + expected_pcr14!(), + ]] + ).await?; + + let expected_ref_values = [ + // PCR4 + expected_pcr4_hash!(), + "0c4e52c0bc5d2fedbf83b2fee82664dbe5347a79cfb2cbcb9a37f64211add6e8", + "cc5a5360e64b25718be370ca2056645a9ba9e9bae33df08308d6b8e05b8ebb87", + secondary_expected_pcr4_hash, + // PCR7 + expected_pcr7_hash!(), + // PCR14 + expected_pcr14_hash!(), + ]; + + let configmap_api: Api = Api::namespaced(client.clone(), namespace); + let poller = Poller::new() + .with_timeout(Duration::from_secs(180)) + .with_interval(Duration::from_secs(5)) + .with_error_message("Reference value expectations not met".to_string()); + poller.poll_async(|| { + let api = configmap_api.clone(); + async move { + let cm = api.get("trustee-data").await?; + if let Some(data) = &cm.data + && let Some(reference_values_json) = data.get("reference-values.json") + { + for value in expected_ref_values { + if !reference_values_json.contains(value) { + return Err(anyhow::anyhow!("Reference value expectations not met")); + } + } + } + Ok(()) + } + }).await?; + + test_ctx.cleanup().await?; + Ok(()) +} +}