Skip to content

Commit 145e363

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 266585b commit 145e363

17 files changed

Lines changed: 476 additions & 349 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
@@ -202,10 +202,10 @@ equal-conditions:
202202
lint: fmt-check clippy vet equal-conditions
203203

204204
test: crds-rs
205-
cargo test --workspace --bins
205+
cargo test --workspace --bins --lib
206206

207207
test-release: crds-rs
208-
cargo test --workspace --bins --release
208+
cargo test --workspace --bins --lib --release
209209

210210
integration-tests: generate trusted-cluster-gen crds-rs
211211
RUST_LOG=info REGISTRY=$(REGISTRY) TAG=$(TAG) \

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 {
@@ -105,7 +105,7 @@ pub fn committed_condition(
105105
/// Generate an OwnerReference for any Kubernetes resource
106106
pub fn generate_owner_reference<T: Resource<DynamicType = ()>>(
107107
object: &T,
108-
) -> anyhow::Result<OwnerReference> {
108+
) -> Result<OwnerReference> {
109109
let name = object.meta().name.clone();
110110
let uid = object.meta().uid.clone();
111111
let kind = T::kind(&()).to_string();
@@ -119,32 +119,108 @@ pub fn generate_owner_reference<T: Resource<DynamicType = ()>>(
119119
})
120120
}
121121

122-
/// Get the single TrustedExecutionCluster in the namespace
123-
///
124-
/// Returns an error if:
125-
/// - No TrustedExecutionCluster is found
126-
/// - More than one TrustedExecutionCluster is found (not supported)
127-
pub async fn get_trusted_execution_cluster(
128-
client: kube::Client,
129-
) -> anyhow::Result<TrustedExecutionCluster> {
130-
use kube::Api;
131-
122+
pub async fn get_opt_trusted_execution_cluster(
123+
client: Client,
124+
) -> Result<Option<TrustedExecutionCluster>> {
132125
let namespace = client.default_namespace().to_string();
133126
let clusters: Api<TrustedExecutionCluster> = Api::default_namespaced(client);
134-
let params = Default::default();
135-
let mut list = clusters.list(&params).await?;
136-
137-
if list.items.is_empty() {
138-
return Err(anyhow::Error::msg(format!(
139-
"No TrustedExecutionCluster found in namespace {namespace}. \
140-
Ensure that this service is in the same namespace as the TrustedExecutionCluster."
141-
)));
142-
} else if list.items.len() > 1 {
143-
return Err(anyhow::Error::msg(format!(
127+
let list = clusters.list(&Default::default()).await?;
128+
if list.items.len() > 1 {
129+
return Err(anyhow!(
144130
"More than one TrustedExecutionCluster found in namespace {namespace}. \
145131
trusted-cluster-operator does not support more than one TrustedExecutionCluster."
146-
)));
132+
));
133+
}
134+
Ok(list.items.into_iter().next())
135+
}
136+
137+
/// Get the single TrustedExecutionCluster in the namespace
138+
pub async fn get_trusted_execution_cluster(client: Client) -> Result<TrustedExecutionCluster> {
139+
let namespace = client.default_namespace().to_string();
140+
let cluster = get_opt_trusted_execution_cluster(client).await;
141+
let err = anyhow!(
142+
"No TrustedExecutionCluster found in namespace {namespace}. \
143+
Ensure that this service is in the same namespace as the TrustedExecutionCluster."
144+
);
145+
cluster.and_then(|c| c.ok_or(err))
146+
}
147+
148+
#[cfg(test)]
149+
mod tests {
150+
use super::*;
151+
use http::StatusCode;
152+
use kube::api::ObjectList;
153+
use trusted_cluster_operator_test_utils::mock_client::*;
154+
155+
#[tokio::test]
156+
async fn test_get_some_trusted_execution_cluster() {
157+
let clos = async |_, _| {
158+
let object_list = ObjectList {
159+
items: vec![dummy_cluster()],
160+
types: Default::default(),
161+
metadata: Default::default(),
162+
};
163+
Ok(serde_json::to_string(&object_list).unwrap())
164+
};
165+
count_check!(1, clos, |client| {
166+
let res = get_opt_trusted_execution_cluster(client).await;
167+
assert!(res.unwrap().is_some());
168+
});
169+
}
170+
171+
#[tokio::test]
172+
async fn test_get_none_trusted_execution_cluster() {
173+
let clos = async |_, _| {
174+
let object_list = ObjectList::<TrustedExecutionCluster> {
175+
items: vec![],
176+
types: Default::default(),
177+
metadata: Default::default(),
178+
};
179+
Ok(serde_json::to_string(&object_list).unwrap())
180+
};
181+
count_check!(1, clos, |client| {
182+
let res = get_opt_trusted_execution_cluster(client).await;
183+
assert!(res.unwrap().is_none());
184+
});
185+
}
186+
187+
#[tokio::test]
188+
async fn test_non_unique_trusted_execution_cluster() {
189+
let clos = async |_, _| {
190+
let object_list = ObjectList {
191+
items: vec![dummy_cluster(), dummy_cluster()],
192+
types: Default::default(),
193+
metadata: Default::default(),
194+
};
195+
Ok(serde_json::to_string(&object_list).unwrap())
196+
};
197+
count_check!(1, clos, |client| {
198+
let err = get_opt_trusted_execution_cluster(client).await.unwrap_err();
199+
assert!(err.to_string().contains("More than one"));
200+
});
201+
}
202+
203+
#[tokio::test]
204+
async fn test_get_opt_trusted_execution_cluster_error() {
205+
let clos = async |_, _| Err(StatusCode::INTERNAL_SERVER_ERROR);
206+
count_check!(1, clos, |client| {
207+
assert!(get_opt_trusted_execution_cluster(client).await.is_err());
208+
});
147209
}
148210

149-
Ok(list.items.pop().unwrap())
211+
#[tokio::test]
212+
async fn test_get_no_trusted_execution_cluster() {
213+
let clos = async |_, _| {
214+
let object_list = ObjectList::<TrustedExecutionCluster> {
215+
items: vec![],
216+
types: Default::default(),
217+
metadata: Default::default(),
218+
};
219+
Ok(serde_json::to_string(&object_list).unwrap())
220+
};
221+
count_check!(1, clos, |client| {
222+
let err = get_trusted_execution_cluster(client).await.unwrap_err();
223+
assert!(err.to_string().contains("No TrustedExecutionCluster found"));
224+
});
225+
}
150226
}

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)