Skip to content

Commit bb4dece

Browse files
feat: Support DCs in the subject DN of TLS certificates (#708)
* feat: Add domain components to the subject DN if enabled * test(tls): Add a test case for a subject DN with domain components * chore: Update changelog * chore: Fix typo in the changelog * docs: Document the annotation "secrets.stackable.tech/backend.autotls.cert.domain-components-in-subject-dn" * docs: Improve the documentation of the annotation "secrets.stackable.tech/backend.autotls.cert.domain-components-in-subject-dn" * doc: Remove unnecessary empty line * Apply suggestion from @Techassi Co-authored-by: Techassi <git@techassi.dev> --------- Co-authored-by: Techassi <git@techassi.dev>
1 parent 66cacc7 commit bb4dece

6 files changed

Lines changed: 113 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ All notable changes to this project will be documented in this file.
88

99
- Support configuring the name of the key in the ConfigMap/Secret, in which the PEM encoded CA certificate of the Truststore should be placed.
1010
This is e.g. needed to be able to use the generated Secret within an OpenShift Ingress ([#679]).
11+
- Support adding domain components to the subject DN of TLS certificates with the volume annotation
12+
`secrets.stackable.tech/backend.autotls.cert.domain-components-in-subject-dn` ([#708]).
1113

1214
### Changed
1315

@@ -21,6 +23,7 @@ All notable changes to this project will be documented in this file.
2123
[#693]: https://github.com/stackabletech/secret-operator/pull/693
2224
[#706]: https://github.com/stackabletech/secret-operator/pull/706
2325
[#707]: https://github.com/stackabletech/secret-operator/pull/707
26+
[#708]: https://github.com/stackabletech/secret-operator/pull/708
2427

2528
## [26.3.0] - 2026-03-16
2629

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,43 @@ For example, given a requested lifetime of 1 day and a jitter factor of 0.2, the
166166

167167
Jittering may be disabled by setting the jitter factor to 0.
168168

169+
=== `secrets.stackable.tech/backend.autotls.cert.domain-components-in-subject-dn`
170+
171+
*Required*: false
172+
173+
*Default value*: `"false"`
174+
175+
*Backends*: xref:secretclass.adoc#backend-autotls[]
176+
177+
If set to `"true"`, the domain components of the Pod's fully qualified domain name (FQDN) are appended to the subject distinguished name (DN) of the TLS certificate.
178+
179+
For example, the subject DN could look as follows for a StatefulSet Pod:
180+
181+
```
182+
CN=generated certificate for pod, DC=my-pod-0, DC=my-statefulset-service, DC=my-namespace, DC=svc, DC=cluster, DC=local
183+
```
184+
185+
The service name is not part of the domain components if the Pod spec does not contain a subdomain, e.g. for Deployments and Jobs:
186+
187+
```
188+
CN=generated certificate for pod, DC=my-job-pod-7c5xx, DC=my-namespace, DC=svc, DC=cluster, DC=local
189+
```
190+
191+
[NOTE]
192+
====
193+
Many products use the string representation of distinguished names as described in https://www.ietf.org/rfc/rfc4514.txt[RFC 4514{external-link-icon}^].
194+
The string representation starts with the last element of the sequence and moving backwards toward the first.
195+
196+
The examples above become:
197+
198+
```
199+
DC=local,DC=cluster,DC=svc,DC=my-namespace,DC=my-statefulset-service,DC=my-pod-0,CN=generated certificate for pod
200+
DC=local,DC=cluster,DC=svc,DC=my-namespace,DC=my-job-pod-7c5xx,CN=generated certificate for pod
201+
```
202+
203+
Attribute names can be in upper or lower case, with or without spaces between them.
204+
====
205+
169206
=== `secrets.stackable.tech/backend.cert-manager.cert.lifetime`
170207

171208
*Required*: false

rust/operator-binary/src/backend/auto_tls/mod.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,21 @@ impl SecretBackend for TlsGenerate {
355355
}
356356
}
357357

358+
let domain_components = if selector.autotls_cert_domain_components_in_subject_dn {
359+
[
360+
Some(pod_info.pod_name.as_str()),
361+
pod_info.service_name.as_deref(),
362+
Some(&pod_info.namespace),
363+
Some("svc"),
364+
]
365+
.into_iter()
366+
.flatten()
367+
.chain(pod_info.kubernetes_cluster_domain.split('.'))
368+
.collect()
369+
} else {
370+
vec![]
371+
};
372+
358373
let pod_cert = X509Builder::new()
359374
.and_then(|mut x509| {
360375
let subject_name = X509NameBuilder::new()
@@ -363,6 +378,12 @@ impl SecretBackend for TlsGenerate {
363378
Nid::COMMONNAME,
364379
"generated certificate for pod",
365380
)?;
381+
for domain_component in domain_components {
382+
name.append_entry_by_nid(
383+
Nid::DOMAINCOMPONENT,
384+
domain_component,
385+
)?;
386+
}
366387
Ok(name)
367388
})?
368389
.build();

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,19 @@ pub struct SecretVolumeSelector {
129129
)]
130130
pub autotls_cert_jitter_factor: f64,
131131

132+
/// If set to `"true"`, the domain components of the Pod's fully qualified domain name (FQDN)
133+
/// are appended to the subject distinguished name (DN) of the TLS certificate.
134+
///
135+
/// For example, the subject DN could look as follows for a StatefulSet Pod:
136+
///
137+
/// `CN=generated certificate for pod, DC=my-pod-0, DC=my-statefulset-service, DC=my-namespace, DC=svc, DC=cluster, DC=local`
138+
#[serde(
139+
rename = "secrets.stackable.tech/backend.autotls.cert.domain-components-in-subject-dn",
140+
deserialize_with = "SecretVolumeSelector::deserialize_str_as_bool",
141+
default
142+
)]
143+
pub autotls_cert_domain_components_in_subject_dn: bool,
144+
132145
/// The TLS cert lifetime (when using the [`cert_manager`] backend).
133146
///
134147
/// The format is documented in <https://docs.stackable.tech/home/nightly/concepts/duration>.
@@ -301,6 +314,16 @@ impl SecretVolumeSelector {
301314
)
302315
})
303316
}
317+
318+
fn deserialize_str_as_bool<'de, D: Deserializer<'de>>(de: D) -> Result<bool, D::Error> {
319+
let str = String::deserialize(de)?;
320+
str.parse().map_err(|_| {
321+
<D::Error as serde::de::Error>::invalid_value(
322+
Unexpected::Str(&str),
323+
&"a string containing a boolean",
324+
)
325+
})
326+
}
304327
}
305328

306329
#[derive(Debug)]

rust/operator-binary/src/backend/pod_info.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,9 @@ pub enum FromPodError {
104104
#[derive(Debug)]
105105
pub struct PodInfo {
106106
pub pod_ips: Vec<IpAddr>,
107+
pub pod_name: String,
107108
pub service_name: Option<String>,
109+
pub namespace: String,
108110
pub node_name: String,
109111
pub node_ips: Vec<IpAddr>,
110112
pub listener_addresses: Option<ListenerAddresses>,
@@ -119,6 +121,8 @@ impl PodInfo {
119121
scopes: &[SecretScope],
120122
) -> Result<Self, FromPodError> {
121123
use from_pod_error::*;
124+
let pod_name = pod.metadata.name.clone().context(NoPodNameSnafu)?;
125+
let namespace = pod.metadata.namespace.clone().context(NoNamespaceSnafu)?;
122126
let node_name = pod
123127
.spec
124128
.as_ref()
@@ -150,7 +154,9 @@ impl PodInfo {
150154
.context(from_pod_error::IllegalAddressSnafu { address: ip })
151155
})
152156
.collect::<Result<_, _>>()?,
157+
pod_name,
153158
service_name: pod.spec.as_ref().and_then(|spec| spec.subdomain.clone()),
159+
namespace,
154160
node_name,
155161
node_ips: node
156162
.status

tests/templates/kuttl/tls/10_consumer.yaml.j2

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ spec:
2222
CERT_NAME=tls.crt
2323
CA_NAME=ca.crt
2424
{% endif %}
25-
- |
25+
2626
set -euo pipefail
2727
ls -la /stackable/tls-3d
2828
ls -la /stackable/tls-42h
@@ -67,11 +67,18 @@ spec:
6767
assert_trusted_roots_contain "cert 4b"
6868
assert_trusted_roots_contain "cert 5"
6969
assert_trusted_roots_contain "cert 6"
70+
71+
echo "Test subject DN with domain components"
72+
73+
openssl x509 -in /stackable/tls-domain-components/tls.crt -subject -noout | \
74+
grep "subject=CN=generated certificate for pod, DC=tls-consumer-.*, DC=$NAMESPACE, DC=svc, DC=.*"
7075
volumeMounts:
7176
- mountPath: /stackable/tls-3d
7277
name: tls-3d
7378
- mountPath: /stackable/tls-42h
7479
name: tls-42h
80+
- mountPath: /stackable/tls-domain-components
81+
name: tls-domain-components
7582
volumes:
7683
- name: tls-3d
7784
ephemeral:
@@ -111,6 +118,21 @@ spec:
111118
resources:
112119
requests:
113120
storage: "1"
121+
- name: tls-domain-components
122+
ephemeral:
123+
volumeClaimTemplate:
124+
metadata:
125+
annotations:
126+
secrets.stackable.tech/class: tls-$NAMESPACE
127+
secrets.stackable.tech/scope: pod
128+
secrets.stackable.tech/backend.autotls.cert.domain-components-in-subject-dn: "true"
129+
spec:
130+
storageClassName: secrets.stackable.tech
131+
accessModes:
132+
- ReadWriteOnce
133+
resources:
134+
requests:
135+
storage: "1"
114136
securityContext:
115137
runAsUser: 1000
116138
runAsGroup: 1000

0 commit comments

Comments
 (0)