Skip to content

Commit cc3c1d9

Browse files
feat: Support putting TrustStore information in Secret (#597)
* Refactor tests * feat: Support putting TrustStore information in Secret * changelog * outputResources -> targetKind * update docs * Add PascalCase attribute * docs: Add to concepts page * docs: Add crds link for TrustStore * Update docs/modules/secret-operator/pages/truststore.adoc Co-authored-by: Nick <[email protected]> --------- Co-authored-by: Nick <[email protected]>
1 parent 29651c0 commit cc3c1d9

38 files changed

+167
-67
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@ All notable changes to this project will be documented in this file.
44

55
## [Unreleased]
66

7+
### Added
8+
9+
- Support exporting the TrustStore CA certificate information to Secrets or ConfigMaps ([#597]).
10+
711
### Changed
812

913
- Version CRD structs and enums as v1alpha1 ([#636]).
1014

15+
[#597]: https://github.com/stackabletech/secret-operator/pull/597
1116
[#636]: https://github.com/stackabletech/secret-operator/pull/636
1217

1318
## [25.7.0] - 2025-07-23

deploy/helm/secret-operator/crds/crds.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,18 @@ spec:
395395
secretClassName:
396396
description: The name of the SecretClass that the request concerns.
397397
type: string
398+
targetKind:
399+
default: ConfigMap
400+
description: |-
401+
Which Kubernetes kind should be used to output the requested information to.
402+
403+
The trust information (such as a `ca.crt`) can be considered public information, so we put it in a `ConfigMap` by default. However, some tools might require it to be placed in a `Secret`, so we also support that.
404+
405+
Can be either `ConfigMap` or `Secret`, defaults to `ConfigMap`.
406+
enum:
407+
- Secret
408+
- ConfigMap
409+
type: string
398410
required:
399411
- secretClassName
400412
type: object

docs/modules/secret-operator/examples/truststore-tls.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ metadata:
66
spec:
77
secretClassName: tls # <2>
88
format: tls-pem # <3>
9+
targetKind: ConfigMap # <4>

docs/modules/secret-operator/pages/truststore.adoc

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ A TrustStore looks like this:
1212
include::example$truststore-tls.yaml[]
1313
----
1414
<1> Also used to name the created ConfigMap
15-
<2> The name of the xref:secretclass.adoc[]
16-
<3> The requested xref:secretclass.adoc#format[format]
15+
<2> Mandatory name of the xref:secretclass.adoc[]
16+
<3> Optional requested xref:secretclass.adoc#format[format]
17+
<4> Optional Kubernetes resource kind, which should be used to output the requested information to.
18+
Either `ConfigMap` or `Secret`, defaults to `ConfigMap`.
1719

18-
This will create a ConfigMap named `truststore-pem` containing a `ca.crt` with the trust root certificates.
20+
This will create a ConfigMap (or `Secret` based on `targetKind`) named `truststore-pem` containing a `ca.crt` with the trust root certificates.
1921
It can then either be mounted into a Pod or retrieved and used from outside of Kubernetes.
2022

2123
NOTE: Make sure to have a procedure for updating the retrieved certificates.

docs/modules/secret-operator/partials/nav.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@
1313
* xref:secret-operator:reference/index.adoc[]
1414
** xref:secret-operator:reference/crds.adoc[]
1515
*** {crd-docs}/secrets.stackable.tech/secretclass/v1alpha1/[SecretClass {external-link-icon}^]
16+
*** {crd-docs}/secrets.stackable.tech/truststore/v1alpha1/[TrustStore {external-link-icon}^]
1617
** xref:secret-operator:reference/commandline-parameters.adoc[]
1718
* xref:secret-operator:troubleshooting.adoc[]

rust/operator-binary/src/crd/mod.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,9 +314,28 @@ pub mod versioned {
314314
/// The name of the SecretClass that the request concerns.
315315
pub secret_class_name: String,
316316

317+
/// Which Kubernetes kind should be used to output the requested information to.
318+
///
319+
/// The trust information (such as a `ca.crt`) can be considered public information, so we put
320+
/// it in a `ConfigMap` by default. However, some tools might require it to be placed in a
321+
/// `Secret`, so we also support that.
322+
///
323+
/// Can be either `ConfigMap` or `Secret`, defaults to `ConfigMap`.
324+
#[serde(default)]
325+
pub target_kind: TrustStoreOutputType,
326+
317327
/// The [format](DOCS_BASE_URL_PLACEHOLDER/secret-operator/secretclass#format) that the data should be converted into.
318328
pub format: Option<SecretFormat>,
319329
}
330+
331+
#[derive(Clone, Debug, Default, PartialEq, JsonSchema, Serialize, Deserialize)]
332+
#[serde(rename_all = "PascalCase")]
333+
pub enum TrustStoreOutputType {
334+
Secret,
335+
336+
#[default]
337+
ConfigMap,
338+
}
320339
}
321340

322341
#[cfg(test)]

rust/operator-binary/src/truststore_controller.rs

Lines changed: 55 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@ pub async fn start(client: &stackable_operator::client::Client, watch_namespace:
8585
watch_namespace.get_api::<PartialObjectMeta<ConfigMap>>(client),
8686
watcher::Config::default(),
8787
)
88+
// TODO: merge this into the other Secret watch
89+
.owns(
90+
watch_namespace.get_api::<PartialObjectMeta<Secret>>(client),
91+
watcher::Config::default(),
92+
)
8893
.watches(
8994
watch_namespace.get_api::<PartialObjectMeta<ConfigMap>>(client),
9095
watcher::Config::default(),
@@ -212,7 +217,14 @@ pub enum Error {
212217
source: stackable_operator::client::Error,
213218
config_map: ObjectRef<ConfigMap>,
214219
},
220+
221+
#[snafu(display("failed to apply target {secret} for the TrustStore"))]
222+
ApplyTrustStoreSecret {
223+
source: stackable_operator::client::Error,
224+
secret: ObjectRef<Secret>,
225+
},
215226
}
227+
216228
type Result<T, E = Error> = std::result::Result<T, E>;
217229
impl ReconcilerError for Error {
218230
fn category(&self) -> &'static str {
@@ -229,6 +241,7 @@ impl ReconcilerError for Error {
229241
Error::FormatData { secret_class, .. } => Some(secret_class.clone().erase()),
230242
Error::BuildOwnerReference { .. } => None,
231243
Error::ApplyTrustStoreConfigMap { config_map, .. } => Some(config_map.clone().erase()),
244+
Error::ApplyTrustStoreSecret { secret, .. } => Some(secret.clone().erase()),
232245
}
233246
}
234247
}
@@ -271,7 +284,7 @@ async fn reconcile(
271284
.get_trust_data(&selector)
272285
.await
273286
.context(BackendGetTrustDataSnafu)?;
274-
let (Flattened(string_data), Flattened(binary_data)) = trust_data
287+
let trust_file_contents = trust_data
275288
.data
276289
.into_files(
277290
truststore.spec.format,
@@ -280,30 +293,53 @@ async fn reconcile(
280293
)
281294
.context(FormatDataSnafu {
282295
secret_class: secret_class_ref,
283-
})?
296+
})?;
297+
let (Flattened(string_data), Flattened(binary_data)) = trust_file_contents
284298
.into_iter()
285-
// Try to put valid UTF-8 data into `data`, but fall back to `binary_data` otherwise
299+
// Try to put valid UTF-8 data into `string_data`, but fall back to `binary_data` otherwise
286300
.map(|(k, v)| match String::from_utf8(v) {
287301
Ok(v) => (Some((k, v)), None),
288302
Err(v) => (None, Some((k, ByteString(v.into_bytes())))),
289303
})
290304
.collect();
291-
let trust_cm = ConfigMap {
292-
metadata: ObjectMetaBuilder::new()
293-
.name_and_namespace(truststore)
294-
.ownerreference_from_resource(truststore, None, Some(true))
295-
.context(BuildOwnerReferenceSnafu)?
296-
.build(),
297-
data: Some(string_data),
298-
binary_data: Some(binary_data),
299-
..Default::default()
300-
};
301-
ctx.client
302-
.apply_patch(CONTROLLER_NAME, &trust_cm, &trust_cm)
303-
.await
304-
.context(ApplyTrustStoreConfigMapSnafu {
305-
config_map: &trust_cm,
306-
})?;
305+
306+
let trust_metadata = ObjectMetaBuilder::new()
307+
.name_and_namespace(truststore)
308+
.ownerreference_from_resource(truststore, None, Some(true))
309+
.context(BuildOwnerReferenceSnafu)?
310+
.build();
311+
312+
match truststore.spec.target_kind {
313+
v1alpha1::TrustStoreOutputType::ConfigMap => {
314+
let trust_cm = ConfigMap {
315+
metadata: trust_metadata,
316+
data: Some(string_data),
317+
binary_data: Some(binary_data),
318+
..Default::default()
319+
};
320+
ctx.client
321+
.apply_patch(CONTROLLER_NAME, &trust_cm, &trust_cm)
322+
.await
323+
.context(ApplyTrustStoreConfigMapSnafu {
324+
config_map: &trust_cm,
325+
})?;
326+
}
327+
v1alpha1::TrustStoreOutputType::Secret => {
328+
let trust_secret = Secret {
329+
metadata: trust_metadata,
330+
string_data: Some(string_data),
331+
data: Some(binary_data),
332+
..Default::default()
333+
};
334+
ctx.client
335+
.apply_patch(CONTROLLER_NAME, &trust_secret, &trust_secret)
336+
.await
337+
.context(ApplyTrustStoreSecretSnafu {
338+
secret: &trust_secret,
339+
})?;
340+
}
341+
}
342+
307343
Ok(controller::Action::await_change())
308344
}
309345

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
apiVersion: kuttl.dev/v1beta1
3+
kind: TestStep
4+
commands:
5+
- script: envsubst '$NAMESPACE' < 02_secretclass.yaml | kubectl apply -f -

tests/templates/kuttl/cert-manager-tls/02-secretclass.yaml

Lines changed: 0 additions & 5 deletions
This file was deleted.
File renamed without changes.

0 commit comments

Comments
 (0)