diff --git a/api/v1alpha1/conditions.go b/api/v1alpha1/conditions.go index d2fd1d6d..05bdd14e 100644 --- a/api/v1alpha1/conditions.go +++ b/api/v1alpha1/conditions.go @@ -26,4 +26,12 @@ const ( AttestationKeyApprovedCondition string = "Approved" AttestationKeyRegistrationReason string = "Registration" AttestationKeyMachineApprovedReason string = "MachineCreated" + + // Conditions for the Machine + MachineKeyProvisionedCondition string = "KeyProvisioned" + MachineKeyProvisionedReason string = "SecretMounted" + MachineKeyNotProvisionedReason string = "ProvisioningFailed" + MachineAkApprovedCondition string = "AttestationKeyApproved" + MachineAkApprovedReason string = "KeyApproved" + MachineAkNotApprovedReason string = "NoKeyMatched" ) diff --git a/lib/src/conditions.rs b/lib/src/conditions.rs index b2d571b0..365e58da 100644 --- a/lib/src/conditions.rs +++ b/lib/src/conditions.rs @@ -22,3 +22,11 @@ pub const NOT_COMMITTED_REASON_PENDING: &str = "PodPending"; pub const ATTESTATION_KEY_APPROVED_CONDITION: &str = "Approved"; pub const ATTESTATION_KEY_REGISTRATION_REASON: &str = "Registration"; pub const ATTESTATION_KEY_MACHINE_APPROVE: &str = "MachineCreated"; + +pub const MACHINE_KEY_PROVISIONED_CONDITION: &str = "KeyProvisioned"; +pub const MACHINE_KEY_PROVISIONED_REASON: &str = "SecretMounted"; +pub const MACHINE_KEY_NOT_PROVISIONED_REASON: &str = "ProvisioningFailed"; + +pub const MACHINE_AK_APPROVED_CONDITION: &str = "AttestationKeyApproved"; +pub const MACHINE_AK_APPROVED_REASON: &str = "KeyApproved"; +pub const MACHINE_AK_NOT_APPROVED_REASON: &str = "NoKeyMatched"; diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 03adb539..794f1045 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -66,6 +66,12 @@ impl Conditions for ApprovedImageStatus { } } +impl Conditions for MachineStatus { + fn conditions(&self) -> &Option> { + &self.conditions + } +} + pub fn transition_time( existing_status: &Option, type_: &str, diff --git a/operator/src/attestation_key_register.rs b/operator/src/attestation_key_register.rs index a8c9efc3..4729a146 100644 --- a/operator/src/attestation_key_register.rs +++ b/operator/src/attestation_key_register.rs @@ -31,12 +31,12 @@ use std::{collections::BTreeMap, sync::Arc, time::Duration}; use trusted_cluster_operator_lib::conditions::ATTESTATION_KEY_MACHINE_APPROVE; use trusted_cluster_operator_lib::endpoints::*; -use trusted_cluster_operator_lib::{AttestationKey, AttestationKeyStatus, Machine, update_status}; +use trusted_cluster_operator_lib::{AttestationKey, Machine}; -use crate::conditions::attestation_key_approved_condition; +use crate::conditions::{attestation_key_approved_condition, machine_ak_approved_condition}; use crate::trustee; use operator::{ControllerError, LONG_REQUEUE, TLS_DIR, controller_error_policy}; -use operator::{create_or_info_if_exists, read_certificate, upsert_condition}; +use operator::{create_or_info_if_exists, patch_status_condition, read_certificate}; /// Shared context for the three attestation-key controllers. /// Stores give local cache access to avoid repeated API-server reads. @@ -225,6 +225,19 @@ async fn machine_reconcile( return Ok(LONG_REQUEUE); } } + + let machine_name = machine.metadata.name.clone().unwrap_or_default(); + let condition = + machine_ak_approved_condition(false, machine.metadata.generation, &machine.status); + patch_status_condition::( + ctx.client.clone(), + &machine_name, + &machine.status, + condition, + "attestation-key-register", + ) + .await?; + Ok(LONG_REQUEUE) } @@ -233,15 +246,20 @@ async fn approve_ak(ak: &AttestationKey, machine: &Machine, ctx: &AkContextData) let client = &ctx.client; let aks: Api = Api::default_namespaced(client.clone()); - let generation = ak.metadata.generation; - let approve_reason = ATTESTATION_KEY_MACHINE_APPROVE; - let condition = attestation_key_approved_condition(approve_reason, generation, &ak.status); - let mut conditions = ak.status.as_ref().and_then(|s| s.conditions.clone()); - let changed = upsert_condition(&mut conditions, condition); - - if changed { - let status = AttestationKeyStatus { conditions }; - update_status!(aks, &name, status)?; + let condition = attestation_key_approved_condition( + ATTESTATION_KEY_MACHINE_APPROVE, + ak.metadata.generation, + &ak.status, + ); + if patch_status_condition::( + client.clone(), + &name, + &ak.status, + condition, + "attestation-key-register", + ) + .await? + { info!("Approved attestation key {name}"); } @@ -300,6 +318,20 @@ async fn approve_ak(ak: &AttestationKey, machine: &Machine, ctx: &AkContextData) info!("Created secret {secret_name} for attestation key {name} with finalizer"); } + let machine_condition = + machine_ak_approved_condition(true, machine.metadata.generation, &machine.status); + if patch_status_condition::( + client.clone(), + &machine_name, + &machine.status, + machine_condition, + "attestation-key-register", + ) + .await? + { + info!("Set AttestationKeyApproved condition on Machine {machine_name}"); + } + Ok(()) } diff --git a/operator/src/conditions.rs b/operator/src/conditions.rs index 817d92c1..3b49cd92 100644 --- a/operator/src/conditions.rs +++ b/operator/src/conditions.rs @@ -3,7 +3,9 @@ // SPDX-License-Identifier: MIT use k8s_openapi::apimachinery::pkg::apis::meta::v1::Condition; -use trusted_cluster_operator_lib::{AttestationKeyStatus, TrustedExecutionClusterStatus}; +use trusted_cluster_operator_lib::{ + AttestationKeyStatus, MachineStatus, TrustedExecutionClusterStatus, +}; use trusted_cluster_operator_lib::{condition_status, conditions::*, transition_time}; pub fn known_trustee_address_condition( @@ -55,6 +57,60 @@ pub fn installed_condition( } } +pub fn machine_key_provisioned_condition( + provisioned: bool, + generation: Option, + existing_status: &Option, +) -> Condition { + let (reason, message) = match provisioned { + true => ( + MACHINE_KEY_PROVISIONED_REASON, + "LUKS key generated and mounted in Trustee", + ), + false => ( + MACHINE_KEY_NOT_PROVISIONED_REASON, + "Key provisioning failed, check operator logs for details", + ), + }; + let type_ = MACHINE_KEY_PROVISIONED_CONDITION; + let status = condition_status(provisioned); + Condition { + type_: type_.to_string(), + reason: reason.to_string(), + message: message.to_string(), + last_transition_time: transition_time(existing_status, type_, &status), + status, + observed_generation: generation, + } +} + +pub fn machine_ak_approved_condition( + approved: bool, + generation: Option, + existing_status: &Option, +) -> Condition { + let (reason, message) = match approved { + true => ( + MACHINE_AK_APPROVED_REASON, + "A matching attestation key was found and approved", + ), + false => ( + MACHINE_AK_NOT_APPROVED_REASON, + "No matching attestation key found", + ), + }; + let type_ = MACHINE_AK_APPROVED_CONDITION; + let status = condition_status(approved); + Condition { + type_: type_.to_string(), + reason: reason.to_string(), + message: message.to_string(), + last_transition_time: transition_time(existing_status, type_, &status), + status, + observed_generation: generation, + } +} + pub fn attestation_key_approved_condition( reason: &str, generation: Option, diff --git a/operator/src/lib.rs b/operator/src/lib.rs index d77bc31e..da74d4bc 100644 --- a/operator/src/lib.rs +++ b/operator/src/lib.rs @@ -23,6 +23,8 @@ use std::{sync::Arc, time::Duration}; use tokio::time::timeout; // Re-export common functions from the lib +use kube::api::{Patch, PatchParams}; +use trusted_cluster_operator_lib::Conditions; pub use trusted_cluster_operator_lib::generate_owner_reference; #[derive(Debug, thiserror::Error)] @@ -172,3 +174,36 @@ pub fn upsert_condition( true } } + +pub async fn patch_status_condition( + client: Client, + name: &str, + existing_status: &Option, + condition: Condition, + field_manager: &str, +) -> Result +where + K: Resource + + Clone + + serde::de::DeserializeOwned + + Debug, + K::DynamicType: Default, + S: Conditions, +{ + let mut conditions = existing_status + .as_ref() + .and_then(|s| s.conditions().clone()); + if upsert_condition(&mut conditions, condition.clone()) { + let api: Api = Api::default_namespaced(client); + let patch = Patch::Apply(serde_json::json!({ + "apiVersion": K::api_version(&Default::default()), + "kind": K::kind(&Default::default()), + "status": { "conditions": [condition] } + })); + api.patch_status(name, &PatchParams::apply(field_manager), &patch) + .await + .map_err(Into::::into)?; + return Ok(true); + } + Ok(false) +} diff --git a/operator/src/register_server.rs b/operator/src/register_server.rs index 3ee1351c..2a748c64 100644 --- a/operator/src/register_server.rs +++ b/operator/src/register_server.rs @@ -22,6 +22,7 @@ use kube::{Api, Client, Resource}; use log::info; use std::{collections::BTreeMap, sync::Arc}; +use crate::conditions::machine_key_provisioned_condition; use crate::trustee; use operator::*; use trusted_cluster_operator_lib::{Machine, TrustedExecutionCluster, endpoints::*}; @@ -135,11 +136,35 @@ async fn keygen_reconcile( match ev { Event::Apply(machine) => { let kube_client = Arc::unwrap_or_clone(client); - let id = &machine.spec.id.clone(); + let id = machine.spec.id.clone(); + let machine_name = machine.metadata.name.clone().unwrap_or_default(); + let generation = machine.metadata.generation; + let existing_status = machine.status.clone(); async { let owner_reference = generate_owner_reference(&Arc::unwrap_or_clone(machine))?; - trustee::generate_secret(kube_client.clone(), id, owner_reference).await?; - trustee::send_secret(kube_client, id).await + let provisioning_result = + trustee::generate_secret(kube_client.clone(), &id, owner_reference).await; + let provisioning_result = match provisioning_result { + Ok(_) => trustee::send_secret(kube_client.clone(), &id).await, + Err(e) => Err(e), + }; + + let provisioned = provisioning_result.is_ok(); + let condition = machine_key_provisioned_condition( + provisioned, + generation, + &existing_status, + ); + patch_status_condition::( + kube_client, + &machine_name, + &existing_status, + condition, + "register-server", + ) + .await?; + provisioning_result?; + Ok::<(), anyhow::Error>(()) } .await .map(|_| LONG_REQUEUE)