Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
5 changes: 4 additions & 1 deletion rust/krb5-provision-keytab/src/lib.rs
Comment thread
sbernauer marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,10 @@ pub enum Error {
/// Provisions a Kerberos Keytab based on the [`Request`].
///
/// This function assumes that the binary produced by this crate is on the `$PATH`, and will fail otherwise.
pub async fn provision_keytab(krb5_config_path: &Path, req: &Request) -> Result<Response, Error> {
pub async fn provision_keytab_file(
krb5_config_path: &Path,
req: &Request,
) -> Result<Response, Error> {
let req_str = serde_json::to_vec(&req).context(SerializeRequestSnafu)?;

let mut child = Command::new("stackable-krb5-provision-keytab")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@ use stackable_operator::{
};
use time::OffsetDateTime;

use super::{
ScopeAddressesError, SecretBackend, SecretBackendError, SecretContents,
pod_info::{Address, PodInfo},
scope::SecretScope,
};
use crate::{
backend::{
ScopeAddressesError, SecretBackend, SecretBackendError, SecretContents,
SecretVolumeSelector,
pod_info::{Address, PodInfo},
scope::SecretScope,
},
crd::v1alpha2,
format::{SecretData, WellKnownSecretData, well_known},
utils::iterator_try_concat_bytes,
Expand Down Expand Up @@ -262,7 +263,7 @@ impl SecretBackend for TlsGenerate {
/// Then add the ca certificate and return these files for provisioning to the volume.
async fn get_secret_data(
&self,
selector: &super::SecretVolumeSelector,
selector: &SecretVolumeSelector,
pod_info: PodInfo,
) -> Result<SecretContents, Self::Error> {
let now = OffsetDateTime::now_utc();
Expand All @@ -271,6 +272,7 @@ impl SecretBackend for TlsGenerate {
// Extract and convert consumer input from the Volume annotations.
let cert_lifetime = selector.autotls_cert_lifetime;
let cert_restart_buffer = selector.autotls_cert_restart_buffer;
let provision_cert = !selector.only_provision_identity;

// We need to check that the cert lifetime it is not longer than allowed,
// by capping it to the maximum configured at the SecretClass.
Expand Down Expand Up @@ -300,6 +302,7 @@ impl SecretBackend for TlsGenerate {
let jitter_amount = Duration::from(cert_lifetime.mul_f64(jitter_factor));
let unjittered_cert_lifetime = cert_lifetime;
let cert_lifetime = cert_lifetime - jitter_amount;

tracing::info!(
certificate.lifetime.requested = %unjittered_cert_lifetime,
certificate.lifetime.jitter = %jitter_amount,
Expand All @@ -319,113 +322,140 @@ impl SecretBackend for TlsGenerate {
.fail()?;
}

let conf =
Conf::new(ConfMethod::default()).expect("failed to initialize OpenSSL configuration");

let pod_key_length = match self.key_generation {
v1alpha2::CertificateKeyGeneration::Rsa { length } => length,
};

let pod_key = Rsa::generate(pod_key_length)
.and_then(PKey::try_from)
.context(GenerateKeySnafu)?;
let mut addresses = Vec::new();
for scope in &selector.scope {
addresses.extend(
selector
.scope_addresses(&pod_info, scope)
.context(ScopeAddressesSnafu { scope })?,
);
}
for address in &mut addresses {
if let Address::Dns(dns) = address {
// Turn FQDNs into bare domain names by removing the trailing dot
if dns.ends_with('.') {
dns.pop();
}
}
}
let ca = self
.ca_manager
.find_certificate_authority_for_signing(not_after)
.context(PickCaSnafu)?;
let pod_cert = X509Builder::new()
.and_then(|mut x509| {
let subject_name = X509NameBuilder::new()
.and_then(|mut name| {
name.append_entry_by_nid(Nid::COMMONNAME, "generated certificate for pod")?;
Ok(name)
})?
.build();
x509.set_subject_name(&subject_name)?;
x509.set_issuer_name(ca.certificate.subject_name())?;
x509.set_not_before(Asn1Time::from_unix(not_before.unix_timestamp())?.as_ref())?;
x509.set_not_after(Asn1Time::from_unix(not_after.unix_timestamp())?.as_ref())?;
x509.set_pubkey(&pod_key)?;
x509.set_version(
3 - 1, // zero-indexed
)?;
let mut serial = BigNum::new()?;
serial.rand(64, MsbOption::MAYBE_ZERO, false)?;
x509.set_serial_number(Asn1Integer::from_bn(&serial)?.as_ref())?;
let ctx = x509.x509v3_context(Some(&ca.certificate), Some(&conf));
let mut exts = vec![
BasicConstraints::new().critical().build()?,
KeyUsage::new()
.key_encipherment()
.digital_signature()
.build()?,
ExtendedKeyUsage::new()
.server_auth()
.client_auth()
.build()?,
SubjectKeyIdentifier::new().build(&ctx)?,
AuthorityKeyIdentifier::new()
.issuer(true)
.keyid(true)
.build(&ctx)?,
];
let mut san_ext = SubjectAlternativeName::new();
san_ext.critical();
let mut has_san = false;
for addr in addresses {
has_san = true;
match addr {
Address::Dns(dns) => san_ext.dns(&dns),
Address::Ip(ip) => san_ext.ip(&ip.to_string()),
};
}
if has_san {
exts.push(san_ext.build(&ctx)?);
}
for ext in exts {
x509.append_extension(ext)?;

// Only run leaf certificate generation if it was requested based on the
// secret volume selector. Otherwise only a ca.crt file as a PEM envelope
// will be available (to be mounted).
Comment thread
Techassi marked this conversation as resolved.
let tls_secret_data = if provision_cert {
let conf = Conf::new(ConfMethod::default())
.expect("failed to initialize OpenSSL configuration");

let pod_key_length = match self.key_generation {
v1alpha2::CertificateKeyGeneration::Rsa { length } => length,
};

let pod_key = Rsa::generate(pod_key_length)
.and_then(PKey::try_from)
.context(GenerateKeySnafu)?;

let mut addresses = Vec::new();
for scope in &selector.scope {
addresses.extend(
selector
.scope_addresses(&pod_info, scope)
.context(ScopeAddressesSnafu { scope })?,
);
}
for address in &mut addresses {
if let Address::Dns(dns) = address {
// Turn FQDNs into bare domain names by removing the trailing dot
if dns.ends_with('.') {
dns.pop();
}
}
x509.sign(&ca.private_key, MessageDigest::sha256())?;
Ok(x509)
})
.context(BuildCertificateSnafu)?
.build();
}

let pod_cert = X509Builder::new()
.and_then(|mut x509| {
let subject_name = X509NameBuilder::new()
.and_then(|mut name| {
name.append_entry_by_nid(
Nid::COMMONNAME,
"generated certificate for pod",
)?;
Ok(name)
})?
.build();
x509.set_subject_name(&subject_name)?;
x509.set_issuer_name(ca.certificate.subject_name())?;
x509.set_not_before(
Asn1Time::from_unix(not_before.unix_timestamp())?.as_ref(),
)?;
x509.set_not_after(Asn1Time::from_unix(not_after.unix_timestamp())?.as_ref())?;
x509.set_pubkey(&pod_key)?;
x509.set_version(
3 - 1, // zero-indexed
)?;
let mut serial = BigNum::new()?;
serial.rand(64, MsbOption::MAYBE_ZERO, false)?;
x509.set_serial_number(Asn1Integer::from_bn(&serial)?.as_ref())?;
let ctx = x509.x509v3_context(Some(&ca.certificate), Some(&conf));
let mut exts = vec![
BasicConstraints::new().critical().build()?,
KeyUsage::new()
.key_encipherment()
.digital_signature()
.build()?,
ExtendedKeyUsage::new()
.server_auth()
.client_auth()
.build()?,
SubjectKeyIdentifier::new().build(&ctx)?,
AuthorityKeyIdentifier::new()
.issuer(true)
.keyid(true)
.build(&ctx)?,
];
let mut san_ext = SubjectAlternativeName::new();
san_ext.critical();
let mut has_san = false;
for addr in addresses {
has_san = true;
match addr {
Address::Dns(dns) => san_ext.dns(&dns),
Address::Ip(ip) => san_ext.ip(&ip.to_string()),
};
}
if has_san {
exts.push(san_ext.build(&ctx)?);
}
for ext in exts {
x509.append_extension(ext)?;
}
x509.sign(&ca.private_key, MessageDigest::sha256())?;
Ok(x509)
})
.context(BuildCertificateSnafu)?
.build();

well_known::TlsPem {
ca_pem: iterator_try_concat_bytes(
self.ca_manager.trust_roots(now).into_iter().map(|ca| {
ca.to_pem()
.context(SerializeCertificateSnafu { tpe: CertType::Ca })
}),
)?,
certificate_pem: Some(
pod_cert
.to_pem()
.context(SerializeCertificateSnafu { tpe: CertType::Pod })?,
),
key_pem: Some(
pod_key
.private_key_to_pem_pkcs8()
.context(SerializeCertificateSnafu { tpe: CertType::Pod })?,
),
}
} else {
well_known::TlsPem {
ca_pem: iterator_try_concat_bytes(
self.ca_manager.trust_roots(now).into_iter().map(|ca| {
ca.to_pem()
.context(SerializeCertificateSnafu { tpe: CertType::Ca })
}),
)?,
certificate_pem: None,
key_pem: None,
}
};

Ok(
SecretContents::new(SecretData::WellKnown(WellKnownSecretData::TlsPem(
well_known::TlsPem {
ca_pem: iterator_try_concat_bytes(
self.ca_manager.trust_roots(now).into_iter().map(|ca| {
ca.to_pem()
.context(SerializeCertificateSnafu { tpe: CertType::Ca })
}),
)?,
certificate_pem: Some(
pod_cert
.to_pem()
.context(SerializeCertificateSnafu { tpe: CertType::Pod })?,
),
key_pem: Some(
pod_key
.private_key_to_pem_pkcs8()
.context(SerializeCertificateSnafu { tpe: CertType::Pod })?,
),
},
tls_secret_data,
)))
.expires_after(
time_datetime_to_chrono(expire_pod_after).context(InvalidCertLifetimeSnafu)?,
Expand Down
5 changes: 2 additions & 3 deletions rust/operator-binary/src/backend/dynamic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ use snafu::{ResultExt, Snafu};
use stackable_operator::kube::runtime::reflector::ObjectRef;

use super::{
SecretBackend, SecretBackendError, SecretVolumeSelector,
SecretBackend, SecretBackendError, SecretVolumeSelector, auto_tls,
kerberos_keytab::{self, KerberosProfile},
pod_info::{PodInfo, SchedulingPodInfo},
tls,
};
use crate::{crd::v1alpha2, utils::Unloggable};

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

#[snafu(
display("failed to initialize Kerberos Keytab backend"),
Expand Down
Loading