Skip to content

feat: Support putting TrustStore information in Secret #597

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

### Added

- Support exporting the TrustStore CA certificate information to Secrets or ConfigMaps ([#597]).

[#597]: https://github.com/stackabletech/secret-operator/pull/597

## [25.7.0] - 2025-07-23

## [25.7.0-rc1] - 2025-07-18
Expand Down
12 changes: 12 additions & 0 deletions deploy/helm/secret-operator/crds/crds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,18 @@ spec:
secretClassName:
description: The name of the SecretClass that the request concerns.
type: string
targetKind:
default: ConfigMap
description: |-
Which Kubernetes kind should be used to output the requested information to.

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.

Can be either `ConfigMap` or `Secret`, defaults to `ConfigMap`.
enum:
- Secret
- ConfigMap
type: string
required:
- secretClassName
type: object
Expand Down
19 changes: 19 additions & 0 deletions rust/operator-binary/src/crd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -518,10 +518,29 @@ pub struct TrustStoreSpec {
/// The name of the SecretClass that the request concerns.
pub secret_class_name: String,

/// Which Kubernetes kind should be used to output the requested information to.
///
/// 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.
///
/// Can be either `ConfigMap` or `Secret`, defaults to `ConfigMap`.
#[serde(default)]
pub target_kind: TrustStoreOutputType,

/// The [format](DOCS_BASE_URL_PLACEHOLDER/secret-operator/secretclass#format) that the data should be converted into.
pub format: Option<SecretFormat>,
}

#[derive(Clone, Debug, Default, PartialEq, JsonSchema, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub enum TrustStoreOutputType {
Secret,

#[default]
ConfigMap,
}

#[cfg(test)]
mod test {
use super::*;
Expand Down
76 changes: 56 additions & 20 deletions rust/operator-binary/src/truststore_controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use strum::{EnumDiscriminants, IntoStaticStr};
use crate::{
OPERATOR_NAME,
backend::{self, SecretBackendError, TrustSelector},
crd::{SearchNamespaceMatchCondition, SecretClass, TrustStore},
crd::{SearchNamespaceMatchCondition, SecretClass, TrustStore, TrustStoreOutputType},
format::{
self,
well_known::{CompatibilityOptions, NamingOptions},
Expand Down Expand Up @@ -85,6 +85,11 @@ pub async fn start(client: &stackable_operator::client::Client, watch_namespace:
watch_namespace.get_api::<PartialObjectMeta<ConfigMap>>(client),
watcher::Config::default(),
)
// TODO: merge this into the other Secret watch
.owns(
watch_namespace.get_api::<PartialObjectMeta<Secret>>(client),
watcher::Config::default(),
)
.watches(
watch_namespace.get_api::<PartialObjectMeta<ConfigMap>>(client),
watcher::Config::default(),
Expand Down Expand Up @@ -208,7 +213,14 @@ pub enum Error {
source: stackable_operator::client::Error,
config_map: ObjectRef<ConfigMap>,
},

#[snafu(display("failed to apply target {secret} for the TrustStore"))]
ApplyTrustStoreSecret {
source: stackable_operator::client::Error,
secret: ObjectRef<Secret>,
},
}

type Result<T, E = Error> = std::result::Result<T, E>;
impl ReconcilerError for Error {
fn category(&self) -> &'static str {
Expand All @@ -225,6 +237,7 @@ impl ReconcilerError for Error {
Error::FormatData { secret_class, .. } => Some(secret_class.clone().erase()),
Error::BuildOwnerReference { .. } => None,
Error::ApplyTrustStoreConfigMap { config_map, .. } => Some(config_map.clone().erase()),
Error::ApplyTrustStoreSecret { secret, .. } => Some(secret.clone().erase()),
}
}
}
Expand Down Expand Up @@ -267,7 +280,7 @@ async fn reconcile(
.get_trust_data(&selector)
.await
.context(BackendGetTrustDataSnafu)?;
let (Flattened(string_data), Flattened(binary_data)) = trust_data
let trust_file_contents = trust_data
.data
.into_files(
truststore.spec.format,
Expand All @@ -276,30 +289,53 @@ async fn reconcile(
)
.context(FormatDataSnafu {
secret_class: secret_class_ref,
})?
})?;
let (Flattened(string_data), Flattened(binary_data)) = trust_file_contents
.into_iter()
// Try to put valid UTF-8 data into `data`, but fall back to `binary_data` otherwise
// Try to put valid UTF-8 data into `string_data`, but fall back to `binary_data` otherwise
.map(|(k, v)| match String::from_utf8(v) {
Ok(v) => (Some((k, v)), None),
Err(v) => (None, Some((k, ByteString(v.into_bytes())))),
})
.collect();
let trust_cm = ConfigMap {
metadata: ObjectMetaBuilder::new()
.name_and_namespace(truststore)
.ownerreference_from_resource(truststore, None, Some(true))
.context(BuildOwnerReferenceSnafu)?
.build(),
data: Some(string_data),
binary_data: Some(binary_data),
..Default::default()
};
ctx.client
.apply_patch(CONTROLLER_NAME, &trust_cm, &trust_cm)
.await
.context(ApplyTrustStoreConfigMapSnafu {
config_map: &trust_cm,
})?;

let trust_metadata = ObjectMetaBuilder::new()
.name_and_namespace(truststore)
.ownerreference_from_resource(truststore, None, Some(true))
.context(BuildOwnerReferenceSnafu)?
.build();

match truststore.spec.target_kind {
TrustStoreOutputType::ConfigMap => {
let trust_cm = ConfigMap {
metadata: trust_metadata,
data: Some(string_data),
binary_data: Some(binary_data),
..Default::default()
};
ctx.client
.apply_patch(CONTROLLER_NAME, &trust_cm, &trust_cm)
.await
.context(ApplyTrustStoreConfigMapSnafu {
config_map: &trust_cm,
})?;
}
TrustStoreOutputType::Secret => {
let trust_secret = Secret {
metadata: trust_metadata,
string_data: Some(string_data),
data: Some(binary_data),
..Default::default()
};
ctx.client
.apply_patch(CONTROLLER_NAME, &trust_secret, &trust_secret)
.await
.context(ApplyTrustStoreSecretSnafu {
secret: &trust_secret,
})?;
}
}

Ok(controller::Action::await_change())
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
apiVersion: kuttl.dev/v1beta1
kind: TestStep
commands:
- script: envsubst '$NAMESPACE' < 02_secretclass.yaml | kubectl apply -f -
5 changes: 0 additions & 5 deletions tests/templates/kuttl/cert-manager-tls/02-secretclass.yaml

This file was deleted.

5 changes: 0 additions & 5 deletions tests/templates/kuttl/cert-manager-tls/10-consumer.yaml

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
apiVersion: kuttl.dev/v1beta1
kind: TestStep
commands:
- script: envsubst '$NAMESPACE' < 10_consumer.yaml | kubectl apply -n $NAMESPACE -f -
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
commands:
- script: envsubst '$NAMESPACE' < secretclass.yaml | kubectl apply -f -
- script: envsubst '$NAMESPACE' < 01_secretclass.yaml | kubectl apply -f -
---
apiVersion: v1
kind: Secret
Expand Down
2 changes: 1 addition & 1 deletion tests/templates/kuttl/kerberos-ad/02-kinit-client.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
commands:
- script: envsubst '$NAMESPACE' < kinit-client.yaml | kubectl apply -n $NAMESPACE -f -
- script: envsubst '$NAMESPACE' < 02_kinit-client.yaml | kubectl apply -n $NAMESPACE -f -
---
apiVersion: v1
kind: Service
Expand Down
4 changes: 2 additions & 2 deletions tests/templates/kuttl/kerberos/01-install-kdc.yaml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
commands:
- script: envsubst '$NAMESPACE' < secretclass.yaml | kubectl apply -f -
- script: envsubst '$NAMESPACE' < listenerclass.yaml | kubectl apply -f -
- script: envsubst '$NAMESPACE' < 01_secretclass.yaml | kubectl apply -f -
- script: envsubst '$NAMESPACE' < 01_listenerclass.yaml | kubectl apply -f -
---
apiVersion: apps/v1
kind: StatefulSet
Expand Down
2 changes: 1 addition & 1 deletion tests/templates/kuttl/kerberos/02-kinit-client.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
commands:
- script: envsubst '$NAMESPACE' < kinit-client.yaml | kubectl apply -n $NAMESPACE -f -
- script: envsubst '$NAMESPACE' < 02_kinit-client.yaml | kubectl apply -n $NAMESPACE -f -
---
apiVersion: v1
kind: Service
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
apiVersion: kuttl.dev/v1beta1
kind: TestStep
commands:
- script: envsubst '$NAMESPACE' < 01_secretclass.yaml | kubectl --namespace=$NAMESPACE apply -f -
5 changes: 0 additions & 5 deletions tests/templates/kuttl/tls-truststore/01-secretclass.yaml

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@ kind: TestAssert
timeout: 5
---
apiVersion: v1
kind: ConfigMap
kind: {{ test_scenario['values']['truststore-target-kind'] }}
metadata:
name: truststore-pem
# data is validated in 03-assert.yaml
---
apiVersion: v1
kind: ConfigMap
kind: {{ test_scenario['values']['truststore-target-kind'] }}
metadata:
name: truststore-pkcs12
# data is validated in 03-assert.yaml
---
{% if test_scenario['values']['truststore-target-kind'] == 'ConfigMap' %}
apiVersion: v1
kind: ConfigMap
metadata:
Expand All @@ -26,3 +27,13 @@ data:
binaryData:
# Should stay binary since it is not legal UTF-8
actuallyBinary: aWxsZWdhbIB1dGYtOA==
{% else %}
apiVersion: v1
kind: Secret
metadata:
name: truststore-k8ssearch
data:
foo: YmFy
baz: aGVsbG8=
actuallyBinary: aWxsZWdhbIB1dGYtOA==
{% endif %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
apiVersion: kuttl.dev/v1beta1
kind: TestStep
commands:
- script: envsubst '$NAMESPACE' < 02_truststore.yaml | kubectl --namespace=$NAMESPACE apply -f -
5 changes: 0 additions & 5 deletions tests/templates/kuttl/tls-truststore/02-truststore.yaml

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ metadata:
spec:
secretClassName: tls-$NAMESPACE
format: tls-pem
targetKind: {{ test_scenario['values']['truststore-target-kind'] }}
---
apiVersion: secrets.stackable.tech/v1alpha1
kind: TrustStore
Expand All @@ -15,10 +16,12 @@ metadata:
spec:
secretClassName: tls-$NAMESPACE
format: tls-pkcs12
targetKind: {{ test_scenario['values']['truststore-target-kind'] }}
---
apiVersion: secrets.stackable.tech/v1alpha1
kind: TrustStore
metadata:
name: truststore-k8ssearch
spec:
secretClassName: k8ssearch-$NAMESPACE
targetKind: {{ test_scenario['values']['truststore-target-kind'] }}
8 changes: 0 additions & 8 deletions tests/templates/kuttl/tls-truststore/03-assert.yaml

This file was deleted.

13 changes: 13 additions & 0 deletions tests/templates/kuttl/tls-truststore/03-assert.yaml.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Validate certificates generated by step 02
---
apiVersion: kuttl.dev/v1beta1
kind: TestAssert
timeout: 10
commands:
{% if test_scenario['values']['truststore-target-kind'] == 'ConfigMap' %}
- script: kubectl --namespace=$NAMESPACE get configmap/truststore-pem --output=jsonpath='{.data.ca\.crt}' | openssl x509 -noout
- script: kubectl --namespace=$NAMESPACE get configmap/truststore-pkcs12 --output=jsonpath='{.binaryData.truststore\.p12}' | base64 --decode | openssl pkcs12 -noout -passin 'pass:' -legacy
{% else %}
- script: kubectl --namespace=$NAMESPACE get secret/truststore-pem --output=jsonpath='{.data.ca\.crt}' | base64 --decode | openssl x509 -noout
- script: kubectl --namespace=$NAMESPACE get secret/truststore-pkcs12 --output=jsonpath='{.data.truststore\.p12}' | base64 --decode | openssl pkcs12 -noout -passin 'pass:' -legacy
{% endif %}
5 changes: 5 additions & 0 deletions tests/templates/kuttl/tls/01-create-secretclass.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
apiVersion: kuttl.dev/v1beta1
kind: TestStep
commands:
- script: envsubst '$NAMESPACE' < 01_secretclass.yaml | kubectl apply -f -
5 changes: 0 additions & 5 deletions tests/templates/kuttl/tls/01-secretclass.yaml

This file was deleted.

5 changes: 0 additions & 5 deletions tests/templates/kuttl/tls/10-consumer.yaml

This file was deleted.

5 changes: 5 additions & 0 deletions tests/templates/kuttl/tls/10-create-consumer.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
apiVersion: kuttl.dev/v1beta1
kind: TestStep
commands:
- script: envsubst '$NAMESPACE' < 10_consumer.yaml | kubectl apply -n $NAMESPACE -f -
5 changes: 5 additions & 0 deletions tests/test-definition.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,16 @@
values:
- false
- true
- name: truststore-target-kind
values:
- ConfigMap
- Secret
tests:
- name: kerberos
dimensions:
- krb5
- openshift
# Requires manual connection to an AD cluster

Check warning on line 31 in tests/test-definition.yaml

View workflow job for this annotation

GitHub Actions / pre-commit

31:5 [comments-indentation] comment not indented like content
# - name: kerberos-ad
# dimensions:
# - krb5
Expand All @@ -40,6 +44,7 @@
- name: tls-truststore
dimensions:
- openshift
- truststore-target-kind
- name: cert-manager-tls
dimensions:
- openshift
Expand Down
Loading