Skip to content

Commit ee28fe7

Browse files
Techassisbernauer
andauthored
feat: Add annotation to provision non-sensitive data only (#676)
* feat: Add annotation to provision public secret data only * chore: Rename tls module to auto_tls * feat: Add relaxed file loading * feat: Conditionally generate Kerberos keytab * test: Add non-sensitive-data integration test * test: Add PKCS#12 format volume + mount * chore: Rename annotation * docs: Add section for new annotation * chore: Remove outdated TODO comment * chore: Add doc comment explaining the default variant * test: Use updated annotation in integration test * chore: Apply suggestion Co-authored-by: Sebastian Bernauer <sebastian.bernauer@stackable.de> * docs: Fix typo Co-authored-by: Sebastian Bernauer <sebastian.bernauer@stackable.de> * refactor: Slightly optimize conditional leaf cert generation * refactor: Slightly optimize conditional kerberos keytab generation * test: Increase second step timeout to 30 seconds Sometimes, this step takes slightly longer than the default timeout of ten seconds and therefore fails. --------- Co-authored-by: Sebastian Bernauer <sebastian.bernauer@stackable.de>
1 parent b55b5ea commit ee28fe7

21 files changed

Lines changed: 524 additions & 244 deletions

File tree

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,23 @@ The xref:secretclass.adoc#format[format] that the secret should be written as.
5050

5151
This can be either the default output format of the xref:secretclass.adoc#backend[backend], or a format that it defines a conversion into.
5252

53+
=== `secrets.stackable.tech/provision-parts`
54+
55+
*Required*: false
56+
57+
*Default value*: `public-private`
58+
59+
*Backend*: All
60+
61+
This annotation allows configuring which parts of the secret material should be provisioned.
62+
Supported values are `public` and `public-private`, provisioning only public or public+private data respectively.
63+
Using this annotation enables the following use-cases:
64+
65+
* Use the `autoTls` backend, but only provision the `ca.crt`/`truststore.p12` for the consumer.
66+
* Use the `kerberosKeytab` backend, but only provision the `krb5.conf` for the consumer.
67+
* Use the `k8sSearch` backend to select Secrets which contain public data only and support parsing the partial set of files.
68+
Using this annotation disables the strict parsing of files when an explicit format is requested.
69+
5370
=== `secrets.stackable.tech/format.tls-pkcs12.keystore-name`
5471

5572
*Required*: false

rust/krb5-provision-keytab/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,10 @@ pub enum Error {
6868
/// Provisions a Kerberos Keytab based on the [`Request`].
6969
///
7070
/// This function assumes that the binary produced by this crate is on the `$PATH`, and will fail otherwise.
71-
pub async fn provision_keytab(krb5_config_path: &Path, req: &Request) -> Result<Response, Error> {
71+
pub async fn provision_keytab_file(
72+
krb5_config_path: &Path,
73+
req: &Request,
74+
) -> Result<Response, Error> {
7275
let req_str = serde_json::to_vec(&req).context(SerializeRequestSnafu)?;
7376

7477
let mut child = Command::new("stackable-krb5-provision-keytab")
File renamed without changes.

rust/operator-binary/src/backend/tls/mod.rs renamed to rust/operator-binary/src/backend/auto_tls/mod.rs

Lines changed: 126 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,13 @@ use snafu::{OptionExt, ResultExt, Snafu, ensure};
2525
use stackable_operator::{kube::runtime::reflector::ObjectRef, shared::time::Duration};
2626
use time::OffsetDateTime;
2727

28-
use super::{
29-
ScopeAddressesError, SecretBackend, SecretBackendError, SecretContents,
30-
pod_info::{Address, PodInfo},
31-
scope::SecretScope,
32-
};
3328
use crate::{
29+
backend::{
30+
ProvisionParts, ScopeAddressesError, SecretBackend, SecretBackendError, SecretContents,
31+
SecretVolumeSelector,
32+
pod_info::{Address, PodInfo},
33+
scope::SecretScope,
34+
},
3435
crd::v1alpha2,
3536
format::{SecretData, WellKnownSecretData, well_known},
3637
utils::iterator_try_concat_bytes,
@@ -257,7 +258,7 @@ impl SecretBackend for TlsGenerate {
257258
/// Then add the ca certificate and return these files for provisioning to the volume.
258259
async fn get_secret_data(
259260
&self,
260-
selector: &super::SecretVolumeSelector,
261+
selector: &SecretVolumeSelector,
261262
pod_info: PodInfo,
262263
) -> Result<SecretContents, Self::Error> {
263264
let now = OffsetDateTime::now_utc();
@@ -295,6 +296,7 @@ impl SecretBackend for TlsGenerate {
295296
let jitter_amount = Duration::from(cert_lifetime.mul_f64(jitter_factor));
296297
let unjittered_cert_lifetime = cert_lifetime;
297298
let cert_lifetime = cert_lifetime - jitter_amount;
299+
298300
tracing::info!(
299301
certificate.lifetime.requested = %unjittered_cert_lifetime,
300302
certificate.lifetime.jitter = %jitter_amount,
@@ -314,112 +316,133 @@ impl SecretBackend for TlsGenerate {
314316
.fail()?;
315317
}
316318

317-
let conf =
318-
Conf::new(ConfMethod::default()).expect("failed to initialize OpenSSL configuration");
319-
320-
let pod_key_length = match self.key_generation {
321-
v1alpha2::CertificateKeyGeneration::Rsa { length } => length,
322-
};
323-
324-
let pod_key = Rsa::generate(pod_key_length)
325-
.and_then(PKey::try_from)
326-
.context(GenerateKeySnafu)?;
327-
let mut addresses = Vec::new();
328-
for scope in &selector.scope {
329-
addresses.extend(
330-
selector
331-
.scope_addresses(&pod_info, scope)
332-
.context(ScopeAddressesSnafu { scope })?,
333-
);
334-
}
335-
for address in &mut addresses {
336-
if let Address::Dns(dns) = address {
337-
// Turn FQDNs into bare domain names by removing the trailing dot
338-
if dns.ends_with('.') {
339-
dns.pop();
340-
}
341-
}
342-
}
343319
let ca = self
344320
.ca_manager
345321
.find_certificate_authority_for_signing(not_after)
346322
.context(PickCaSnafu)?;
347-
let pod_cert = X509Builder::new()
348-
.and_then(|mut x509| {
349-
let subject_name = X509NameBuilder::new()
350-
.and_then(|mut name| {
351-
name.append_entry_by_nid(Nid::COMMONNAME, "generated certificate for pod")?;
352-
Ok(name)
353-
})?
354-
.build();
355-
x509.set_subject_name(&subject_name)?;
356-
x509.set_issuer_name(ca.certificate.subject_name())?;
357-
x509.set_not_before(Asn1Time::from_unix(not_before.unix_timestamp())?.as_ref())?;
358-
x509.set_not_after(Asn1Time::from_unix(not_after.unix_timestamp())?.as_ref())?;
359-
x509.set_pubkey(&pod_key)?;
360-
x509.set_version(
361-
3 - 1, // zero-indexed
362-
)?;
363-
let mut serial = BigNum::new()?;
364-
serial.rand(64, MsbOption::MAYBE_ZERO, false)?;
365-
x509.set_serial_number(Asn1Integer::from_bn(&serial)?.as_ref())?;
366-
let ctx = x509.x509v3_context(Some(&ca.certificate), Some(&conf));
367-
let mut exts = vec![
368-
BasicConstraints::new().critical().build()?,
369-
KeyUsage::new()
370-
.key_encipherment()
371-
.digital_signature()
372-
.build()?,
373-
ExtendedKeyUsage::new()
374-
.server_auth()
375-
.client_auth()
376-
.build()?,
377-
SubjectKeyIdentifier::new().build(&ctx)?,
378-
AuthorityKeyIdentifier::new()
379-
.issuer(true)
380-
.keyid(true)
381-
.build(&ctx)?,
382-
];
383-
let mut san_ext = SubjectAlternativeName::new();
384-
san_ext.critical();
385-
let mut has_san = false;
386-
for addr in addresses {
387-
has_san = true;
388-
match addr {
389-
Address::Dns(dns) => san_ext.dns(&dns),
390-
Address::Ip(ip) => san_ext.ip(&ip.to_string()),
391-
};
392-
}
393-
if has_san {
394-
exts.push(san_ext.build(&ctx)?);
323+
324+
// Only run leaf certificate generation if it was requested based on the
325+
// secret volume selector. Otherwise only a ca.crt file as a PEM envelope
326+
// will be available (to be mounted).
327+
let (certificate_pem, key_pem) = match selector.provision_parts {
328+
ProvisionParts::Public => (None, None),
329+
ProvisionParts::PublicPrivate => {
330+
let conf = Conf::new(ConfMethod::default())
331+
.expect("failed to initialize OpenSSL configuration");
332+
333+
let pod_key_length = match self.key_generation {
334+
v1alpha2::CertificateKeyGeneration::Rsa { length } => length,
335+
};
336+
337+
let pod_key = Rsa::generate(pod_key_length)
338+
.and_then(PKey::try_from)
339+
.context(GenerateKeySnafu)?;
340+
341+
let mut addresses = Vec::new();
342+
for scope in &selector.scope {
343+
addresses.extend(
344+
selector
345+
.scope_addresses(&pod_info, scope)
346+
.context(ScopeAddressesSnafu { scope })?,
347+
);
395348
}
396-
for ext in exts {
397-
x509.append_extension(ext)?;
349+
for address in &mut addresses {
350+
if let Address::Dns(dns) = address {
351+
// Turn FQDNs into bare domain names by removing the trailing dot
352+
if dns.ends_with('.') {
353+
dns.pop();
354+
}
355+
}
398356
}
399-
x509.sign(&ca.private_key, MessageDigest::sha256())?;
400-
Ok(x509)
401-
})
402-
.context(BuildCertificateSnafu)?
403-
.build();
357+
358+
let pod_cert = X509Builder::new()
359+
.and_then(|mut x509| {
360+
let subject_name = X509NameBuilder::new()
361+
.and_then(|mut name| {
362+
name.append_entry_by_nid(
363+
Nid::COMMONNAME,
364+
"generated certificate for pod",
365+
)?;
366+
Ok(name)
367+
})?
368+
.build();
369+
x509.set_subject_name(&subject_name)?;
370+
x509.set_issuer_name(ca.certificate.subject_name())?;
371+
x509.set_not_before(
372+
Asn1Time::from_unix(not_before.unix_timestamp())?.as_ref(),
373+
)?;
374+
x509.set_not_after(
375+
Asn1Time::from_unix(not_after.unix_timestamp())?.as_ref(),
376+
)?;
377+
x509.set_pubkey(&pod_key)?;
378+
x509.set_version(
379+
3 - 1, // zero-indexed
380+
)?;
381+
let mut serial = BigNum::new()?;
382+
serial.rand(64, MsbOption::MAYBE_ZERO, false)?;
383+
x509.set_serial_number(Asn1Integer::from_bn(&serial)?.as_ref())?;
384+
let ctx = x509.x509v3_context(Some(&ca.certificate), Some(&conf));
385+
let mut exts = vec![
386+
BasicConstraints::new().critical().build()?,
387+
KeyUsage::new()
388+
.key_encipherment()
389+
.digital_signature()
390+
.build()?,
391+
ExtendedKeyUsage::new()
392+
.server_auth()
393+
.client_auth()
394+
.build()?,
395+
SubjectKeyIdentifier::new().build(&ctx)?,
396+
AuthorityKeyIdentifier::new()
397+
.issuer(true)
398+
.keyid(true)
399+
.build(&ctx)?,
400+
];
401+
let mut san_ext = SubjectAlternativeName::new();
402+
san_ext.critical();
403+
let mut has_san = false;
404+
for addr in addresses {
405+
has_san = true;
406+
match addr {
407+
Address::Dns(dns) => san_ext.dns(&dns),
408+
Address::Ip(ip) => san_ext.ip(&ip.to_string()),
409+
};
410+
}
411+
if has_san {
412+
exts.push(san_ext.build(&ctx)?);
413+
}
414+
for ext in exts {
415+
x509.append_extension(ext)?;
416+
}
417+
x509.sign(&ca.private_key, MessageDigest::sha256())?;
418+
Ok(x509)
419+
})
420+
.context(BuildCertificateSnafu)?
421+
.build();
422+
423+
let certificate_pem = pod_cert
424+
.to_pem()
425+
.context(SerializeCertificateSnafu { tpe: CertType::Pod })?;
426+
let key_pem = pod_key
427+
.private_key_to_pem_pkcs8()
428+
.context(SerializeCertificateSnafu { tpe: CertType::Pod })?;
429+
430+
(Some(certificate_pem), Some(key_pem))
431+
}
432+
};
433+
434+
let ca_pem =
435+
iterator_try_concat_bytes(self.ca_manager.trust_roots(now).into_iter().map(|ca| {
436+
ca.to_pem()
437+
.context(SerializeCertificateSnafu { tpe: CertType::Ca })
438+
}))?;
439+
404440
Ok(
405441
SecretContents::new(SecretData::WellKnown(WellKnownSecretData::TlsPem(
406442
well_known::TlsPem {
407-
ca_pem: iterator_try_concat_bytes(
408-
self.ca_manager.trust_roots(now).into_iter().map(|ca| {
409-
ca.to_pem()
410-
.context(SerializeCertificateSnafu { tpe: CertType::Ca })
411-
}),
412-
)?,
413-
certificate_pem: Some(
414-
pod_cert
415-
.to_pem()
416-
.context(SerializeCertificateSnafu { tpe: CertType::Pod })?,
417-
),
418-
key_pem: Some(
419-
pod_key
420-
.private_key_to_pem_pkcs8()
421-
.context(SerializeCertificateSnafu { tpe: CertType::Pod })?,
422-
),
443+
certificate_pem,
444+
key_pem,
445+
ca_pem,
423446
},
424447
)))
425448
.expires_after(

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,9 @@ use snafu::{ResultExt, Snafu};
1010
use stackable_operator::kube::runtime::reflector::ObjectRef;
1111

1212
use super::{
13-
SecretBackend, SecretBackendError, SecretVolumeSelector,
13+
SecretBackend, SecretBackendError, SecretVolumeSelector, auto_tls,
1414
kerberos_keytab::{self, KerberosProfile},
1515
pod_info::{PodInfo, SchedulingPodInfo},
16-
tls,
1716
};
1817
use crate::{crd::v1alpha2, utils::Unloggable};
1918

@@ -99,7 +98,7 @@ pub fn from(backend: impl SecretBackend + 'static) -> Box<Dynamic> {
9998
#[snafu(module)]
10099
pub enum FromClassError {
101100
#[snafu(display("failed to initialize TLS backend"), context(false))]
102-
Tls { source: tls::Error },
101+
Tls { source: auto_tls::Error },
103102

104103
#[snafu(
105104
display("failed to initialize Kerberos Keytab backend"),

0 commit comments

Comments
 (0)