-
Notifications
You must be signed in to change notification settings - Fork 10
operator: KBS API for LUKS key registration #248
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
4f0afc1
3d919ea
ea6a801
2c9ef78
8515269
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,7 +15,7 @@ use kube::runtime::controller::{Action, Controller}; | |
| use kube::runtime::reflector::{self, Store}; | ||
| use kube::runtime::watcher; | ||
| use kube::{Api, Client}; | ||
| use log::{info, warn}; | ||
| use log::{error, info, warn}; | ||
|
|
||
| use operator::{generate_owner_reference, upsert_condition}; | ||
| use trusted_cluster_operator_lib::{TrustedExecutionCluster, TrustedExecutionClusterStatus}; | ||
|
|
@@ -28,12 +28,11 @@ mod register_server; | |
| #[cfg(test)] | ||
| mod test_utils; | ||
| mod trustee; | ||
|
|
||
| use crate::conditions::*; | ||
| use operator::*; | ||
|
|
||
| /// Default tag for Trustee image | ||
| const TRUSTEE_VERSION: &str = "v0.17.0"; | ||
| const TRUSTEE_VERSION: &str = "v0.20.0"; | ||
| /// Default version tag for operator-managed component images | ||
| const COMPONENT_VERSION: &str = "v0.2.0"; | ||
| /// Default registry | ||
|
|
@@ -137,7 +136,6 @@ async fn reconcile( | |
| warn!("Installation of a component failed: {e:?}\nRequeueing..."); | ||
| return Ok(Action::requeue(Duration::from_secs(60))); | ||
| } | ||
| reference_values::adopt_approved_images(kube_client, &cluster).await?; | ||
|
|
||
| let installed_condition = installed_condition(INSTALLED_REASON, generation, existing_status); | ||
| let changed = upsert_condition(&mut conditions, installed_condition); | ||
|
|
@@ -167,10 +165,21 @@ async fn install_trustee_configuration( | |
| .context("Failed to create the KBS configuration configmap")?; | ||
| info!("Generated configmap for the KBS configuration"); | ||
|
|
||
| trustee::generate_attestation_policy(client.clone(), owner_reference.clone()) | ||
| .await | ||
| .context("Failed to create the attestation policy configmap")?; | ||
| info!("Generated configmap for the attestation policy"); | ||
| match reference_values::create_pcrs_config_map(client.clone()).await { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a need to keep the call to reference_values::create_pcrs_config_map in main() (line -291 or +297)?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no need for this call in main. I removed it |
||
| Ok(_) => info!("Created bare configmap for PCRs"), | ||
| Err(e) => error!("Failed to create the PCRs configmap: {e}"), | ||
| } | ||
|
|
||
| match trustee::generate_trustee_auth_keys_secret(client.clone(), owner_reference.clone()).await | ||
| { | ||
| Ok(_) => info!("Generate auth keys for the KBS API",), | ||
| Err(e) => error!("Failed to create the auth keys: {e}"), | ||
| } | ||
|
|
||
| match trustee::generate_rv_data(client.clone(), owner_reference.clone()).await { | ||
| Ok(_) => info!("Created configmap for reference values"), | ||
| Err(e) => error!("Failed to create the reference values configmap: {e}"), | ||
| } | ||
|
|
||
| let kbs_port = cluster.spec.trustee_kbs_port; | ||
| trustee::generate_kbs_service(client.clone(), owner_reference.clone(), kbs_port) | ||
|
|
@@ -247,7 +256,7 @@ async fn install_attestation_key_register( | |
| #[tokio::main] | ||
| async fn main() -> Result<()> { | ||
| env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); | ||
|
|
||
| let _ = jsonwebtoken_openssl::install_default(); | ||
| let kube_client = Client::try_default().await?; | ||
| info!("trusted execution clusters operator",); | ||
|
|
||
|
|
@@ -278,9 +287,9 @@ async fn main() -> Result<()> { | |
| attestation_key_register::launch_ak_controller(ak_ctx.clone()).await; | ||
| attestation_key_register::launch_machine_ak_controller(ak_ctx.clone()).await; | ||
| attestation_key_register::launch_secret_ak_controller(ak_ctx).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; | ||
| trustee::launch_trustee_sync_controller(kube_client.clone()).await; | ||
|
|
||
| Controller::new(cl, watcher::Config::default()) | ||
| .run(reconcile, controller_error_policy, ctx) | ||
|
|
@@ -294,11 +303,9 @@ async fn main() -> Result<()> { | |
| mod tests { | ||
| use http::{Method, Request, StatusCode}; | ||
| use k8s_openapi::api::apps::v1::Deployment; | ||
| use k8s_openapi::api::core::v1::{ConfigMap, Service}; | ||
| use k8s_openapi::api::core::v1::{ConfigMap, Secret, Service}; | ||
| use k8s_openapi::{apimachinery::pkg::apis::meta::v1::Time, jiff::Timestamp}; | ||
| use kube::api::ObjectList; | ||
| use kube::client::Body; | ||
| use trusted_cluster_operator_lib::ApprovedImage; | ||
|
|
||
| use super::*; | ||
| use trusted_cluster_operator_test_utils::mock_client::*; | ||
|
|
@@ -436,31 +443,26 @@ mod tests { | |
| }; | ||
|
|
||
| let clos = async |req: Request<Body>, ctr| { | ||
| if ctr < 8 && req.method() == Method::POST { | ||
| if ctr < 10 && req.method() == Method::POST { | ||
| use serde_json::to_string; | ||
| let resp = match ctr { | ||
| // Trustee | ||
| 0 => to_string(&ConfigMap::default()), | ||
| 1 => to_string(&ConfigMap::default()), | ||
| 2 => to_string(&Service::default()), | ||
| 3 => to_string(&Deployment::default()), | ||
| // Registration server | ||
| 4 => to_string(&Deployment::default()), | ||
| 5 => to_string(&Service::default()), | ||
| // Attestation key register server | ||
| 6 => to_string(&Deployment::default()), | ||
| 7 => to_string(&Service::default()), | ||
| // install_trustee_configuration | ||
| 0 => to_string(&ConfigMap::default()), // trustee-data | ||
| 1 => to_string(&ConfigMap::default()), // image-pcrs | ||
| 2 => to_string(&Secret::default()), // trustee-auth | ||
| 3 => to_string(&ConfigMap::default()), // trustee-rv-data | ||
| 4 => to_string(&Service::default()), // kbs-service | ||
| 5 => to_string(&Deployment::default()), // trustee-deployment | ||
| // install_register_server | ||
| 6 => to_string(&Deployment::default()), // register-server | ||
| 7 => to_string(&Service::default()), // register-server-svc | ||
| // install_attestation_key_register | ||
| 8 => to_string(&Deployment::default()), // ak-register | ||
| 9 => to_string(&Service::default()), // ak-register-svc | ||
| _ => unreachable!("unexpected counter {ctr}"), | ||
| }; | ||
| Ok(resp.unwrap()) | ||
| } else if ctr == 8 && req.method() == Method::GET { | ||
| let object_list = ObjectList::<ApprovedImage> { | ||
| items: Vec::new(), | ||
| types: Default::default(), | ||
| metadata: Default::default(), | ||
| }; | ||
| Ok(serde_json::to_string(&object_list).unwrap()) | ||
| } else if ctr == 9 && req.method() == Method::PATCH { | ||
| } else if ctr == 10 && req.method() == Method::PATCH { | ||
| let body = req.into_body().collect_bytes().await.unwrap().to_vec(); | ||
| let body = String::from_utf8_lossy(&body); | ||
| assert!(body.contains("ForeignCondition"),); | ||
|
|
@@ -491,7 +493,7 @@ mod tests { | |
| cluster.status = Some(TrustedExecutionClusterStatus { | ||
| conditions: Some(vec![pre_existing_installed, foreign_condition]), | ||
| }); | ||
| count_check!(10, clos, |client| { | ||
| count_check!(11, clos, |client| { | ||
| let result = reconcile(Arc::new(cluster), Arc::new(dummy_cluster_ctx(client))).await; | ||
| assert_eq!(result.unwrap(), Action::await_change()); | ||
| }); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -220,22 +220,6 @@ async fn adopt_approved_image( | |
| Ok(()) | ||
| } | ||
|
|
||
| pub async fn adopt_approved_images( | ||
| client: Client, | ||
| cluster: &TrustedExecutionCluster, | ||
| ) -> Result<()> { | ||
| let images: Api<ApprovedImage> = 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<ApprovedImage>, | ||
| client: Arc<Client>, | ||
|
|
@@ -247,6 +231,12 @@ async fn image_reconcile( | |
| .await | ||
| .map_err(|e| -> ControllerError { e.into() })?; | ||
|
|
||
| if let Some(ref cluster) = cluster { | ||
| adopt_approved_image(kube_client.clone(), &name, cluster) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch, but should we still check if it's owned?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll add this check, this can save some unnecessary patches. |
||
| .await | ||
| .map_err(|e| -> ControllerError { e.into() })?; | ||
| } | ||
|
|
||
| let images: Api<ApprovedImage> = Api::default_namespaced(kube_client.clone()); | ||
| finalizer(&images, APPROVED_IMAGE_FINALIZER, image, |ev| async { | ||
| match ev { | ||
|
|
@@ -277,19 +267,6 @@ async fn image_add_reconcile( | |
| info!("TrustedExecutionCluster is being deleted, deferring image processing for {name}"); | ||
| return Ok(Action::requeue(Duration::from_secs(5))); | ||
| } | ||
| 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 | ||
| if !cluster_owns(&cluster) { | ||
| adopt_approved_image(client.clone(), name, &cluster).await?; | ||
| } | ||
|
|
||
| let (action, reason) = match handle_new_image(client.clone(), image).await { | ||
| Ok(reason) => (Action::await_change(), reason), | ||
| Err(e) => { | ||
|
|
@@ -428,7 +405,6 @@ 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; | ||
|
|
@@ -491,12 +467,13 @@ mod tests { | |
| Ok(serde_json::to_string(&dummy_pcrs_map()).unwrap()) | ||
| } | ||
| (2, &Method::GET) | (3, &Method::PUT) => { | ||
| assert!(req.uri().path().contains(trustee::TRUSTEE_DATA_MAP)); | ||
| assert!(req.uri().path().contains(trustee::TRUSTEE_RV_MAP)); | ||
| Ok(serde_json::to_string(&dummy_trustee_map()).unwrap()) | ||
| } | ||
| (4, &Method::GET) => Err(StatusCode::NOT_FOUND), | ||
| _ => panic!("unexpected API interaction: {req:?}, counter {ctr}"), | ||
| }; | ||
| count_check!(4, clos, |client| { | ||
| count_check!(5, clos, |client| { | ||
| let job = Arc::new(dummy_job()); | ||
| let result = job_reconcile(job, Arc::new(client)).await.unwrap(); | ||
| assert_eq!(result, Action::await_change()); | ||
|
|
@@ -563,37 +540,6 @@ mod tests { | |
| 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 | ||
|
|
||
|
|
@@ -608,12 +554,13 @@ mod tests { | |
| Ok(serde_json::to_string(&dummy_pcrs_map()).unwrap()) | ||
| } | ||
| (3, &Method::GET) | (4, &Method::PUT) => { | ||
| assert!(req.uri().path().contains(trustee::TRUSTEE_DATA_MAP)); | ||
| assert!(req.uri().path().contains(trustee::TRUSTEE_RV_MAP)); | ||
| Ok(serde_json::to_string(&dummy_trustee_map()).unwrap()) | ||
| } | ||
| (5, &Method::GET) => Err(StatusCode::NOT_FOUND), | ||
| _ => panic!("unexpected API interaction: {req:?}, counter {ctr}"), | ||
| }; | ||
| count_check!(5, clos, |client| { | ||
| count_check!(6, clos, |client| { | ||
| assert!(image_remove_reconcile(client, image, cluster).await.is_ok()); | ||
| }); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please run lints after every commit