Skip to content

Commit 262b4af

Browse files
committed
Bump trustee to v0.20.0 & Synchronize Trustee Resources via KBS API
All trustee resources, including the resource policy, attestation policy, RV, LUKS key, and AK, are now synchronized using the KBS API. The main motivation for this is replacing the patch mechanism that causes a restart of the trustee deployment. Also, this abstraction over the trustee backend simplifies the process of upgrading to future versions of trustee. Config: - Update kbs-config.toml to v0.20.0 format - Store reference values in dedicated ConfigMap (trustee-rv-data) instead of trustee-data API-driven resource management: - Add KBS API client functions: send_secret, delete_secret, register_ak, sync_resource_policy, sync_attestation_policy, sync_reference_values - Replace volume-mount approach for secrets with KBS API calls - Add trustee deployment reconciler that syncs policies, reference values, and secrets when deployment becomes available Dependencies: - Add kbs-client and jsonwebtoken crates - Bump trustee image tag to v0.20.0 Signed-off-by: Roy Kaufman <rkaufman@redhat.com>
1 parent 512baf6 commit 262b4af

11 files changed

Lines changed: 1631 additions & 539 deletions

Cargo.lock

Lines changed: 1184 additions & 83 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Makefile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ OPERATOR_IMAGE ?= $(REGISTRY)/trusted-cluster-operator:$(TAG)
2828
COMPUTE_PCRS_IMAGE=$(REGISTRY)/compute-pcrs:$(TAG)
2929
REG_SERVER_IMAGE=$(REGISTRY)/registration-server:$(TAG)
3030
ATTESTATION_KEY_REGISTER_IMAGE=$(REGISTRY)/attestation-key-register:$(TAG)
31-
TRUSTEE_IMAGE ?= quay.io/trusted-execution-clusters/key-broker-service:v0.17.0
31+
32+
TRUSTEE_IMAGE ?= quay.io/trusted-execution-clusters/key-broker-service:v0.20.0
3233
TEST_IMAGE ?= quay.io/trusted-execution-clusters/fedora-coreos-kubevirt:42.20260622
34+
3335
# tagged as 42.20251012.2.0
3436
APPROVED_IMAGE ?= quay.io/trusted-execution-clusters/fedora-coreos@sha256:6997f51fd27d1be1b5fc2e6cc3ebf16c17eb94d819b5d44ea8d6cf5f826ee773
3537

operator/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ serde_json.workspace = true
3131
thiserror = "2.0.18"
3232
tokio.workspace = true
3333
toml = "1.1.2"
34+
kbs-client = {git = "https://github.com/confidential-containers/trustee.git", rev = "e65897a9ad4eb3ac69fa2ec75ed831200eb2acd7", default-features = false, features = ["native-tls"] }
35+
jsonwebtoken = { version = "10.4.0", default-features = false, features = ["use_pem"] }
36+
jsonwebtoken-openssl = "1.0.0"
3437

3538
[dev-dependencies]
3639
http.workspace = true

operator/src/attestation_key_register.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ async fn secret_reconcile(
331331
match ev {
332332
Event::Apply(_secret) => {
333333
// On creation/update, just update the trustee deployment volumes
334-
trustee::update_attestation_keys(&ctx)
334+
trustee::update_attestation_keys(ctx.client.clone())
335335
.await
336336
.map(|_| Action::await_change())
337337
.map_err(|e| {
@@ -345,7 +345,7 @@ async fn secret_reconcile(
345345
"AttestationKey secret {secret_name} is being deleted, updating trustee deployment volumes"
346346
);
347347
// Update trustee deployment - secrets with deletion_timestamp will be filtered out
348-
trustee::update_attestation_keys(&ctx)
348+
trustee::update_attestation_keys(ctx.client.clone())
349349
.await
350350
.map(|_| Action::await_change())
351351
.map_err(|e| {

operator/src/kbs-config.toml

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,35 @@
22
sockets = ["0.0.0.0:8080"]
33

44
[admin]
5-
type = "DenyAll"
5+
authorization_mode = "AuthenticatedAuthorization"
6+
7+
[admin.authentication.bearer_jwt]
8+
identity_providers = [
9+
{ public_key_uri = "/opt/trustee/keys/public.pub" }
10+
]
11+
12+
[admin.authorization.regex_acl]
13+
acls = [{ role = "admin", allowed_endpoints = "^/kbs/.+$" }]
614

715
[attestation_token]
8-
insecure_key = true
9-
attestation_token_type = "CoCo"
16+
insecure_header_jwk = true
1017

1118
[attestation_service]
1219
type = "coco_as_builtin"
13-
work_dir = "/opt/trustee"
14-
policy_engine = "opa"
20+
timeout = 5
1521

1622
[attestation_service.attestation_token_broker]
17-
type = "Ear"
18-
policy_dir = "/opt/trustee/policies"
19-
20-
[attestation_service.attestation_token_config]
2123
duration_min = 5
2224

2325
[attestation_service.rvps_config]
2426
type = "BuiltIn"
2527

26-
[attestation_service.rvps_config.storage]
27-
type = "LocalJson"
28-
file_path = "/opt/trustee/reference-values.json"
29-
30-
3128
[[plugins]]
3229
name = "resource"
33-
type = "LocalFs"
34-
dir_path = "/opt/trustee/kbs-repository"
30+
storage_backend_type = "kvstorage"
31+
32+
[storage_backend]
33+
storage_type = "LocalJson"
3534

36-
[policy_engine]
37-
policy_path = "/opt/trustee/policy.rego"
35+
[storage_backend.backends.local_json]
36+
file_dir_path = "/opt/trustee/storage"

operator/src/main.rs

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,11 @@ mod register_server;
2828
#[cfg(test)]
2929
mod test_utils;
3030
mod trustee;
31-
3231
use crate::conditions::*;
3332
use operator::*;
3433

3534
/// Default tag for Trustee image
36-
const TRUSTEE_VERSION: &str = "v0.17.0";
35+
const TRUSTEE_VERSION: &str = "v0.20.0";
3736
/// Default version tag for operator-managed component images
3837
const COMPONENT_VERSION: &str = "v0.2.0";
3938
/// Default registry
@@ -132,7 +131,6 @@ async fn reconcile(
132131
update_status!(clusters, name, status)?;
133132
}
134133

135-
136134
if let Err(e) = install_components(&kube_client, &cluster).await {
137135
// warn with `:?` to also get context
138136
warn!("Installation of a component failed: {e:?}\nRequeueing...");
@@ -167,17 +165,18 @@ async fn install_trustee_configuration(
167165
.context("Failed to create the KBS configuration configmap")?;
168166
info!("Generated configmap for the KBS configuration");
169167

170-
trustee::generate_attestation_policy(client.clone(), owner_reference.clone())
168+
reference_values::create_pcrs_config_map(client.clone())
171169
.await
172-
.context("Failed to create the attestation policy configmap")?;
173-
info!("Generated configmap for the attestation policy");
174-
175-
match trustee::generate_trustee_auth_keys_secret(client.clone(), owner_reference.clone()).await
176-
{
177-
Ok(_) => info!("Generate auth keys for the KBS API",),
178-
Err(e) => error!("Failed to create the auth keys: {e}"),
179-
}
180-
170+
.context("Failed to create the PCRs configmap")?;
171+
info!("Created bare configmap for PCRs");
172+
trustee::generate_trustee_auth_keys_secret(client.clone(), owner_reference.clone())
173+
.await
174+
.context("Failed to create the auth keys")?;
175+
info!("Generate auth keys for the KBS API");
176+
trustee::generate_rv_data(client.clone(), owner_reference.clone())
177+
.await
178+
.context("Failed to create the reference values configmap")?;
179+
info!("Created configmap for reference values");
181180
let kbs_port = cluster.spec.trustee_kbs_port;
182181
trustee::generate_kbs_service(client.clone(), owner_reference.clone(), kbs_port)
183182
.await
@@ -253,7 +252,7 @@ async fn install_attestation_key_register(
253252
#[tokio::main]
254253
async fn main() -> Result<()> {
255254
env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
256-
255+
let _ = jsonwebtoken_openssl::install_default();
257256
let kube_client = Client::try_default().await?;
258257
info!("trusted execution clusters operator",);
259258

@@ -284,9 +283,9 @@ async fn main() -> Result<()> {
284283
attestation_key_register::launch_ak_controller(ak_ctx.clone()).await;
285284
attestation_key_register::launch_machine_ak_controller(ak_ctx.clone()).await;
286285
attestation_key_register::launch_secret_ak_controller(ak_ctx).await;
287-
reference_values::create_pcrs_config_map(kube_client.clone()).await?;
288286
reference_values::launch_rv_image_controller(kube_client.clone()).await;
289287
reference_values::launch_rv_job_controller(kube_client.clone()).await;
288+
trustee::launch_trustee_sync_controller(kube_client.clone()).await;
290289

291290
Controller::new(cl, watcher::Config::default())
292291
.run(reconcile, controller_error_policy, ctx)
@@ -300,7 +299,7 @@ async fn main() -> Result<()> {
300299
mod tests {
301300
use http::{Method, Request, StatusCode};
302301
use k8s_openapi::api::apps::v1::Deployment;
303-
use k8s_openapi::api::core::v1::{ConfigMap, Service};
302+
use k8s_openapi::api::core::v1::{ConfigMap, Secret, Service};
304303
use k8s_openapi::{apimachinery::pkg::apis::meta::v1::Time, jiff::Timestamp};
305304
use kube::client::Body;
306305

@@ -440,25 +439,26 @@ mod tests {
440439
};
441440

442441
let clos = async |req: Request<Body>, ctr| {
443-
if ctr < 8 && req.method() == Method::POST {
442+
if ctr < 10 && req.method() == Method::POST {
444443
use serde_json::to_string;
445444
let resp = match ctr {
446-
// Trustee
447-
0 => to_string(&ConfigMap::default()),
448-
1 => to_string(&ConfigMap::default()),
449-
2 => to_string(&Service::default()),
450-
3 => to_string(&Deployment::default()),
451-
// Registration server
452-
4 => to_string(&Deployment::default()),
453-
5 => to_string(&Service::default()),
454-
// Attestation key register server
455-
6 => to_string(&Deployment::default()),
456-
7 => to_string(&Service::default()),
445+
// install_trustee_configuration
446+
0 => to_string(&ConfigMap::default()), // trustee-data
447+
1 => to_string(&ConfigMap::default()), // image-pcrs
448+
2 => to_string(&Secret::default()), // trustee-auth
449+
3 => to_string(&ConfigMap::default()), // trustee-rv-data
450+
4 => to_string(&Service::default()), // kbs-service
451+
5 => to_string(&Deployment::default()), // trustee-deployment
452+
// install_register_server
453+
6 => to_string(&Deployment::default()), // register-server
454+
7 => to_string(&Service::default()), // register-server-svc
455+
// install_attestation_key_register
456+
8 => to_string(&Deployment::default()), // ak-register
457+
9 => to_string(&Service::default()), // ak-register-svc
457458
_ => unreachable!("unexpected counter {ctr}"),
458459
};
459460
Ok(resp.unwrap())
460-
461-
} else if ctr == 8 && req.method() == Method::PATCH {
461+
} else if ctr == 10 && req.method() == Method::PATCH {
462462
let body = req.into_body().collect_bytes().await.unwrap().to_vec();
463463
let body = String::from_utf8_lossy(&body);
464464
assert!(body.contains("ForeignCondition"),);
@@ -489,7 +489,7 @@ mod tests {
489489
cluster.status = Some(TrustedExecutionClusterStatus {
490490
conditions: Some(vec![pre_existing_installed, foreign_condition]),
491491
});
492-
count_check!(9, clos, |client| {
492+
count_check!(11, clos, |client| {
493493
let result = reconcile(Arc::new(cluster), Arc::new(dummy_cluster_ctx(client))).await;
494494
assert_eq!(result.unwrap(), Action::await_change());
495495
});

operator/src/reference_values.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,6 @@ async fn adopt_approved_image(
220220
Ok(())
221221
}
222222

223-
224223
async fn image_reconcile(
225224
image: Arc<ApprovedImage>,
226225
client: Arc<Client>,
@@ -479,12 +478,13 @@ mod tests {
479478
Ok(serde_json::to_string(&dummy_pcrs_map()).unwrap())
480479
}
481480
(2, &Method::GET) | (3, &Method::PUT) => {
482-
assert!(req.uri().path().contains(trustee::TRUSTEE_DATA_MAP));
481+
assert!(req.uri().path().contains(trustee::TRUSTEE_RV_MAP));
483482
Ok(serde_json::to_string(&dummy_trustee_map()).unwrap())
484483
}
484+
(4, &Method::GET) => Err(StatusCode::NOT_FOUND),
485485
_ => panic!("unexpected API interaction: {req:?}, counter {ctr}"),
486486
};
487-
count_check!(4, clos, |client| {
487+
count_check!(5, clos, |client| {
488488
let job = Arc::new(dummy_job());
489489
let result = job_reconcile(job, Arc::new(client)).await.unwrap();
490490
assert_eq!(result, Action::await_change());
@@ -551,7 +551,6 @@ mod tests {
551551
test_error_method!(clos, Method::PATCH);
552552
}
553553

554-
555554
// handle_new_image and its caller image_add_reconcile are
556555
// inherently online functions and not tested here
557556

@@ -566,12 +565,13 @@ mod tests {
566565
Ok(serde_json::to_string(&dummy_pcrs_map()).unwrap())
567566
}
568567
(3, &Method::GET) | (4, &Method::PUT) => {
569-
assert!(req.uri().path().contains(trustee::TRUSTEE_DATA_MAP));
568+
assert!(req.uri().path().contains(trustee::TRUSTEE_RV_MAP));
570569
Ok(serde_json::to_string(&dummy_trustee_map()).unwrap())
571570
}
571+
(5, &Method::GET) => Err(StatusCode::NOT_FOUND),
572572
_ => panic!("unexpected API interaction: {req:?}, counter {ctr}"),
573573
};
574-
count_check!(5, clos, |client| {
574+
count_check!(6, clos, |client| {
575575
assert!(image_remove_reconcile(client, image, cluster).await.is_ok());
576576
});
577577
}

operator/src/register_server.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ async fn keygen_reconcile(
139139
async {
140140
let owner_reference = generate_owner_reference(&Arc::unwrap_or_clone(machine))?;
141141
trustee::generate_secret(kube_client.clone(), id, owner_reference).await?;
142-
trustee::mount_secret(kube_client, id).await
142+
trustee::send_secret(kube_client, id).await
143143
}
144144
.await
145145
.map(|_| Action::await_change())
@@ -185,10 +185,12 @@ async fn keygen_reconcile(
185185
}
186186
}
187187

188-
trustee::unmount_secret(kube_client, id)
189-
.await
190-
.map(|_| Action::await_change())
191-
.map_err(|e| finalizer::Error::<ControllerError>::CleanupFailed(e.into()))
188+
trustee::delete_secret(kube_client, id).await.map_err(|e| {
189+
finalizer::Error::<ControllerError>::CleanupFailed(
190+
anyhow!("failed to delete secret for machine {id}: {e}").into(),
191+
)
192+
})?;
193+
Ok(Action::await_change())
192194
}
193195
}
194196
})

operator/src/test_utils.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
// SPDX-License-Identifier: MIT
44

55
use compute_pcrs_lib::Pcr;
6-
use k8s_openapi::{api::core::v1::ConfigMap, jiff::Timestamp};
6+
use k8s_openapi::{
7+
api::core::v1::{ConfigMap, Secret},
8+
jiff::Timestamp,
9+
};
710
use std::collections::BTreeMap;
811

912
use crate::trustee;
@@ -41,6 +44,26 @@ pub fn dummy_trustee_map() -> ConfigMap {
4144
}
4245
}
4346

47+
pub fn dummy_trustee_auth() -> Secret {
48+
let key_pair =
49+
trustee::generate_ed25519_key_pair().expect("Failed to generate ed25519 key pair");
50+
let data = BTreeMap::from([
51+
(
52+
trustee::TRUSTEE_AUTH_PRIV_KEY.to_string(),
53+
k8s_openapi::ByteString(key_pair.private_key_pem),
54+
),
55+
(
56+
trustee::TRUSTEE_AUTH_PUB_KEY.to_string(),
57+
k8s_openapi::ByteString(key_pair.public_key_pem),
58+
),
59+
]);
60+
61+
Secret {
62+
data: Some(data),
63+
..Default::default()
64+
}
65+
}
66+
4467
pub fn dummy_pcrs_map() -> ConfigMap {
4568
let data = BTreeMap::from([(
4669
PCR_CONFIG_FILE.to_string(),

operator/src/tpm.rego

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ executables := 3 if {
99
input.tpm.pcr04 in query_reference_value("tpm_pcr4")
1010
input.tpm.pcr14 in query_reference_value("tpm_pcr14")
1111

12+
input.tpm.ak_public in query_reference_value("trusted_aks")
1213
}
1314
# Azure SNP vTPM validation
1415
executables := 3 if {

0 commit comments

Comments
 (0)