@@ -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
247247type 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.
277277fn 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+
286319struct 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