From e807d1b0458cbe42741ad712b8f1eddb0bc94143 Mon Sep 17 00:00:00 2001 From: Jakob Naucke Date: Wed, 22 Apr 2026 15:07:43 +0200 Subject: [PATCH 1/3] api: Always use RELATED_IMAGE_* for container images Move trusteeImage etc. out of the TEC CRD. Treat them as operator-wide, and always use RELATED_IMAGE_TRUSTEE etc. as reference instead of as fallback. Fixes: #215 Signed-off-by: Jakob Naucke --- api/trusted-cluster-gen.go | 22 ++++++++++++++---- api/v1alpha1/crds.go | 24 ------------------- operator/src/main.rs | 44 ++++++++++++----------------------- test_utils/src/mock_client.rs | 4 ---- 4 files changed, 33 insertions(+), 61 deletions(-) diff --git a/api/trusted-cluster-gen.go b/api/trusted-cluster-gen.go index 793f803f..57bb2c0b 100644 --- a/api/trusted-cluster-gen.go +++ b/api/trusted-cluster-gen.go @@ -92,6 +92,24 @@ func generateOperator(args *Args) error { Name: name, Image: args.image, Command: []string{"/usr/bin/operator"}, + Env: []corev1.EnvVar{ + { + Name: "RELATED_IMAGE_TRUSTEE", + Value: args.trusteeImage, + }, + { + Name: "RELATED_IMAGE_COMPUTE_PCRS", + Value: args.pcrsComputeImage, + }, + { + Name: "RELATED_IMAGE_REGISTRATION_SERVER", + Value: args.registerServerImage, + }, + { + Name: "RELATED_IMAGE_ATTESTATION_KEY_REGISTER", + Value: args.attestationKeyRegisterImage, + }, + }, }, }, }, @@ -140,10 +158,6 @@ func generateTrustedExecutionClusterCR(args *Args) error { Namespace: args.namespace, }, Spec: v1alpha1.TrustedExecutionClusterSpec{ - TrusteeImage: &args.trusteeImage, - PcrsComputeImage: &args.pcrsComputeImage, - RegisterServerImage: &args.registerServerImage, - AttestationKeyRegisterImage: &args.attestationKeyRegisterImage, PublicAttestationKeyRegisterAddr: nil, PublicTrusteeAddr: nil, TrusteeKbsPort: 0, diff --git a/api/v1alpha1/crds.go b/api/v1alpha1/crds.go index 68a6ac71..ba11a574 100644 --- a/api/v1alpha1/crds.go +++ b/api/v1alpha1/crds.go @@ -38,30 +38,6 @@ var ( // +kubebuilder:validation:XValidation:rule="!has(oldSelf.publicAttestationKeyRegisterAddr) || has(self.publicAttestationKeyRegisterAddr)", message="Value is required once set" // +kubebuilder:validation:XValidation:rule="!has(oldSelf.publicTrusteeAddr) || has(self.publicTrusteeAddr)", message="Value is required once set" type TrustedExecutionClusterSpec struct { - // Image reference to Trustee all-in-one image. - // If not specified, uses RELATED_IMAGE_TRUSTEE environment variable from operator deployment. - // +optional - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" - TrusteeImage *string `json:"trusteeImage,omitempty"` - - // Image reference to trusted-cluster-operator's compute-pcrs image. - // If not specified, uses RELATED_IMAGE_COMPUTE_PCRS environment variable from operator deployment. - // +optional - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" - PcrsComputeImage *string `json:"pcrsComputeImage,omitempty"` - - // Image reference to trusted-cluster-operator's register-server image. - // If not specified, uses RELATED_IMAGE_REGISTRATION_SERVER environment variable from operator deployment. - // +optional - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" - RegisterServerImage *string `json:"registerServerImage,omitempty"` - - // Image reference to trusted-cluster-operator's attestation-key-register image. - // If not specified, uses RELATED_IMAGE_ATTESTATION_KEY_REGISTER environment variable from operator deployment. - // +optional - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" - AttestationKeyRegisterImage *string `json:"attestationKeyRegisterImage,omitempty"` - // Address where attester can connect to Attestation Key Register // +optional // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" diff --git a/operator/src/main.rs b/operator/src/main.rs index 06f4c88a..404144fd 100644 --- a/operator/src/main.rs +++ b/operator/src/main.rs @@ -34,15 +34,6 @@ use operator::*; /// Default version tag for operator-managed component images const COMPONENT_VERSION: &str = "0.2.0"; -/// Get image from CR spec, falling back to environment variable (set by OLM), then default. -/// This enables disconnected/airgap installations where OLM rewrites RELATED_IMAGE_* env vars. -fn get_image_or_env(cr_image: &Option, env_var: &str, default: &str) -> String { - cr_image - .clone() - .or_else(|| env::var(env_var).ok()) - .unwrap_or_else(|| default.to_string()) -} - struct ClusterContext { client: Client, /// UID of cluster that watchers are based on @@ -82,11 +73,10 @@ async fn launch_rv_watchers( Launching reference value watchers." ); let owner_reference = generate_owner_reference(&*cluster)?; - let pcrs_compute_image = get_image_or_env( - &cluster.spec.pcrs_compute_image, - "RELATED_IMAGE_COMPUTE_PCRS", - &format!("quay.io/trusted-execution-clusters/compute-pcrs:{COMPONENT_VERSION}"), - ); + let env = "RELATED_IMAGE_COMPUTE_PCRS"; + let default_image = + format!("quay.io/trusted-execution-clusters/compute-pcrs:{COMPONENT_VERSION}"); + let pcrs_compute_image = env::var(env).ok().unwrap_or(default_image); let rv_ctx = RvContextData { client, owner_reference: owner_reference.clone(), @@ -190,11 +180,9 @@ async fn install_trustee_configuration( Err(e) => error!("Failed to create the KBS service: {e}"), } - let trustee_image = get_image_or_env( - &cluster.spec.trustee_image, - "RELATED_IMAGE_TRUSTEE", - "quay.io/trusted-execution-clusters/key-broker-service:20260106", - ); + let trustee_image = env::var("RELATED_IMAGE_TRUSTEE") + .ok() + .unwrap_or("quay.io/trusted-execution-clusters/key-broker-service:20260106".to_string()); match trustee::generate_kbs_deployment(client, owner_reference, &trustee_image).await { Ok(_) => info!("Generate the KBS deployment"), Err(e) => error!("Failed to create the KBS deployment: {e}"), @@ -206,11 +194,10 @@ async fn install_trustee_configuration( async fn install_register_server(client: Client, cluster: &TrustedExecutionCluster) -> Result<()> { let owner_reference = generate_owner_reference(cluster)?; - let register_server_image = get_image_or_env( - &cluster.spec.register_server_image, - "RELATED_IMAGE_REGISTRATION_SERVER", - &format!("quay.io/trusted-execution-clusters/registration-server:{COMPONENT_VERSION}"), - ); + let env = "RELATED_IMAGE_REGISTRATION_SERVER"; + let default_image = + format!("quay.io/trusted-execution-clusters/registration-server:{COMPONENT_VERSION}"); + let register_server_image = env::var(env).ok().unwrap_or(default_image); match register_server::create_register_server_deployment( client.clone(), owner_reference.clone(), @@ -239,11 +226,10 @@ async fn install_attestation_key_register( ) -> Result<()> { let owner_reference = generate_owner_reference(cluster)?; - let attestation_key_register_image = get_image_or_env( - &cluster.spec.attestation_key_register_image, - "RELATED_IMAGE_ATTESTATION_KEY_REGISTER", - &format!("quay.io/trusted-execution-clusters/attestation-key-register:{COMPONENT_VERSION}"), - ); + let env = "RELATED_IMAGE_ATTESTATION_KEY_REGISTER"; + let default_image = + format!("quay.io/trusted-execution-clusters/attestation-key-register:{COMPONENT_VERSION}"); + let attestation_key_register_image = env::var(env).ok().unwrap_or(default_image); match attestation_key_register::create_attestation_key_register_deployment( client.clone(), owner_reference.clone(), diff --git a/test_utils/src/mock_client.rs b/test_utils/src/mock_client.rs index 1390cc56..555e0cf2 100644 --- a/test_utils/src/mock_client.rs +++ b/test_utils/src/mock_client.rs @@ -185,13 +185,9 @@ pub fn dummy_cluster() -> TrustedExecutionCluster { }, status: None, spec: TrustedExecutionClusterSpec { - trustee_image: Some("".to_string()), - pcrs_compute_image: Some("".to_string()), - register_server_image: Some("".to_string()), public_trustee_addr: Some("::".to_string()), register_server_port: None, trustee_kbs_port: None, - attestation_key_register_image: Some("".to_string()), attestation_key_register_port: None, public_attestation_key_register_addr: Some("::".to_string()), }, From 266585b9c8fc6a1e6ce679b475b344ceb4c9983a Mon Sep 17 00:00:00 2001 From: Jakob Naucke Date: Thu, 23 Apr 2026 17:03:11 +0200 Subject: [PATCH 2/3] tests: Genericize test_error method Signed-off-by: Jakob Naucke --- operator/src/attestation_key_register.rs | 6 +++-- operator/src/reference_values.rs | 7 +++--- operator/src/register_server.rs | 6 +++-- operator/src/trustee.rs | 11 +++++---- register-server/src/main.rs | 21 ++++++++++------ test_utils/src/mock_client.rs | 31 +++++++++--------------- 6 files changed, 43 insertions(+), 39 deletions(-) diff --git a/operator/src/attestation_key_register.rs b/operator/src/attestation_key_register.rs index 3bda32be..0697cb96 100644 --- a/operator/src/attestation_key_register.rs +++ b/operator/src/attestation_key_register.rs @@ -381,7 +381,9 @@ pub async fn launch_secret_ak_controller(client: Client) { #[cfg(test)] mod tests { use super::*; + use http::{Method, Request, StatusCode}; use trusted_cluster_operator_test_utils::mock_client::*; + use trusted_cluster_operator_test_utils::test_error_method; #[tokio::test] async fn test_create_ak_register_depl_success() { @@ -396,7 +398,7 @@ mod tests { let clos = |client| { create_attestation_key_register_deployment(client, Default::default(), "image") }; - test_create_error(clos).await; + test_error_method!(clos, Method::POST); } #[tokio::test] @@ -410,6 +412,6 @@ mod tests { async fn test_create_ak_register_svc_error() { let clos = |client| create_attestation_key_register_service(client, Default::default(), Some(80)); - test_create_error(clos).await; + test_error_method!(clos, Method::POST); } } diff --git a/operator/src/reference_values.rs b/operator/src/reference_values.rs index bf16fab4..1cf8b96c 100644 --- a/operator/src/reference_values.rs +++ b/operator/src/reference_values.rs @@ -413,10 +413,11 @@ pub async fn disallow_image(ctx: RvContextData, resource_name: &str) -> Result<( mod tests { use super::*; use crate::test_utils::*; - use http::{Method, Request}; + use http::{Method, Request, StatusCode}; use k8s_openapi::api::batch::v1::JobStatus; use k8s_openapi::apimachinery::pkg::apis::meta::v1::Time; use trusted_cluster_operator_test_utils::mock_client::*; + use trusted_cluster_operator_test_utils::test_error_method; #[tokio::test] async fn test_create_pcrs_cm_success() { @@ -433,7 +434,7 @@ mod tests { #[tokio::test] async fn test_create_pcrs_cm_error() { let clos = |client| create_pcrs_config_map(client, Default::default()); - test_create_error(clos).await; + test_error_method!(clos, Method::POST); } fn dummy_job() -> Job { @@ -509,7 +510,7 @@ mod tests { #[tokio::test] async fn test_compute_fresh_pcrs_error() { let clos = |client| compute_fresh_pcrs(generate_rv_ctx(client), "image", "registry"); - test_create_error(clos).await; + test_error_method!(clos, Method::POST); } // handle_new_image is an inherently online function and not tested here. diff --git a/operator/src/register_server.rs b/operator/src/register_server.rs index 959799ec..743823f1 100644 --- a/operator/src/register_server.rs +++ b/operator/src/register_server.rs @@ -200,7 +200,9 @@ pub async fn launch_keygen_controller(client: Client) { #[cfg(test)] mod tests { use super::*; + use http::{Method, Request, StatusCode}; use trusted_cluster_operator_test_utils::mock_client::*; + use trusted_cluster_operator_test_utils::test_error_method; #[tokio::test] async fn test_create_reg_server_depl_success() { @@ -211,7 +213,7 @@ mod tests { #[tokio::test] async fn test_create_reg_server_depl_error() { let clos = |client| create_register_server_deployment(client, Default::default(), "image"); - test_create_error(clos).await; + test_error_method!(clos, Method::POST); } #[tokio::test] @@ -223,6 +225,6 @@ mod tests { #[tokio::test] async fn test_create_reg_server_svc_error() { let clos = |client| create_register_server_service(client, Default::default(), Some(80)); - test_create_error(clos).await; + test_error_method!(clos, Method::POST); } } diff --git a/operator/src/trustee.rs b/operator/src/trustee.rs index aba4c542..bb209dcf 100644 --- a/operator/src/trustee.rs +++ b/operator/src/trustee.rs @@ -550,6 +550,7 @@ mod tests { use http::{Method, Request, StatusCode}; use kube::client::Body; use trusted_cluster_operator_test_utils::mock_client::*; + use trusted_cluster_operator_test_utils::test_error_method; #[test] fn test_get_image_pcrs_success() { @@ -792,7 +793,7 @@ mod tests { #[tokio::test] async fn test_generate_att_policy_error() { let clos = |client| generate_attestation_policy(client, Default::default()); - test_create_error(clos).await; + test_error_method!(clos, Method::POST); } #[tokio::test] @@ -810,7 +811,7 @@ mod tests { #[tokio::test] async fn test_generate_secret_error() { let clos = |client| generate_secret(client, "id", Default::default()); - test_create_error(clos).await; + test_error_method!(clos, Method::POST); } #[tokio::test] @@ -828,7 +829,7 @@ mod tests { #[tokio::test] async fn test_generate_trustee_data_error() { let clos = |client| generate_trustee_data(client, Default::default()); - test_create_error(clos).await; + test_error_method!(clos, Method::POST); } #[tokio::test] @@ -840,7 +841,7 @@ mod tests { #[tokio::test] async fn test_generate_kbs_service_error() { let clos = |client| generate_kbs_service(client, Default::default(), Some(80)); - test_create_error(clos).await; + test_error_method!(clos, Method::POST); } #[tokio::test] @@ -852,6 +853,6 @@ mod tests { #[tokio::test] async fn test_generate_kbs_depl_error() { let clos = |client| generate_kbs_deployment(client, Default::default(), "image"); - test_create_error(clos).await; + test_error_method!(clos, Method::POST); } } diff --git a/register-server/src/main.rs b/register-server/src/main.rs index 002283af..797baccb 100644 --- a/register-server/src/main.rs +++ b/register-server/src/main.rs @@ -215,10 +215,15 @@ async fn main() { #[cfg(test)] mod tests { - use super::*; + use super::{create_machine, EndpointInfo, Machine}; + use http::{Method, Request, StatusCode}; + use k8s_openapi::apimachinery::pkg::apis::meta::v1::OwnerReference; use kube::api::ObjectList; + use kube::api::ObjectMeta; + use trusted_cluster_operator_lib::MachineSpec; use trusted_cluster_operator_lib::TrustedExecutionCluster; use trusted_cluster_operator_test_utils::mock_client::*; + use trusted_cluster_operator_test_utils::test_error_method; fn dummy_clusters() -> ObjectList { ObjectList { @@ -280,7 +285,8 @@ mod tests { #[tokio::test] async fn test_get_public_trustee_error() { - test_get_error(async |c| EndpointInfo::create(c).await.map(|_| ())).await; + let clos = async |c| EndpointInfo::create(c).await.map(|_| ()); + test_error_method!(clos, Method::GET); } fn dummy_machine() -> Machine { @@ -319,11 +325,10 @@ mod tests { #[tokio::test] async fn test_create_machine_error() { - test_create_error(async |c| { - create_machine(c, "test", dummy_owner_reference()) - .await - .map(|_| ()) - }) - .await; + let clos = async |c| { + let machine = create_machine(c, "test", dummy_owner_reference()); + machine.await.map(|_| ()) + }; + test_error_method!(clos, Method::POST); } } diff --git a/test_utils/src/mock_client.rs b/test_utils/src/mock_client.rs index 555e0cf2..35cc8e9e 100644 --- a/test_utils/src/mock_client.rs +++ b/test_utils/src/mock_client.rs @@ -43,6 +43,17 @@ macro_rules! count_check { } } +#[macro_export] +macro_rules! test_error_method { + ($action:ident, $method:path) => { + let clos = async |req: Request<_>, _| match req.method() { + &$method => Err(StatusCode::INTERNAL_SERVER_ERROR), + _ => panic!("unexpected API interaction: {req:?}"), + }; + test_error($action, clos).await; + }; +} + pub use count_check; async fn create_response>>( @@ -141,7 +152,7 @@ pub async fn test_create_already_exists< }); } -async fn test_error< +pub async fn test_error< F: Fn(Client) -> S, S: Future>, T: Debug, @@ -158,24 +169,6 @@ async fn test_error< }); } -pub async fn test_create_error S, S: Future>>( - create: F, -) { - let clos = async |req: Request<_>, _| match req.method() { - &Method::POST => Err(StatusCode::INTERNAL_SERVER_ERROR), - _ => panic!("unexpected API interaction: {req:?}"), - }; - test_error(create, clos).await; -} - -pub async fn test_get_error S, S: Future>>(get: F) { - let clos = async |req: Request<_>, _| match req.method() { - &Method::GET => Err(StatusCode::INTERNAL_SERVER_ERROR), - _ => panic!("unexpected API interaction: {req:?}"), - }; - test_error(get, clos).await; -} - pub fn dummy_cluster() -> TrustedExecutionCluster { TrustedExecutionCluster { metadata: ObjectMeta { From 145e36381489cc64303cd37689e1fd84a4cb22f8 Mon Sep 17 00:00:00 2001 From: Jakob Naucke Date: Wed, 22 Apr 2026 22:43:41 +0200 Subject: [PATCH 3/3] rvs: Run watchers independently of TECs Watch ApprovedImages and compute reference values independently of a TEC existing. Adopt ApprovedImages from the TEC in order to include them in uninstallation. Have PCR computation jobs owned by the ApprovedImages. This avoids conflicting watchers on ApprovedImages while using a simple flow. Fixes: #216 Signed-off-by: Jakob Naucke --- .github/workflows/rust.yml | 8 +- Cargo.lock | 3 + Makefile | 4 +- docs/design/reference-values.md | 13 + docs/pics/image-flow.png | Bin 0 -> 128970 bytes docs/usage/os-and-node-lifecycle.md | 3 + lib/Cargo.toml | 3 + lib/src/lib.rs | 126 ++++++++-- operator/src/lib.rs | 10 +- operator/src/main.rs | 129 ++-------- operator/src/reference_values.rs | 374 ++++++++++++++++------------ operator/src/test_utils.rs | 10 - operator/src/trustee.rs | 18 +- register-server/src/main.rs | 2 +- test_utils/src/lib.rs | 2 +- test_utils/src/mock_client.rs | 4 +- tests/trusted_execution_cluster.rs | 116 +++++++-- 17 files changed, 476 insertions(+), 349 deletions(-) create mode 100644 docs/pics/image-flow.png diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 04bf92ab..04017ed5 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -46,7 +46,7 @@ jobs: - name: "cargo build" run: cargo build --all-targets - name: "cargo test" - run: cargo test --bins + run: cargo test --bins --lib tests-release-stable: name: "Tests (release), stable toolchain" runs-on: "ubuntu-24.04" @@ -70,7 +70,7 @@ jobs: - name: "cargo build (release)" run: cargo build --lib --release - name: "cargo test (release)" - run: cargo test --bins --release + run: cargo test --bins --lib --release tests-release-msrv: name: "Tests (release), minimum supported toolchain" runs-on: "ubuntu-24.04" @@ -100,7 +100,7 @@ jobs: - name: "cargo build (release)" run: cargo build --lib --release - name: "cargo test (release)" - run: cargo test --bins --release + run: cargo test --bins --lib --release tests-other-channels: name: "Tests, unstable toolchain" runs-on: "ubuntu-24.04" @@ -128,4 +128,4 @@ jobs: - name: "cargo build" run: cargo build --lib - name: "cargo test" - run: cargo test --bins + run: cargo test --bins --lib diff --git a/Cargo.lock b/Cargo.lock index 836536d0..0e258b1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3940,11 +3940,14 @@ version = "0.2.1" dependencies = [ "anyhow", "compute-pcrs-lib", + "http 1.4.0", "k8s-openapi", "kopium", "kube", "serde", "serde_json", + "tokio", + "trusted-cluster-operator-test-utils", ] [[package]] diff --git a/Makefile b/Makefile index a23134ba..0fadbc67 100644 --- a/Makefile +++ b/Makefile @@ -202,10 +202,10 @@ equal-conditions: lint: fmt-check clippy vet equal-conditions test: crds-rs - cargo test --workspace --bins + cargo test --workspace --bins --lib test-release: crds-rs - cargo test --workspace --bins --release + cargo test --workspace --bins --lib --release integration-tests: generate trusted-cluster-gen crds-rs RUST_LOG=info REGISTRY=$(REGISTRY) TAG=$(TAG) \ diff --git a/docs/design/reference-values.md b/docs/design/reference-values.md index 240d1a0c..f8a40a5e 100644 --- a/docs/design/reference-values.md +++ b/docs/design/reference-values.md @@ -93,3 +93,16 @@ A reference value listing for Trustee could then look like this: ## Data flow ![](../pics/rv-flow.png) + +## Ownership + +Unlike `reference-values`, `ApprovedImages` can live independent of a `TrustedExecutionCluster` object. +They can be created without one existing, and reference values are written by jobs (that the `ApprovedImages` also own) to the `image-pcrs` ConfigMap, which is created by the operator and is also independent of `TrustedExecutionClusters`. + +However, the `ApprovedImages` are adopted by the `TrustedExecutionCluster` object, both when created with a `TrustedExecutionCluster` existing and retroactively when created before `TrustedExecutionCluster` creation. +This ensures that removal of a `TrustedExecutionCluster` acts as complete uninstallation. +Finalizers on the `ApprovedImages` ensure the PCR values are removed back out of `image-pcrs` again. + +## Ownership flow + +![](../pics/image-flow.png) diff --git a/docs/pics/image-flow.png b/docs/pics/image-flow.png new file mode 100644 index 0000000000000000000000000000000000000000..4003248ca93d9b57467cb7c3f3aece018dc2944a GIT binary patch literal 128970 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%0Dq=a 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
  • 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 -s1UPA1=`P@Ds1h!YTFZd3SnxR+^Cs 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
    E@`c2n3a=XwV|QnbYWTBha1wN1exUaCrsovf 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 literal 0 HcmV?d00001 diff --git a/docs/usage/os-and-node-lifecycle.md b/docs/usage/os-and-node-lifecycle.md index b6362f50..ae4e66d8 100644 --- a/docs/usage/os-and-node-lifecycle.md +++ b/docs/usage/os-and-node-lifecycle.md @@ -52,6 +52,9 @@ Machines booting this image can now register and attest. **NB:** Updating nodes is not supported yet. Updates incur one intermediary stage of PCR values (assuming no further update on that boot) because kernel update is effective one boot _before_ shim & GRUB update. +**NB:** The TrustedExecutionCluster object adopts ApprovedImages, including those that lived before it. +This ensures that removal of a TrustedExecutionCluster acts as complete uninstallation. + # Disallowing a bootable container image For the example above: diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 4e424953..f6cc3d37 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -21,3 +21,6 @@ serde_json.workspace = true [dev-dependencies] # Only a generate dependency, not a Rust dependency. Included here for auto-updates. kopium = "0.23.0" +http.workspace = true +tokio.workspace = true +trusted-cluster-operator-test-utils = { path = "../test_utils" } diff --git a/lib/src/lib.rs b/lib/src/lib.rs index b5079494..d1d31b52 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -19,10 +19,10 @@ pub use kopium::trustedexecutionclusters::*; pub use vendor_kopium::virtualmachineinstances; pub use vendor_kopium::virtualmachines; -use anyhow::Context; +use anyhow::{Context, Result, anyhow}; use conditions::*; use k8s_openapi::apimachinery::pkg::apis::meta::v1::{Condition, OwnerReference, Time}; -use kube::Resource; +use kube::{Api, Client, Resource}; #[macro_export] macro_rules! update_status { @@ -105,7 +105,7 @@ pub fn committed_condition( /// Generate an OwnerReference for any Kubernetes resource pub fn generate_owner_reference>( object: &T, -) -> anyhow::Result { +) -> Result { let name = object.meta().name.clone(); let uid = object.meta().uid.clone(); let kind = T::kind(&()).to_string(); @@ -119,32 +119,108 @@ pub fn generate_owner_reference>( }) } -/// Get the single TrustedExecutionCluster in the namespace -/// -/// Returns an error if: -/// - No TrustedExecutionCluster is found -/// - More than one TrustedExecutionCluster is found (not supported) -pub async fn get_trusted_execution_cluster( - client: kube::Client, -) -> anyhow::Result { - use kube::Api; - +pub async fn get_opt_trusted_execution_cluster( + client: Client, +) -> Result> { let namespace = client.default_namespace().to_string(); let clusters: Api = Api::default_namespaced(client); - let params = Default::default(); - let mut list = clusters.list(¶ms).await?; - - if list.items.is_empty() { - return Err(anyhow::Error::msg(format!( - "No TrustedExecutionCluster found in namespace {namespace}. \ - Ensure that this service is in the same namespace as the TrustedExecutionCluster." - ))); - } else if list.items.len() > 1 { - return Err(anyhow::Error::msg(format!( + let list = clusters.list(&Default::default()).await?; + if list.items.len() > 1 { + return Err(anyhow!( "More than one TrustedExecutionCluster found in namespace {namespace}. \ trusted-cluster-operator does not support more than one TrustedExecutionCluster." - ))); + )); + } + Ok(list.items.into_iter().next()) +} + +/// Get the single TrustedExecutionCluster in the namespace +pub async fn get_trusted_execution_cluster(client: Client) -> Result { + let namespace = client.default_namespace().to_string(); + let cluster = get_opt_trusted_execution_cluster(client).await; + let err = anyhow!( + "No TrustedExecutionCluster found in namespace {namespace}. \ + Ensure that this service is in the same namespace as the TrustedExecutionCluster." + ); + cluster.and_then(|c| c.ok_or(err)) +} + +#[cfg(test)] +mod tests { + use super::*; + use http::StatusCode; + use kube::api::ObjectList; + use trusted_cluster_operator_test_utils::mock_client::*; + + #[tokio::test] + async fn test_get_some_trusted_execution_cluster() { + let clos = async |_, _| { + let object_list = ObjectList { + items: vec![dummy_cluster()], + types: Default::default(), + metadata: Default::default(), + }; + Ok(serde_json::to_string(&object_list).unwrap()) + }; + count_check!(1, clos, |client| { + let res = get_opt_trusted_execution_cluster(client).await; + assert!(res.unwrap().is_some()); + }); + } + + #[tokio::test] + async fn test_get_none_trusted_execution_cluster() { + let clos = async |_, _| { + let object_list = ObjectList:: { + items: vec![], + types: Default::default(), + metadata: Default::default(), + }; + Ok(serde_json::to_string(&object_list).unwrap()) + }; + count_check!(1, clos, |client| { + let res = get_opt_trusted_execution_cluster(client).await; + assert!(res.unwrap().is_none()); + }); + } + + #[tokio::test] + async fn test_non_unique_trusted_execution_cluster() { + let clos = async |_, _| { + let object_list = ObjectList { + items: vec![dummy_cluster(), dummy_cluster()], + types: Default::default(), + metadata: Default::default(), + }; + Ok(serde_json::to_string(&object_list).unwrap()) + }; + count_check!(1, clos, |client| { + let err = get_opt_trusted_execution_cluster(client).await.unwrap_err(); + assert!(err.to_string().contains("More than one")); + }); + } + + #[tokio::test] + async fn test_get_opt_trusted_execution_cluster_error() { + let clos = async |_, _| Err(StatusCode::INTERNAL_SERVER_ERROR); + count_check!(1, clos, |client| { + assert!(get_opt_trusted_execution_cluster(client).await.is_err()); + }); } - Ok(list.items.pop().unwrap()) + #[tokio::test] + async fn test_get_no_trusted_execution_cluster() { + let clos = async |_, _| { + let object_list = ObjectList:: { + items: vec![], + types: Default::default(), + metadata: Default::default(), + }; + Ok(serde_json::to_string(&object_list).unwrap()) + }; + count_check!(1, clos, |client| { + let err = get_trusted_execution_cluster(client).await.unwrap_err(); + assert!(err.to_string().contains("No TrustedExecutionCluster found")); + }); + } } diff --git a/operator/src/lib.rs b/operator/src/lib.rs index bd7e4a09..f39c0cf9 100644 --- a/operator/src/lib.rs +++ b/operator/src/lib.rs @@ -8,8 +8,7 @@ // // Use in other crates is not an intended purpose. -use k8s_openapi::apimachinery::pkg::apis::meta::v1::OwnerReference; -use kube::{Client, runtime::controller::Action}; +use kube::runtime::controller::Action; use log::info; use std::fmt::{Debug, Display}; use std::{sync::Arc, time::Duration}; @@ -17,13 +16,6 @@ use std::{sync::Arc, time::Duration}; // Re-export common functions from the lib pub use trusted_cluster_operator_lib::generate_owner_reference; -#[derive(Clone)] -pub struct RvContextData { - pub client: Client, - pub owner_reference: OwnerReference, - pub pcrs_compute_image: String, -} - #[derive(Debug, thiserror::Error)] pub enum ControllerError { #[error("{0}")] diff --git a/operator/src/main.rs b/operator/src/main.rs index 404144fd..eac6be88 100644 --- a/operator/src/main.rs +++ b/operator/src/main.rs @@ -4,7 +4,7 @@ // SPDX-License-Identifier: MIT use std::env; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; use std::time::Duration; use anyhow::Result; @@ -34,12 +34,6 @@ use operator::*; /// Default version tag for operator-managed component images const COMPONENT_VERSION: &str = "0.2.0"; -struct ClusterContext { - client: Client, - /// UID of cluster that watchers are based on - uid: Mutex>, -} - fn is_installed(status: Option) -> bool { let chk = |c: &Condition| c.type_ == INSTALLED_CONDITION && c.status == "True"; status @@ -48,49 +42,9 @@ fn is_installed(status: Option) -> bool { .unwrap_or(false) } -/// Launch reference value-related watchers. Is run once per TrustedExecutionCluster and operator -/// process. Returns whether watchers were launched. -async fn launch_rv_watchers( - cluster: Arc, - ctx: Arc, - name: &str, -) -> Result { - let client = ctx.client.clone(); - let mut launch_watchers = false; - if let Ok(mut ctx_uid) = ctx.uid.lock() { - let err = format!("TrustedExecutionCluster {name} had no UID"); - let cluster_uid = cluster.metadata.uid.clone().expect(&err); - if ctx_uid.is_none() || ctx_uid.clone() != Some(cluster_uid.clone()) { - launch_watchers = true; - *ctx_uid = Some(cluster_uid); - } - } else { - warn!("Failed to acquire lock on context UID store"); - } - if launch_watchers { - info!( - "First registration of TrustedExecutionCluster {name} by this operator. \ - Launching reference value watchers." - ); - let owner_reference = generate_owner_reference(&*cluster)?; - let env = "RELATED_IMAGE_COMPUTE_PCRS"; - let default_image = - format!("quay.io/trusted-execution-clusters/compute-pcrs:{COMPONENT_VERSION}"); - let pcrs_compute_image = env::var(env).ok().unwrap_or(default_image); - let rv_ctx = RvContextData { - client, - owner_reference: owner_reference.clone(), - pcrs_compute_image, - }; - reference_values::launch_rv_image_controller(rv_ctx.clone()).await; - reference_values::launch_rv_job_controller(rv_ctx.clone()).await; - } - Ok(launch_watchers) -} - async fn reconcile( cluster: Arc, - ctx: Arc, + client: Arc, ) -> Result { let generation = cluster.metadata.generation; let known_address = cluster.spec.public_trustee_addr.is_some(); @@ -99,7 +53,7 @@ async fn reconcile( known_trustee_address_condition(known_address, generation, existing_status); let mut conditions = Some(vec![address_condition]); - let kube_client = ctx.client.clone(); + let kube_client = Arc::unwrap_or_clone(client); let err = "trusted execution cluster had no name"; let name = &cluster.metadata.name.clone().expect(err); let clusters: Api = Api::default_namespaced(kube_client.clone()); @@ -113,8 +67,6 @@ async fn reconcile( return Ok(Action::await_change()); } - let _ = launch_rv_watchers(cluster.clone(), ctx.clone(), name).await?; - if is_installed(cluster.status.clone()) { return Ok(Action::await_change()); } @@ -146,7 +98,9 @@ async fn reconcile( install_trustee_configuration(kube_client.clone(), &cluster).await?; install_register_server(kube_client.clone(), &cluster).await?; - install_attestation_key_register(kube_client, &cluster).await?; + install_attestation_key_register(kube_client.clone(), &cluster).await?; + reference_values::adopt_approved_images(kube_client, &cluster).await?; + let condition = installed_condition(INSTALLED_REASON, generation, existing_status); conditions.as_mut().unwrap().push(condition); update_status!(clusters, name, TrustedExecutionClusterStatus { conditions })?; @@ -164,11 +118,6 @@ async fn install_trustee_configuration( Err(e) => error!("Failed to create the KBS configuration configmap: {e}"), } - match reference_values::create_pcrs_config_map(client.clone(), owner_reference.clone()).await { - Ok(_) => info!("Created bare configmap for PCRs"), - Err(e) => error!("Failed to create the PCRs configmap: {e}"), - } - match trustee::generate_attestation_policy(client.clone(), owner_reference.clone()).await { Ok(_) => info!("Generate configmap for the attestation policy",), Err(e) => error!("Failed to create the attestation policy configmap: {e}"), @@ -264,18 +213,16 @@ async fn main() -> Result<()> { info!("trusted execution clusters operator",); let cl: Api = Api::default_namespaced(kube_client.clone()); - // Launch all controllers except reference value-related ones register_server::launch_keygen_controller(kube_client.clone()).await; attestation_key_register::launch_ak_controller(kube_client.clone()).await; attestation_key_register::launch_machine_ak_controller(kube_client.clone()).await; attestation_key_register::launch_secret_ak_controller(kube_client.clone()).await; + reference_values::create_pcrs_config_map(kube_client.clone()).await?; + reference_values::launch_rv_image_controller(kube_client.clone()).await; + reference_values::launch_rv_job_controller(kube_client.clone()).await; - let ctx = Arc::new(ClusterContext { - client: kube_client, - uid: Mutex::new(None), - }); Controller::new(cl, watcher::Config::default()) - .run(reconcile, controller_error_policy, ctx) + .run(reconcile, controller_error_policy, Arc::new(kube_client)) .for_each(controller_info) .await; @@ -292,47 +239,6 @@ mod tests { use super::*; use trusted_cluster_operator_test_utils::mock_client::*; - fn dummy_cluster_ctx(client: Client) -> ClusterContext { - ClusterContext { - client, - uid: Mutex::new(None), - } - } - - #[tokio::test] - async fn test_launch_watchers_create() { - let clos = async |req, ctr| panic!("unexpected API interaction: {req:?}, counter {ctr}"); - count_check!(0, clos, |client| { - let cluster = Arc::new(dummy_cluster()); - let ctx = Arc::new(dummy_cluster_ctx(client)); - assert!(launch_rv_watchers(cluster, ctx, "test").await.unwrap()); - }); - } - - #[tokio::test] - async fn test_launch_watchers_update() { - let clos = async |req, ctr| panic!("unexpected API interaction: {req:?}, counter {ctr}"); - count_check!(0, clos, |client| { - let cluster = Arc::new(dummy_cluster()); - let mut ctx = dummy_cluster_ctx(client); - ctx.uid = Mutex::new(Some("def".to_string())); - let result = launch_rv_watchers(cluster, Arc::new(ctx), "test"); - assert!(result.await.unwrap()); - }); - } - - #[tokio::test] - async fn test_launch_watchers_existing() { - let clos = async |req, ctr| panic!("unexpected API interaction: {req:?}, counter {ctr}"); - count_check!(0, clos, |client| { - let cluster = dummy_cluster(); - let mut ctx = dummy_cluster_ctx(client); - ctx.uid = Mutex::new(cluster.metadata.uid.clone()); - let result = launch_rv_watchers(Arc::new(cluster), Arc::new(ctx), "test"); - assert!(!result.await.unwrap()); - }); - } - #[tokio::test] async fn test_reconcile_uninstalling() { let clos = async |req: Request, ctr| match req.method() { @@ -345,7 +251,7 @@ mod tests { count_check!(1, clos, |client| { let mut cluster = dummy_cluster(); cluster.metadata.deletion_timestamp = Some(Time(Timestamp::now())); - let result = reconcile(Arc::new(cluster), Arc::new(dummy_cluster_ctx(client))).await; + let result = reconcile(Arc::new(cluster), Arc::new(client)).await; assert_eq!(result.unwrap(), Action::await_change()); }); } @@ -360,19 +266,16 @@ mod tests { metadata: Default::default(), }; Ok(serde_json::to_string(&object_list).unwrap()) - } else if 1 < ctr && ctr < 4 { - // Watchers - Ok(serde_json::to_string(&dummy_cluster()).unwrap()) - } else if ctr == 4 && req.method() == Method::PATCH { + } else if ctr == 1 && req.method() == Method::PATCH { assert_body_contains(req, NOT_INSTALLED_REASON_NON_UNIQUE).await; Ok(serde_json::to_string(&dummy_cluster()).unwrap()) } else { panic!("unexpected API interaction: {req:?}, counter {ctr}"); } }; - count_check!(4, clos, |client| { + count_check!(2, clos, |client| { let cluster = Arc::new(dummy_cluster()); - let result = reconcile(cluster, Arc::new(dummy_cluster_ctx(client))).await; + let result = reconcile(cluster, Arc::new(client)).await; assert_eq!(result.unwrap(), Action::requeue(Duration::from_secs(60))); }); } @@ -383,9 +286,9 @@ mod tests { r if r.method() == Method::GET => Err(StatusCode::INTERNAL_SERVER_ERROR), _ => panic!("unexpected API interaction: {req:?}"), }; - count_check!(3, clos, |client| { + count_check!(1, clos, |client| { let cluster = Arc::new(dummy_cluster()); - let result = reconcile(cluster, Arc::new(dummy_cluster_ctx(client))).await; + let result = reconcile(cluster, Arc::new(client)).await; assert!(result.is_err()); }); } diff --git a/operator/src/reference_values.rs b/operator/src/reference_values.rs index 1cf8b96c..a244685e 100644 --- a/operator/src/reference_values.rs +++ b/operator/src/reference_values.rs @@ -12,10 +12,9 @@ use k8s_openapi::{ core::v1::{ConfigMap, Container, ImageVolumeSource, Volume, VolumeMount}, core::v1::{Pod, PodSpec, PodTemplateSpec}, }, - apimachinery::pkg::apis::meta::v1::OwnerReference, jiff::Timestamp, }; -use kube::api::{DeleteParams, ListParams, ObjectMeta}; +use kube::api::{DeleteParams, ListParams, ObjectMeta, Patch}; use kube::runtime::{ controller::{Action, Controller}, finalizer, @@ -28,13 +27,13 @@ use oci_client::secrets::RegistryAuth; use oci_spec::image::ImageConfiguration; use openssl::hash::{MessageDigest, hash}; use serde::Deserialize; +use serde_json::json; use std::{collections::BTreeMap, sync::Arc, time::Duration}; +use crate::COMPONENT_VERSION; use crate::trustee::{self, get_image_pcrs}; -use operator::{ - ControllerError, RvContextData, controller_error_policy, controller_info, - create_or_info_if_exists, -}; +use operator::ControllerError; +use operator::{controller_error_policy, controller_info, create_or_info_if_exists}; use trusted_cluster_operator_lib::{conditions::*, reference_values::*, *}; const JOB_LABEL_KEY: &str = "kind"; @@ -50,7 +49,7 @@ struct ComputePcrsOutput { pcrs: Vec, } -pub async fn create_pcrs_config_map(client: Client, owner_reference: OwnerReference) -> Result<()> { +pub async fn create_pcrs_config_map(client: Client) -> Result<()> { let empty_data = BTreeMap::from([( PCR_CONFIG_FILE.to_string(), serde_json::to_string(&ImagePcrs::default())?, @@ -58,7 +57,6 @@ pub async fn create_pcrs_config_map(client: Client, owner_reference: OwnerRefere let config_map = ConfigMap { metadata: ObjectMeta { name: Some(PCR_CONFIG_MAP.to_string()), - owner_references: Some(vec![owner_reference]), ..Default::default() }, data: Some(empty_data), @@ -117,32 +115,33 @@ fn build_compute_pcrs_pod_spec( } } -async fn job_reconcile(job: Arc, ctx: Arc) -> Result { +async fn job_reconcile(job: Arc, client: Arc) -> Result { let err = "Job changed, but had no name"; let name = &job.metadata.name.clone().context(err)?; let err = format!("Job {name} changed, but had no status"); let status = &job.status.clone().context(err)?; + let kube_client = Arc::unwrap_or_clone(client); if status.completion_time.is_none() { info!("Job {name} changed, but had not completed"); return Ok(Action::requeue(Duration::from_secs(300))); } - let jobs: Api = Api::default_namespaced(ctx.client.clone()); + let jobs: Api = Api::default_namespaced(kube_client.clone()); // Foreground deletion: Delete the pod too let delete = jobs.delete(name, &DeleteParams::foreground()).await; delete.map_err(Into::::into)?; - trustee::update_reference_values(Arc::unwrap_or_clone(ctx)).await?; + trustee::update_reference_values(kube_client).await?; Ok(Action::await_change()) } -pub async fn launch_rv_job_controller(ctx: RvContextData) { - let jobs: Api = Api::default_namespaced(ctx.client.clone()); +pub async fn launch_rv_job_controller(client: Client) { + let jobs: Api = Api::default_namespaced(client.clone()); let watcher = watcher::Config { label_selector: Some(format!("{JOB_LABEL_KEY}={PCR_COMMAND_NAME}")), ..Default::default() }; tokio::spawn( Controller::new(jobs, watcher) - .run(job_reconcile, controller_error_policy, Arc::new(ctx)) + .run(job_reconcile, controller_error_policy, Arc::new(client)) .for_each(controller_info), ); } @@ -160,13 +159,15 @@ fn get_job_name(boot_image: &str) -> Result { Ok(trimmed) } -async fn compute_fresh_pcrs( - ctx: RvContextData, - resource_name: &str, - boot_image: &str, -) -> anyhow::Result<()> { - let job_name = get_job_name(boot_image)?; - let pod_spec = build_compute_pcrs_pod_spec(resource_name, boot_image, &ctx.pcrs_compute_image); +async fn compute_fresh_pcrs(client: Client, image: &ApprovedImage) -> anyhow::Result<()> { + let job_name = get_job_name(&image.spec.image)?; + let env = "RELATED_IMAGE_COMPUTE_PCRS"; + let default_image = + format!("quay.io/trusted-execution-clusters/compute-pcrs:{COMPONENT_VERSION}"); + let pcrs_compute_image = std::env::var(env).ok().unwrap_or(default_image); + let resource_name = image.metadata.name.as_ref().unwrap(); + let pod_spec = + build_compute_pcrs_pod_spec(resource_name, &image.spec.image, &pcrs_compute_image); let job = Job { metadata: ObjectMeta { name: Some(job_name.clone()), @@ -174,7 +175,7 @@ async fn compute_fresh_pcrs( JOB_LABEL_KEY.to_string(), PCR_COMMAND_NAME.to_string(), )])), - owner_references: Some(vec![ctx.owner_reference]), + owner_references: Some(vec![generate_owner_reference(image)?]), ..Default::default() }, spec: Some(JobSpec { @@ -192,121 +193,98 @@ async fn compute_fresh_pcrs( }), ..Default::default() }; - create_or_info_if_exists!(ctx.client, Job, job); + create_or_info_if_exists!(client, Job, job); + Ok(()) +} + +async fn adopt_approved_image( + client: Client, + image_name: &str, + cluster: &TrustedExecutionCluster, +) -> Result<()> { + let images: Api = Api::default_namespaced(client.clone()); + let default = "".to_string(); + let cluster_name = cluster.metadata.name.as_ref().unwrap_or(&default); + info!( + "Adding owner reference from TrustedExecutionCluster {cluster_name} \ + to ApprovedImage {image_name}" + ); + let json = json!({ + "metadata": { + "ownerReferences": [generate_owner_reference(cluster)?], + } + }); + let patch = Patch::Merge(&json); + let params = Default::default(); + images.patch(image_name, ¶ms, &patch).await?; + Ok(()) +} + +pub async fn adopt_approved_images( + client: Client, + cluster: &TrustedExecutionCluster, +) -> Result<()> { + let images: Api = Api::default_namespaced(client.clone()); + let images_list = images.list(&Default::default()).await?; + for image in images_list.items.iter() { + if image.metadata.deletion_timestamp.is_none() + && let Some(name) = image.metadata.name.as_ref() + { + adopt_approved_image(client.clone(), name, cluster).await?; + } + } Ok(()) } async fn image_reconcile( image: Arc, - ctx: Arc, + client: Arc, ) -> Result { - let kube_client = ctx.client.clone(); + let kube_client = Arc::::unwrap_or_clone(client); let err = "ApprovedImage had no name"; - let name = image.metadata.name.clone().expect(err); + let name = image.metadata.name.clone().context(err)?; + let cluster = get_opt_trusted_execution_cluster(kube_client.clone()) + .await + .map_err(|e| -> ControllerError { e.into() })?; let images: Api = Api::default_namespaced(kube_client.clone()); - let finalizer_ctx = Arc::unwrap_or_clone(ctx); finalizer(&images, APPROVED_IMAGE_FINALIZER, image, |ev| async { match ev { - Event::Apply(image) => image_add_reconcile(finalizer_ctx, &image).await, - Event::Cleanup(_) => { - // Check if the TrustedExecutionCluster is being deleted - // If so, skip updating reference values as everything will be cleaned up - let tec_name = finalizer_ctx.owner_reference.name.clone(); - let tecs: Api = - Api::default_namespaced(kube_client.clone()); - - match tecs.get(&tec_name).await { - Ok(tec) if tec.metadata.deletion_timestamp.is_some() => { - // TEC is being deleted, skip disallow_image - info!( - "TrustedExecutionCluster {tec_name} is being deleted, \ - skipping disallow_image for {name}" - ); - Ok(Action::await_change()) - } - Err(kube::Error::Api(ae)) if ae.code == 404 => { - // TEC already deleted, skip disallow_image - info!( - "TrustedExecutionCluster {tec_name} not found, \ - skipping disallow_image for {name}" - ); - Ok(Action::await_change()) - } - Ok(_) => { - // TEC exists and is not being deleted, proceed with disallow_image - disallow_image(finalizer_ctx, &name) - .await - .map(|_| Action::await_change()) - .map_err(|e| { - finalizer::Error::::CleanupFailed(e.into()) - }) - } - Err(e) => { - // Some other error occurred - Err(finalizer::Error::::CleanupFailed( - anyhow::Error::from(e).into(), - )) - } - } - } + Event::Apply(image) => image_add_reconcile(kube_client, &image, cluster) + .await + .map_err(|e| finalizer::Error::::ApplyFailed(e.into())), + Event::Cleanup(image) => image_remove_reconcile(kube_client, image, cluster) + .await + .map_err(|e| finalizer::Error::::CleanupFailed(e.into())), } }) .await - .map_err(|e| anyhow!("failed to reconcile on image: {e}").into()) + .map_err(|e| anyhow!("failed to reconcile on image {name}: {e}").into()) } async fn image_add_reconcile( - ctx: RvContextData, + client: Client, image: &ApprovedImage, -) -> Result> { - let kube_client = ctx.client.clone(); + cluster: Option, +) -> Result { let name = image.metadata.name.as_ref().unwrap(); - + let uid_owns = |uid: &String| { + let refs = image.metadata.owner_references.as_ref(); + refs.map(|os| os.iter().any(|o| o.uid == *uid)) + }; + let cluster_owns = |cluster: &TrustedExecutionCluster| { + let uid = cluster.metadata.uid.as_ref(); + uid.and_then(uid_owns).unwrap_or(false) + }; // Adopt the image by adding TEC as owner reference if not already owned - let tec_uid = &ctx.owner_reference.uid; - let already_owned = image - .metadata - .owner_references - .as_ref() - .map(|owners| { - owners - .iter() - .any(|owner| owner.kind == "TrustedExecutionCluster" && owner.uid == *tec_uid) - }) - .unwrap_or(false); - - if !already_owned { - use kube::api::{Patch, PatchParams}; - use serde_json::json; - - info!("Adding owner reference from ApprovedImage {name} to TrustedExecutionCluster"); - - let patch = json!({ - "metadata": { - "ownerReferences": [ctx.owner_reference] - } - }); - - let images: Api = Api::default_namespaced(kube_client.clone()); - images - .patch(name, &PatchParams::default(), &Patch::Merge(&patch)) - .await - .map_err(|e| { - finalizer::Error::::ApplyFailed(anyhow::Error::from(e).into()) - })?; + if let Some(cluster) = cluster + && cluster_owns(&cluster) + { + adopt_approved_image(client.clone(), name, &cluster).await?; } - let (action, reason) = match handle_new_image(ctx, name, &image.spec.image).await { - Ok(reason) => { - let action = match reason { - NOT_COMMITTED_REASON_COMPUTING | NOT_COMMITTED_REASON_PENDING => { - Action::requeue(Duration::from_secs(10)) - } - _ => Action::await_change(), - }; - (action, reason) - } + let (action, reason) = match handle_new_image(client.clone(), image).await { + Ok(reason) => (Action::await_change(), reason), Err(e) => { warn!("PCR computation for {name} failed: {e}"); let action = Action::requeue(Duration::from_secs(60)); @@ -315,17 +293,40 @@ async fn image_add_reconcile( }; let committed = committed_condition(reason, image.metadata.generation, &image.status); let conditions = Some(vec![committed]); - let images: Api = Api::default_namespaced(kube_client); - update_status!(images, &name, ApprovedImageStatus { conditions }) - .map_err(|e| finalizer::Error::::ApplyFailed(e.into()))?; + let images: Api = Api::default_namespaced(client); + update_status!(images, &name, ApprovedImageStatus { conditions })?; Ok(action) } -pub async fn launch_rv_image_controller(ctx: RvContextData) { - let images: Api = Api::default_namespaced(ctx.client.clone()); +async fn image_remove_reconcile( + client: Client, + image: Arc, + cluster: Option, +) -> Result { + let default = "".to_string(); + let name = image.metadata.name.as_ref().unwrap_or(&default); + if cluster.is_none() { + info!("No TrustedExecutionCluster found, skipping disallow_image for {name}"); + return Ok(Action::await_change()); + } + let cluster = cluster.unwrap(); + let tec_name = cluster.metadata.name.unwrap_or("".to_string()); + if cluster.metadata.deletion_timestamp.is_some() { + info!( + "TrustedExecutionCluster {tec_name} is being deleted, \ + skipping disallow_image for {name}" + ); + return Ok(Action::await_change()); + } + disallow_image(client, name).await?; + Ok(Action::await_change()) +} + +pub async fn launch_rv_image_controller(client: Client) { + let images: Api = Api::default_namespaced(client.clone()); tokio::spawn( Controller::new(images, Default::default()) - .run(image_reconcile, controller_error_policy, Arc::new(ctx)) + .run(image_reconcile, controller_error_policy, Arc::new(client)) .for_each(controller_info), ); } @@ -341,21 +342,18 @@ async fn is_pending(client: &Client, resource_name: &str) -> Result { .is_some_and(|phase| phase == "Pending")) } -pub async fn handle_new_image( - ctx: RvContextData, - resource_name: &str, - boot_image: &str, -) -> Result<&'static str> { - let config_maps: Api = Api::default_namespaced(ctx.client.clone()); +pub async fn handle_new_image(client: Client, image: &ApprovedImage) -> Result<&'static str> { + let resource_name = image.metadata.name.as_ref().unwrap(); + let boot_image = image.spec.image.as_ref(); + let config_maps: Api = Api::default_namespaced(client.clone()); let mut image_pcrs_map = config_maps.get(PCR_CONFIG_MAP).await?; let mut image_pcrs = get_image_pcrs(image_pcrs_map.clone())?; if let Some(pcr) = image_pcrs.0.get(resource_name) && pcr.reference == boot_image { info!("Image {boot_image} was to be allowed, but already was allowed"); - return trustee::update_reference_values(ctx) - .await - .map(|_| COMMITTED_REASON); + let res = trustee::update_reference_values(client).await; + return res.map(|_| COMMITTED_REASON); } let image_ref: oci_client::Reference = boot_image.parse()?; if image_ref.digest().is_none() { @@ -369,7 +367,7 @@ pub async fn handle_new_image( let compute_pcrs = match label { Err(ref e) => { warn!("Fetching PCR label for {image_ref} failed: {e}. Falling back to computation."); - if is_pending(&ctx.client, resource_name).await? { + if is_pending(&client, resource_name).await? { return Ok(NOT_COMMITTED_REASON_PENDING); } true @@ -381,9 +379,8 @@ pub async fn handle_new_image( _ => false, }; if compute_pcrs { - return compute_fresh_pcrs(ctx, resource_name, boot_image) - .await - .map(|_| NOT_COMMITTED_REASON_COMPUTING); + let res = compute_fresh_pcrs(client, image).await; + return res.map(|_| NOT_COMMITTED_REASON_COMPUTING); } let image_pcr = ImagePcr { @@ -393,20 +390,20 @@ pub async fn handle_new_image( }; image_pcrs.0.insert(resource_name.to_string(), image_pcr); update_image_pcrs!(config_maps, image_pcrs_map, image_pcrs); - trustee::update_reference_values(ctx) + trustee::update_reference_values(client) .await .map(|_| COMMITTED_REASON) } -pub async fn disallow_image(ctx: RvContextData, resource_name: &str) -> Result<()> { - let config_maps: Api = Api::default_namespaced(ctx.client.clone()); +pub async fn disallow_image(client: Client, resource_name: &str) -> Result<()> { + let config_maps: Api = Api::default_namespaced(client.clone()); let mut image_pcrs_map = config_maps.get(PCR_CONFIG_MAP).await?; let mut image_pcrs = get_image_pcrs(image_pcrs_map.clone())?; if image_pcrs.0.remove(resource_name).is_none() { info!("Image {resource_name} was to be disallowed, but already was not allowed"); } update_image_pcrs!(config_maps, image_pcrs_map, image_pcrs); - trustee::update_reference_values(ctx).await + trustee::update_reference_values(client).await } #[cfg(test)] @@ -416,27 +413,46 @@ mod tests { use http::{Method, Request, StatusCode}; use k8s_openapi::api::batch::v1::JobStatus; use k8s_openapi::apimachinery::pkg::apis::meta::v1::Time; + use kube::api::ObjectList; + use kube::client::Body; use trusted_cluster_operator_test_utils::mock_client::*; use trusted_cluster_operator_test_utils::test_error_method; + const DUMMY_IMAGE_REF: &str = + "quay.io/some-ref@sha256:e71dad00aa0e3d70540e726a0c66407e3004d96e045ab6c253186e327a2419e5"; + #[tokio::test] async fn test_create_pcrs_cm_success() { - let clos = |client| create_pcrs_config_map(client, Default::default()); + let clos = |client| create_pcrs_config_map(client); test_create_success::<_, _, ConfigMap>(clos).await; } #[tokio::test] async fn test_create_pcrs_cm_exists() { - let clos = |client| create_pcrs_config_map(client, Default::default()); + let clos = |client| create_pcrs_config_map(client); test_create_already_exists(clos).await; } #[tokio::test] async fn test_create_pcrs_cm_error() { - let clos = |client| create_pcrs_config_map(client, Default::default()); + let clos = |client| create_pcrs_config_map(client); test_error_method!(clos, Method::POST); } + fn dummy_image() -> ApprovedImage { + ApprovedImage { + metadata: ObjectMeta { + name: Some("test".to_string()), + uid: Some("test".to_string()), + ..Default::default() + }, + spec: ApprovedImageSpec { + image: DUMMY_IMAGE_REF.to_string(), + }, + status: None, + } + } + fn dummy_job() -> Job { Job { metadata: ObjectMeta { @@ -466,9 +482,8 @@ mod tests { _ => panic!("unexpected API interaction: {req:?}, counter {ctr}"), }; count_check!(4, clos, |client| { - let ctx = Arc::new(generate_rv_ctx(client)); let job = Arc::new(dummy_job()); - let result = job_reconcile(job, ctx).await.unwrap(); + let result = job_reconcile(job, Arc::new(client)).await.unwrap(); assert_eq!(result, Action::await_change()); }); } @@ -477,12 +492,11 @@ mod tests { async fn test_job_reconcile_begun_deletion() { let clos = async |req: Request<_>, _| panic!("unexpected API interaction: {req:?}"); count_check!(0, clos, |client| { - let ctx = Arc::new(generate_rv_ctx(client)); let mut job = dummy_job(); let status = job.status.as_mut().unwrap(); status.completion_time = None; - let result = job_reconcile(Arc::new(job), ctx).await.unwrap(); - assert_eq!(result, Action::requeue(Duration::from_secs(300))); + let result = job_reconcile(Arc::new(job), Arc::new(client)).await; + assert_eq!(result.unwrap(), Action::requeue(Duration::from_secs(300))); }); } @@ -494,7 +508,7 @@ mod tests { #[test] fn test_get_job_name_sha() { - let name = get_job_name("quay.io/some-ref@sha256:e71dad00aa0e3d70540e726a0c66407e3004d96e045ab6c253186e327a2419e5").unwrap(); + let name = get_job_name(DUMMY_IMAGE_REF).unwrap(); assert_eq!( name, "compute-pcrs-6c57e93939-quay-io-some-ref-sha256-e71dad00aa0e3d7" @@ -503,20 +517,75 @@ mod tests { #[tokio::test] async fn test_compute_fresh_pcrs_success() { - let clos = |client| compute_fresh_pcrs(generate_rv_ctx(client), "image", "registry"); + let image = dummy_image(); + let clos = |client| compute_fresh_pcrs(client, &image); test_create_success::<_, _, Job>(clos).await; } #[tokio::test] async fn test_compute_fresh_pcrs_error() { - let clos = |client| compute_fresh_pcrs(generate_rv_ctx(client), "image", "registry"); + let image = dummy_image(); + let clos = |client| compute_fresh_pcrs(client, &image); test_error_method!(clos, Method::POST); } - // handle_new_image is an inherently online function and not tested here. + #[tokio::test] + async fn test_adopt_approved_image() { + let cluster = dummy_cluster(); + let clos = async |req: Request, _| { + assert_body_contains(req, TEST_UID).await; + Ok(serde_json::to_string(&dummy_image()).unwrap()) + }; + count_check!(1, clos, |client| { + assert!(adopt_approved_image(client, "test", &cluster).await.is_ok()); + }); + } + + #[tokio::test] + async fn test_adopt_approved_image_error() { + let cluster = dummy_cluster(); + let clos = |client| adopt_approved_image(client, "test", &cluster); + test_error_method!(clos, Method::PATCH); + } + + #[tokio::test] + async fn test_adopt_approved_images() { + let cluster = dummy_cluster(); + let clos = async |req: Request<_>, ctr| { + if ctr == 0 && req.method() == Method::GET { + let mut deleted = dummy_image(); + deleted.metadata.deletion_timestamp = Some(Time(Timestamp::now())); + let list = ObjectList { + items: vec![dummy_image(), deleted, dummy_image()], + types: Default::default(), + metadata: Default::default(), + }; + Ok(serde_json::to_string(&list).unwrap()) + } else if ctr < 3 && req.method() == Method::PATCH { + Ok(serde_json::to_string(&dummy_image()).unwrap()) + } else { + panic!("unexpected API interaction: {req:?}, counter {ctr}") + } + }; + count_check!(3, clos, |client| { + assert!(adopt_approved_images(client, &cluster).await.is_ok()); + }); + } + + #[tokio::test] + async fn test_adopt_approved_images_error() { + let cluster = dummy_cluster(); + let clos = |client| adopt_approved_images(client, &cluster); + test_error_method!(clos, Method::GET); + } + + // handle_new_image and its caller image_add_reconcile are + // inherently online functions and not tested here #[tokio::test] - async fn test_disallow_image() { + async fn test_image_remove_reconcile() { + let image = Arc::new(dummy_image()); + let cluster = Some(dummy_cluster()); let clos = async |req: Request<_>, ctr| match (ctr, req.method()) { // fetched & updated for removal, then fetched for recomputation (0, &Method::GET) | (1, &Method::PUT) | (2, &Method::GET) => { @@ -530,8 +599,7 @@ mod tests { _ => panic!("unexpected API interaction: {req:?}, counter {ctr}"), }; count_check!(5, clos, |client| { - let ctx = generate_rv_ctx(client); - assert!(disallow_image(ctx, "registry").await.is_ok()); + assert!(image_remove_reconcile(client, image, cluster).await.is_ok()); }); } } diff --git a/operator/src/test_utils.rs b/operator/src/test_utils.rs index a9c0241d..af4ed28c 100644 --- a/operator/src/test_utils.rs +++ b/operator/src/test_utils.rs @@ -4,8 +4,6 @@ use compute_pcrs_lib::Pcr; use k8s_openapi::{api::core::v1::ConfigMap, jiff::Timestamp}; -use kube::Client; -use operator::RvContextData; use std::collections::BTreeMap; use crate::trustee; @@ -53,11 +51,3 @@ pub fn dummy_pcrs_map() -> ConfigMap { ..Default::default() } } - -pub fn generate_rv_ctx(client: Client) -> RvContextData { - RvContextData { - client, - owner_reference: Default::default(), - pcrs_compute_image: String::new(), - } -} diff --git a/operator/src/trustee.rs b/operator/src/trustee.rs index bb209dcf..a1388246 100644 --- a/operator/src/trustee.rs +++ b/operator/src/trustee.rs @@ -23,7 +23,7 @@ use kube::{ api::{ObjectMeta, Patch, PatchParams}, }; use log::info; -use operator::{RvContextData, create_or_info_if_exists}; +use operator::create_or_info_if_exists; use serde::{Serialize, Serializer}; use serde_json::{Value::String as JsonString, json}; use std::collections::BTreeMap; @@ -90,8 +90,8 @@ fn recompute_reference_values(image_pcrs: ImagePcrs) -> Vec { .collect() } -pub async fn update_reference_values(ctx: RvContextData) -> Result<()> { - let config_maps: Api = Api::default_namespaced(ctx.client); +pub async fn update_reference_values(client: Client) -> Result<()> { + let config_maps: Api = Api::default_namespaced(client); let image_pcrs_map = config_maps.get(PCR_CONFIG_MAP).await?; let reference_values = recompute_reference_values(get_image_pcrs(image_pcrs_map)?); @@ -611,8 +611,7 @@ mod tests { _ => panic!("unexpected API interaction: {req:?}, counter {ctr}"), }; count_check!(3, clos, |client| { - let ctx = generate_rv_ctx(client); - assert!(update_reference_values(ctx).await.is_ok()); + assert!(update_reference_values(client).await.is_ok()); }); } @@ -623,8 +622,7 @@ mod tests { _ => panic!("unexpected API interaction: {req:?}"), }; count_check!(1, clos, |client| { - let ctx = generate_rv_ctx(client); - assert!(update_reference_values(ctx).await.is_err()); + assert!(update_reference_values(client).await.is_err()); }); } @@ -638,8 +636,7 @@ mod tests { _ => panic!("unexpected API interaction: {req:?}, counter {ctr}"), }; count_check!(2, clos, |client| { - let ctx = generate_rv_ctx(client); - assert!(update_reference_values(ctx).await.is_err()) + assert!(update_reference_values(client).await.is_err()) }); } @@ -655,8 +652,7 @@ mod tests { _ => panic!("unexpected API interaction: {req:?}, counter {ctr}"), }; count_check!(2, clos, |client| { - let ctx = generate_rv_ctx(client); - let err = update_reference_values(ctx).await.err().unwrap(); + let err = update_reference_values(client).await.err().unwrap(); assert!(err.to_string().contains("but had no data")); }); } diff --git a/register-server/src/main.rs b/register-server/src/main.rs index 797baccb..94601278 100644 --- a/register-server/src/main.rs +++ b/register-server/src/main.rs @@ -307,7 +307,7 @@ mod tests { api_version: "trusted-execution-clusters.io/v1alpha1".to_string(), kind: "TrustedExecutionCluster".to_string(), name: "test-cluster".to_string(), - uid: "test-uid".to_string(), + uid: TEST_UID.to_string(), controller: Some(true), block_owner_deletion: Some(true), } diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index 644b6aa6..a2b2fda9 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -825,7 +825,7 @@ where let name = resource_name.to_string(); async move { match api.get(&name).await { - Ok(_) => Err("{name} still exists, retrying..."), + Ok(_) => Err(format!("{name} still exists, retrying...")), Err(kube::Error::Api(ae)) if ae.code == 404 => Ok(()), Err(e) => { panic!("Unexpected error while fetching {name}: {e:?}"); diff --git a/test_utils/src/mock_client.rs b/test_utils/src/mock_client.rs index 35cc8e9e..c0f12609 100644 --- a/test_utils/src/mock_client.rs +++ b/test_utils/src/mock_client.rs @@ -14,6 +14,8 @@ use std::{convert::Infallible, sync::Arc}; use tower::service_fn; use trusted_cluster_operator_lib::{TrustedExecutionCluster, TrustedExecutionClusterSpec}; +pub const TEST_UID: &str = "test-uid"; + #[macro_export] macro_rules! assert_kube_api_error { ($err:expr, $code:expr, $reason:expr, $message:expr, $status:expr) => {{ @@ -173,7 +175,7 @@ pub fn dummy_cluster() -> TrustedExecutionCluster { TrustedExecutionCluster { metadata: ObjectMeta { name: Some("test".to_string()), - uid: Some("uid".to_string()), + uid: Some(TEST_UID.to_string()), ..Default::default() }, status: None, diff --git a/tests/trusted_execution_cluster.rs b/tests/trusted_execution_cluster.rs index c9263ce3..86d21f01 100644 --- a/tests/trusted_execution_cluster.rs +++ b/tests/trusted_execution_cluster.rs @@ -1,10 +1,13 @@ // SPDX-FileCopyrightText: Alice Frosi +// SPDX-FileCopyrightText: Jakob Naucke // // SPDX-License-Identifier: MIT +use anyhow::anyhow; use compute_pcrs_lib::{Part, Pcr}; use k8s_openapi::api::apps::v1::Deployment; use k8s_openapi::api::core::v1::{ConfigMap, Secret}; +use kube::api::ObjectMeta; use kube::{Api, api::DeleteParams}; use std::time::Duration; use trusted_cluster_operator_lib::conditions::NOT_COMMITTED_REASON_PENDING; @@ -15,18 +18,19 @@ use trusted_cluster_operator_lib::{ use trusted_cluster_operator_test_utils::*; const EXPECTED_PCR4: &str = "ff2b357be4a4bc66be796d4e7b2f1f27077dc89b96220aae60b443bcf4672525"; +const TEC_NAME: &str = "trusted-execution-cluster"; +const APPROVED_IMAGE_NAME: &str = "coreos"; +const TRUSTEE_CONFIG_MAP: &str = "trustee-data"; +const RV_JSON_KEY: &str = "reference-values.json"; named_test!( async fn test_trusted_execution_cluster_uninstall() -> anyhow::Result<()> { let test_ctx = setup!().await?; let client = test_ctx.client(); let namespace = test_ctx.namespace(); - let name = "trusted-execution-cluster"; - - let configmap_api: Api = Api::namespaced(client.clone(), namespace); let tec_api: Api = Api::namespaced(client.clone(), namespace); - let tec = tec_api.get(name).await?; + let tec = tec_api.get(TEC_NAME).await?; let owner_reference = generate_owner_reference(&tec)?; @@ -103,7 +107,7 @@ named_test!( .unwrap_or(false); if !has_approved_condition { - return Err(anyhow::anyhow!( + return Err(anyhow!( "AttestationKey does not have Approved condition yet" )); } @@ -118,18 +122,17 @@ named_test!( // Delete the cluster cr let api: Api = Api::namespaced(client.clone(), namespace); let dp = DeleteParams::default(); - api.delete(name, &dp).await?; + api.delete(TEC_NAME, &dp).await?; // Wait until it disappears - wait_for_resource_deleted(&api, name, 120, 5).await?; + wait_for_resource_deleted(&api, TEC_NAME, 120, 5).await?; let deployments_api: Api = Api::namespaced(client.clone(), namespace); wait_for_resource_deleted(&deployments_api, "trustee-deployment", 120, 1).await?; wait_for_resource_deleted(&deployments_api, "register-server", 120, 1).await?; - wait_for_resource_deleted(&configmap_api, "image-pcrs", 120, 1).await?; let images_api: Api = Api::namespaced(client.clone(), namespace); - wait_for_resource_deleted(&images_api, "coreos", 120, 1).await?; + wait_for_resource_deleted(&images_api, APPROVED_IMAGE_NAME, 120, 1).await?; wait_for_resource_deleted(&machines, &machine_name, 120, 1).await?; wait_for_resource_deleted(&attestation_keys, &ak_name, 120, 1).await?; @@ -169,7 +172,7 @@ async fn test_image_pcrs_configmap_updates() -> anyhow::Result<()> { return Ok(()); } - Err(anyhow::anyhow!("image-pcrs ConfigMap not yet populated with image-pcrs.json data")) + Err(anyhow!("image-pcrs ConfigMap not yet populated with image-pcrs.json data")) } }) .await?; @@ -255,7 +258,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(APPROVED_IMAGE_NAME, &DeleteParams::default()).await?; let configmap_api: Api = Api::namespaced(client.clone(), namespace); let poller = Poller::new() @@ -265,14 +268,14 @@ async fn test_image_disallow() -> anyhow::Result<()> { poller.poll_async(|| { let api = configmap_api.clone(); async move { - let cm = api.get("trustee-data").await?; + let cm = api.get(TRUSTEE_CONFIG_MAP).await?; if let Some(data) = &cm.data - && let Some(reference_values_json) = data.get("reference-values.json") + && let Some(reference_values_json) = data.get(RV_JSON_KEY) && !reference_values_json.contains(EXPECTED_PCR4) { return Ok(()); } - Err(anyhow::anyhow!("Reference value not yet removed")) + Err(anyhow!("Reference value not yet removed")) } }).await?; @@ -286,10 +289,9 @@ async fn test_attestation_key_lifecycle() -> anyhow::Result<()> { let test_ctx = setup!().await?; let client = test_ctx.client(); let namespace = test_ctx.namespace(); - let tec_name = "trusted-execution-cluster"; let tec_api: Api = Api::namespaced(client.clone(), namespace); - let tec = tec_api.get(tec_name).await?; + let tec = tec_api.get(TEC_NAME).await?; let owner_reference = generate_owner_reference(&tec)?; let machine_uuid = uuid::Uuid::new_v4().to_string(); @@ -368,7 +370,7 @@ async fn test_attestation_key_lifecycle() -> anyhow::Result<()> { .unwrap_or(false); if !has_approved_condition { - return Err(anyhow::anyhow!( + return Err(anyhow!( "AttestationKey does not have Approved condition yet" )); } @@ -386,7 +388,7 @@ async fn test_attestation_key_lifecycle() -> anyhow::Result<()> { .unwrap_or(false); if !has_machine_owner_ref { - return Err(anyhow::anyhow!( + return Err(anyhow!( "AttestationKey does not have owner reference to Machine yet" )); } @@ -405,7 +407,7 @@ async fn test_attestation_key_lifecycle() -> anyhow::Result<()> { .unwrap_or(false); if !has_ak_owner_ref { - return Err(anyhow::anyhow!( + return Err(anyhow!( "Secret does not have owner reference to AttestationKey yet" )); } @@ -477,3 +479,79 @@ async fn test_nonexistent_approved_image() -> anyhow::Result<()> { Ok(()) } } + +named_test! { +async fn test_approved_image_readoption() -> anyhow::Result<()> { + let test_ctx = setup!().await?; + let client = test_ctx.client(); + let namespace = test_ctx.namespace(); + + let clusters: Api = Api::namespaced(client.clone(), namespace); + let images: Api = Api::namespaced(client.clone(), namespace); + let configmaps: Api = Api::namespaced(client.clone(), namespace); + + let cluster_spec = clusters.get(TEC_NAME).await?.spec; + let image_spec = images.get(APPROVED_IMAGE_NAME).await?.spec; + + test_ctx.info(format!("Deleting TrustedExecuctionCluster {TEC_NAME}")); + clusters.delete(TEC_NAME, &Default::default()).await?; + let removal_poller = Poller::new() + .with_timeout(Duration::from_secs(60)) + .with_interval(Duration::from_secs(5)) + .with_error_message(format!("{TRUSTEE_CONFIG_MAP} configmap not removed")); + removal_poller.poll_async(|| { + let configmaps = configmaps.clone(); + async move { + if configmaps.get(TRUSTEE_CONFIG_MAP).await.is_err() { + return Ok(()); + } + Err(anyhow!("{TRUSTEE_CONFIG_MAP} not yet removed")) + } + }).await?; + test_ctx.info(format!("Configmap {TRUSTEE_CONFIG_MAP} was removed")); + + let image = ApprovedImage { + spec: image_spec, + metadata: ObjectMeta { + name: Some(APPROVED_IMAGE_NAME.to_string()), + ..Default::default() + }, + status: None + }; + let cluster = TrustedExecutionCluster { + spec: cluster_spec, + metadata: ObjectMeta { + name: Some(TEC_NAME.to_string()), + ..Default::default() + }, + status: None + }; + + test_ctx.info("Creating new ApprovedImage and TrustedExecutionCluster"); + images.create(&Default::default(), &image).await?; + // Ensure adoption works even when cluster creation was delayed + tokio::time::sleep(Duration::from_secs(5)).await; + clusters.create(&Default::default(), &cluster).await?; + let regeneration_poller = Poller::new() + .with_timeout(Duration::from_secs(180)) + .with_interval(Duration::from_secs(5)) + .with_error_message("Reference value not regenerated".to_string()); + regeneration_poller.poll_async(|| { + let configmaps = configmaps.clone(); + async move { + let configmap = configmaps.get(TRUSTEE_CONFIG_MAP).await?; + if let Some(data) = &configmap.data + && let Some(json) = data.get(RV_JSON_KEY) + && json.contains(EXPECTED_PCR4) + { + return Ok(()); + } + Err(anyhow!("Reference value not yet regenerated")) + } + }).await?; + test_ctx.info("Reference values regenerated"); + + test_ctx.cleanup().await?; + Ok(()) +} +}