Skip to content

Commit 051b57c

Browse files
committed
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 <jnaucke@redhat.com>
1 parent 2e8b73f commit 051b57c

17 files changed

Lines changed: 474 additions & 339 deletions

File tree

.github/workflows/rust.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ jobs:
4646
- name: "cargo build"
4747
run: cargo build --all-targets
4848
- name: "cargo test"
49-
run: cargo test --bins
49+
run: cargo test --bins --lib
5050
tests-release-stable:
5151
name: "Tests (release), stable toolchain"
5252
runs-on: "ubuntu-24.04"
@@ -70,7 +70,7 @@ jobs:
7070
- name: "cargo build (release)"
7171
run: cargo build --lib --release
7272
- name: "cargo test (release)"
73-
run: cargo test --bins --release
73+
run: cargo test --bins --lib --release
7474
tests-release-msrv:
7575
name: "Tests (release), minimum supported toolchain"
7676
runs-on: "ubuntu-24.04"
@@ -100,7 +100,7 @@ jobs:
100100
- name: "cargo build (release)"
101101
run: cargo build --lib --release
102102
- name: "cargo test (release)"
103-
run: cargo test --bins --release
103+
run: cargo test --bins --lib --release
104104
tests-other-channels:
105105
name: "Tests, unstable toolchain"
106106
runs-on: "ubuntu-24.04"
@@ -128,4 +128,4 @@ jobs:
128128
- name: "cargo build"
129129
run: cargo build --lib
130130
- name: "cargo test"
131-
run: cargo test --bins
131+
run: cargo test --bins --lib

Cargo.lock

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

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,10 +195,10 @@ equal-conditions:
195195
lint: fmt-check clippy vet equal-conditions
196196

197197
test: crds-rs
198-
cargo test --workspace --bins
198+
cargo test --workspace --bins --lib
199199

200200
test-release: crds-rs
201-
cargo test --workspace --bins --release
201+
cargo test --workspace --bins --lib --release
202202

203203
ENABLE_ATTESTATION_KEY_REGISTRATION ?= true
204204

docs/design/reference-values.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,16 @@ A reference value listing for Trustee could then look like this:
9393
## Data flow
9494

9595
![](../pics/rv-flow.png)
96+
97+
## Ownership
98+
99+
Unlike `reference-values`, `ApprovedImages` can live independent of a `TrustedExecutionCluster` object.
100+
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`.
101+
102+
However, the `ApprovedImages` are adopted by the `TrustedExecutionCluster` object, both when created with a `TrustedExecutionCluster` existing and retroactively when created before `TrustedExecutionCluster` creation.
103+
This ensures that removal of a `TrustedExecutionCluster` acts as complete uninstallation.
104+
Finalizers on the `ApprovedImages` ensure the PCR values are removed back out of `image-pcrs` again.
105+
106+
## Ownership flow
107+
108+
![](../pics/image-flow.png)

docs/pics/image-flow.png

126 KB
Loading

docs/usage/os-and-node-lifecycle.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ Machines booting this image can now register and attest.
5252

5353
**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.
5454

55+
**NB:** The TrustedExecutionCluster object adopts ApprovedImages, including those that lived before it.
56+
This ensures that removal of a TrustedExecutionCluster acts as complete uninstallation.
57+
5558
# Disallowing a bootable container image
5659

5760
For the example above:

lib/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,6 @@ serde_json.workspace = true
2121
[dev-dependencies]
2222
# Only a generate dependency, not a Rust dependency. Included here for auto-updates.
2323
kopium = "0.23.0"
24+
http.workspace = true
25+
tokio.workspace = true
26+
trusted-cluster-operator-test-utils = { path = "../test_utils" }

lib/src/lib.rs

Lines changed: 101 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ pub use kopium::trustedexecutionclusters::*;
1919
pub use vendor_kopium::virtualmachineinstances;
2020
pub use vendor_kopium::virtualmachines;
2121

22-
use anyhow::Context;
22+
use anyhow::{Context, Result, anyhow};
2323
use conditions::*;
2424
use k8s_openapi::apimachinery::pkg::apis::meta::v1::{Condition, OwnerReference, Time};
25-
use kube::Resource;
25+
use kube::{Api, Client, Resource};
2626

2727
#[macro_export]
2828
macro_rules! update_status {
@@ -63,7 +63,7 @@ pub fn committed_condition(reason: &str, generation: Option<i64>) -> Condition {
6363
/// Generate an OwnerReference for any Kubernetes resource
6464
pub fn generate_owner_reference<T: Resource<DynamicType = ()>>(
6565
object: &T,
66-
) -> anyhow::Result<OwnerReference> {
66+
) -> Result<OwnerReference> {
6767
let name = object.meta().name.clone();
6868
let uid = object.meta().uid.clone();
6969
let kind = T::kind(&()).to_string();
@@ -77,32 +77,108 @@ pub fn generate_owner_reference<T: Resource<DynamicType = ()>>(
7777
})
7878
}
7979

80-
/// Get the single TrustedExecutionCluster in the namespace
81-
///
82-
/// Returns an error if:
83-
/// - No TrustedExecutionCluster is found
84-
/// - More than one TrustedExecutionCluster is found (not supported)
85-
pub async fn get_trusted_execution_cluster(
86-
client: kube::Client,
87-
) -> anyhow::Result<TrustedExecutionCluster> {
88-
use kube::Api;
89-
80+
pub async fn get_opt_trusted_execution_cluster(
81+
client: Client,
82+
) -> Result<Option<TrustedExecutionCluster>> {
9083
let namespace = client.default_namespace().to_string();
9184
let clusters: Api<TrustedExecutionCluster> = Api::default_namespaced(client);
92-
let params = Default::default();
93-
let mut list = clusters.list(&params).await?;
94-
95-
if list.items.is_empty() {
96-
return Err(anyhow::Error::msg(format!(
97-
"No TrustedExecutionCluster found in namespace {namespace}. \
98-
Ensure that this service is in the same namespace as the TrustedExecutionCluster."
99-
)));
100-
} else if list.items.len() > 1 {
101-
return Err(anyhow::Error::msg(format!(
85+
let list = clusters.list(&Default::default()).await?;
86+
if list.items.len() > 1 {
87+
return Err(anyhow!(
10288
"More than one TrustedExecutionCluster found in namespace {namespace}. \
10389
trusted-cluster-operator does not support more than one TrustedExecutionCluster."
104-
)));
90+
));
91+
}
92+
Ok(list.items.into_iter().next())
93+
}
94+
95+
/// Get the single TrustedExecutionCluster in the namespace
96+
pub async fn get_trusted_execution_cluster(client: Client) -> Result<TrustedExecutionCluster> {
97+
let namespace = client.default_namespace().to_string();
98+
let cluster = get_opt_trusted_execution_cluster(client).await;
99+
let err = anyhow!(
100+
"No TrustedExecutionCluster found in namespace {namespace}. \
101+
Ensure that this service is in the same namespace as the TrustedExecutionCluster."
102+
);
103+
cluster.and_then(|c| c.ok_or(err))
104+
}
105+
106+
#[cfg(test)]
107+
mod tests {
108+
use super::*;
109+
use http::StatusCode;
110+
use kube::api::ObjectList;
111+
use trusted_cluster_operator_test_utils::mock_client::*;
112+
113+
#[tokio::test]
114+
async fn test_get_some_trusted_execution_cluster() {
115+
let clos = async |_, _| {
116+
let object_list = ObjectList {
117+
items: vec![dummy_cluster()],
118+
types: Default::default(),
119+
metadata: Default::default(),
120+
};
121+
Ok(serde_json::to_string(&object_list).unwrap())
122+
};
123+
count_check!(1, clos, |client| {
124+
let res = get_opt_trusted_execution_cluster(client).await;
125+
assert!(res.unwrap().is_some());
126+
});
105127
}
106128

107-
Ok(list.items.pop().unwrap())
129+
#[tokio::test]
130+
async fn test_get_none_trusted_execution_cluster() {
131+
let clos = async |_, _| {
132+
let object_list = ObjectList::<TrustedExecutionCluster> {
133+
items: vec![],
134+
types: Default::default(),
135+
metadata: Default::default(),
136+
};
137+
Ok(serde_json::to_string(&object_list).unwrap())
138+
};
139+
count_check!(1, clos, |client| {
140+
let res = get_opt_trusted_execution_cluster(client).await;
141+
assert!(res.unwrap().is_none());
142+
});
143+
}
144+
145+
#[tokio::test]
146+
async fn test_non_unique_trusted_execution_cluster() {
147+
let clos = async |_, _| {
148+
let object_list = ObjectList {
149+
items: vec![dummy_cluster(), dummy_cluster()],
150+
types: Default::default(),
151+
metadata: Default::default(),
152+
};
153+
Ok(serde_json::to_string(&object_list).unwrap())
154+
};
155+
count_check!(1, clos, |client| {
156+
let err = get_opt_trusted_execution_cluster(client).await.unwrap_err();
157+
assert!(err.to_string().contains("More than one"));
158+
});
159+
}
160+
161+
#[tokio::test]
162+
async fn test_get_opt_trusted_execution_cluster_error() {
163+
let clos = async |_, _| Err(StatusCode::INTERNAL_SERVER_ERROR);
164+
count_check!(1, clos, |client| {
165+
assert!(get_opt_trusted_execution_cluster(client).await.is_err());
166+
});
167+
}
168+
169+
#[tokio::test]
170+
async fn test_get_no_trusted_execution_cluster() {
171+
let clos = async |_, _| {
172+
let object_list = ObjectList::<TrustedExecutionCluster> {
173+
items: vec![],
174+
types: Default::default(),
175+
metadata: Default::default(),
176+
};
177+
Ok(serde_json::to_string(&object_list).unwrap())
178+
};
179+
count_check!(1, clos, |client| {
180+
let err = get_trusted_execution_cluster(client).await.unwrap_err();
181+
assert!(err.to_string().contains("No TrustedExecutionCluster found"));
182+
});
183+
}
108184
}

operator/src/lib.rs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,14 @@
88
//
99
// Use in other crates is not an intended purpose.
1010

11-
use k8s_openapi::apimachinery::pkg::apis::meta::v1::OwnerReference;
12-
use kube::{Client, runtime::controller::Action};
11+
use kube::runtime::controller::Action;
1312
use log::info;
1413
use std::fmt::{Debug, Display};
1514
use std::{sync::Arc, time::Duration};
1615

1716
// Re-export common functions from the lib
1817
pub use trusted_cluster_operator_lib::generate_owner_reference;
1918

20-
#[derive(Clone)]
21-
pub struct RvContextData {
22-
pub client: Client,
23-
pub owner_reference: OwnerReference,
24-
pub pcrs_compute_image: String,
25-
}
26-
2719
#[derive(Debug, thiserror::Error)]
2820
pub enum ControllerError {
2921
#[error("{0}")]

0 commit comments

Comments
 (0)