Skip to content

Commit d39f8c7

Browse files
committed
fix: address review feedback
1 parent 940dde4 commit d39f8c7

1 file changed

Lines changed: 58 additions & 41 deletions

File tree

rust/operator-binary/src/truststore_controller.rs

Lines changed: 58 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,12 @@ pub enum Error {
199199
#[snafu(display("TrustStore has no associated Namespace"))]
200200
NoTrustStoreNamespace,
201201

202+
#[snafu(display("TrustStore has no associated name"))]
203+
NoTrustStoreName,
204+
205+
#[snafu(display("TrustStore has no associated UID"))]
206+
NoTrustStoreUid,
207+
202208
#[snafu(display("failed to convert trust data into desired format"))]
203209
FormatData {
204210
source: format::IntoFilesError,
@@ -236,12 +242,6 @@ pub enum Error {
236242
RefuseToOverwriteForeignTarget {
237243
object: ObjectRef<stackable_operator::kube::api::DynamicObject>,
238244
},
239-
240-
#[snafu(display("TrustStore has no associated UID"))]
241-
NoTrustStoreUid,
242-
243-
#[snafu(display("TrustStore has no associated name"))]
244-
NoTrustStoreName,
245245
}
246246

247247
type Result<T, E = Error> = std::result::Result<T, E>;
@@ -257,14 +257,14 @@ impl ReconcilerError for Error {
257257
Error::InitBackend { secret_class, .. } => Some(secret_class.clone().erase()),
258258
Error::BackendGetTrustData { source } => source.secondary_object(),
259259
Error::NoTrustStoreNamespace => None,
260+
Error::NoTrustStoreName => None,
261+
Error::NoTrustStoreUid => None,
260262
Error::FormatData { secret_class, .. } => Some(secret_class.clone().erase()),
261263
Error::BuildOwnerReference { .. } => None,
262264
Error::ApplyTrustStoreConfigMap { config_map, .. } => Some(config_map.clone().erase()),
263265
Error::ApplyTrustStoreSecret { secret, .. } => Some(secret.clone().erase()),
264266
Error::GetExistingTarget { object, .. } => Some(object.clone()),
265267
Error::RefuseToOverwriteForeignTarget { object } => Some(object.clone()),
266-
Error::NoTrustStoreUid => None,
267-
Error::NoTrustStoreName => None,
268268
}
269269
}
270270
}
@@ -275,14 +275,47 @@ impl ReconcilerError for Error {
275275
/// from being abused to clobber foreign same-named objects (e.g. `kube-root-ca.crt`) via the
276276
/// operator's elevated cluster-wide write permissions.
277277
fn is_owned_by_truststore(existing_owners: &[OwnerReference], truststore_uid: &str) -> bool {
278+
let truststore_kind = <v1alpha1::TrustStore as Resource>::kind(&());
278279
existing_owners.iter().any(|owner| {
279280
owner.controller == Some(true)
280-
&& owner.kind == "TrustStore"
281+
&& owner.kind == truststore_kind
281282
&& owner.api_version.starts_with("secrets.stackable.tech/")
282283
&& owner.uid == truststore_uid
283284
})
284285
}
285286

287+
/// Looks up a pre-existing object with the same name/namespace as the TrustStore output and fails
288+
/// if it exists but is not controlled by this TrustStore. See [`is_owned_by_truststore`] for the
289+
/// security rationale.
290+
async fn ensure_existing_target_is_not_foreign<K>(
291+
client: &stackable_operator::client::Client,
292+
name: &str,
293+
namespace: &str,
294+
truststore_uid: &str,
295+
object_ref: ObjectRef<stackable_operator::kube::api::DynamicObject>,
296+
) -> Result<()>
297+
where
298+
K: stackable_operator::kube::Resource<DynamicType = ()>
299+
+ stackable_operator::client::GetApi<Namespace = str>
300+
+ Clone
301+
+ std::fmt::Debug
302+
+ serde::de::DeserializeOwned,
303+
{
304+
let existing = client
305+
.get_opt::<K>(name, namespace)
306+
.await
307+
.with_context(|_| GetExistingTargetSnafu {
308+
object: object_ref.clone(),
309+
})?;
310+
if let Some(existing) = existing {
311+
let owners = existing.meta().owner_references.as_deref().unwrap_or(&[]);
312+
if !is_owned_by_truststore(owners, truststore_uid) {
313+
return RefuseToOverwriteForeignTargetSnafu { object: object_ref }.fail();
314+
}
315+
}
316+
Ok(())
317+
}
318+
286319
struct Ctx {
287320
client: stackable_operator::client::Client,
288321
}
@@ -371,22 +404,14 @@ async fn reconcile(
371404
binary_data: Some(binary_data),
372405
..Default::default()
373406
};
374-
let existing = ctx
375-
.client
376-
.get_opt::<ConfigMap>(truststore_name, truststore_namespace)
377-
.await
378-
.with_context(|_| GetExistingTargetSnafu {
379-
object: ObjectRef::from_obj(&trust_cm).erase(),
380-
})?;
381-
if let Some(existing) = existing {
382-
let owners = existing.metadata.owner_references.as_deref().unwrap_or(&[]);
383-
if !is_owned_by_truststore(owners, truststore_uid) {
384-
return RefuseToOverwriteForeignTargetSnafu {
385-
object: ObjectRef::from_obj(&trust_cm).erase(),
386-
}
387-
.fail();
388-
}
389-
}
407+
ensure_existing_target_is_not_foreign::<ConfigMap>(
408+
&ctx.client,
409+
truststore_name,
410+
truststore_namespace,
411+
truststore_uid,
412+
ObjectRef::from_obj(&trust_cm).erase(),
413+
)
414+
.await?;
390415
ctx.client
391416
.apply_patch(CONTROLLER_NAME, &trust_cm, &trust_cm)
392417
.await
@@ -401,22 +426,14 @@ async fn reconcile(
401426
data: Some(binary_data),
402427
..Default::default()
403428
};
404-
let existing = ctx
405-
.client
406-
.get_opt::<Secret>(truststore_name, truststore_namespace)
407-
.await
408-
.with_context(|_| GetExistingTargetSnafu {
409-
object: ObjectRef::from_obj(&trust_secret).erase(),
410-
})?;
411-
if let Some(existing) = existing {
412-
let owners = existing.metadata.owner_references.as_deref().unwrap_or(&[]);
413-
if !is_owned_by_truststore(owners, truststore_uid) {
414-
return RefuseToOverwriteForeignTargetSnafu {
415-
object: ObjectRef::from_obj(&trust_secret).erase(),
416-
}
417-
.fail();
418-
}
419-
}
429+
ensure_existing_target_is_not_foreign::<Secret>(
430+
&ctx.client,
431+
truststore_name,
432+
truststore_namespace,
433+
truststore_uid,
434+
ObjectRef::from_obj(&trust_secret).erase(),
435+
)
436+
.await?;
420437
ctx.client
421438
.apply_patch(CONTROLLER_NAME, &trust_secret, &trust_secret)
422439
.await

0 commit comments

Comments
 (0)