From bde0ad8f7e0a0db0c60a9a57119142879839f2d0 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Thu, 4 Sep 2025 10:59:36 +0200 Subject: [PATCH 01/24] WIP --- Cargo.lock | 38 ++-- Cargo.nix | 160 ++++++++++++----- rust/crd-utils/Cargo.toml | 14 -- rust/krb5-provision-keytab/Cargo.toml | 2 +- .../src/active_directory.rs | 2 +- .../src/credential_cache.rs | 2 +- rust/krb5-provision-keytab/src/lib.rs | 2 +- rust/operator-binary/Cargo.toml | 3 +- .../src/backend/kerberos_keytab.rs | 2 +- rust/operator-binary/src/backend/tls/ca.rs | 2 +- rust/operator-binary/src/crd/mod.rs | 2 +- rust/operator-binary/src/format/convert.rs | 131 +------------- rust/operator-binary/src/format/mod.rs | 1 - rust/operator-binary/src/format/utils.rs | 60 ------- rust/truststore-merger/Cargo.toml | 18 ++ rust/truststore-merger/src/cli_args.rs | 162 +++++++++++++++++ rust/truststore-merger/src/main.rs | 84 +++++++++ rust/utils/Cargo.toml | 24 +++ .../src/lib.rs => utils/src/crd.rs} | 0 rust/utils/src/lib.rs | 5 + rust/utils/src/pem.rs | 169 ++++++++++++++++++ rust/utils/src/pkcs12.rs | 118 ++++++++++++ 22 files changed, 741 insertions(+), 260 deletions(-) delete mode 100644 rust/crd-utils/Cargo.toml delete mode 100644 rust/operator-binary/src/format/utils.rs create mode 100644 rust/truststore-merger/Cargo.toml create mode 100644 rust/truststore-merger/src/cli_args.rs create mode 100644 rust/truststore-merger/src/main.rs create mode 100644 rust/utils/Cargo.toml rename rust/{crd-utils/src/lib.rs => utils/src/crd.rs} (100%) create mode 100644 rust/utils/src/lib.rs create mode 100644 rust/utils/src/pem.rs create mode 100644 rust/utils/src/pkcs12.rs diff --git a/Cargo.lock b/Cargo.lock index c579473e..29abbed4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3079,7 +3079,7 @@ dependencies = [ "serde_json", "snafu 0.8.7", "stackable-operator", - "stackable-secret-operator-crd-utils", + "stackable-secret-operator-utils", "tokio", "tracing", "tracing-subscriber", @@ -3160,7 +3160,7 @@ dependencies = [ "socket2", "stackable-krb5-provision-keytab", "stackable-operator", - "stackable-secret-operator-crd-utils", + "stackable-secret-operator-utils", "strum", "sys-mount", "tempfile", @@ -3173,15 +3173,6 @@ dependencies = [ "tonic-reflection", "tracing", "uuid", - "yasna", -] - -[[package]] -name = "stackable-secret-operator-crd-utils" -version = "0.0.0-dev" -dependencies = [ - "serde", - "stackable-operator", ] [[package]] @@ -3200,6 +3191,19 @@ dependencies = [ "walkdir", ] +[[package]] +name = "stackable-secret-operator-utils" +version = "0.0.0-dev" +dependencies = [ + "anyhow", + "openssl", + "p12", + "serde", + "snafu 0.8.7", + "stackable-operator", + "yasna", +] + [[package]] name = "stackable-shared" version = "0.0.2" @@ -3240,6 +3244,18 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "stackable-truststore-merger" +version = "0.0.0-dev" +dependencies = [ + "anyhow", + "clap", + "openssl", + "stackable-secret-operator-utils", + "tracing", + "tracing-subscriber", +] + [[package]] name = "stackable-versioned" version = "0.8.1" diff --git a/Cargo.nix b/Cargo.nix index 6b343ba7..59064ab8 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -68,20 +68,30 @@ rec { # File a bug if you depend on any for non-debug work! debug = internal.debugCrate { inherit packageId; }; }; - "stackable-secret-operator-crd-utils" = rec { - packageId = "stackable-secret-operator-crd-utils"; + "stackable-secret-operator-olm-deployer" = rec { + packageId = "stackable-secret-operator-olm-deployer"; build = internal.buildRustCrateWithFeatures { - packageId = "stackable-secret-operator-crd-utils"; + packageId = "stackable-secret-operator-olm-deployer"; }; # Debug support which might change between releases. # File a bug if you depend on any for non-debug work! debug = internal.debugCrate { inherit packageId; }; }; - "stackable-secret-operator-olm-deployer" = rec { - packageId = "stackable-secret-operator-olm-deployer"; + "stackable-secret-operator-utils" = rec { + packageId = "stackable-secret-operator-utils"; build = internal.buildRustCrateWithFeatures { - packageId = "stackable-secret-operator-olm-deployer"; + packageId = "stackable-secret-operator-utils"; + }; + + # Debug support which might change between releases. + # File a bug if you depend on any for non-debug work! + debug = internal.debugCrate { inherit packageId; }; + }; + "stackable-truststore-merger" = rec { + packageId = "stackable-truststore-merger"; + build = internal.buildRustCrateWithFeatures { + packageId = "stackable-truststore-merger"; }; # Debug support which might change between releases. @@ -10009,11 +10019,12 @@ rec { { name = "stackable-operator"; packageId = "stackable-operator"; - features = [ "time" "telemetry" ]; + features = [ "time" "telemetry" "versioned" ]; } { - name = "stackable-secret-operator-crd-utils"; - packageId = "stackable-secret-operator-crd-utils"; + name = "stackable-secret-operator-utils"; + packageId = "stackable-secret-operator-utils"; + features = [ "crd" ]; } { name = "tokio"; @@ -10333,11 +10344,12 @@ rec { { name = "stackable-operator"; packageId = "stackable-operator"; - features = [ "time" "telemetry" ]; + features = [ "time" "telemetry" "versioned" ]; } { - name = "stackable-secret-operator-crd-utils"; - packageId = "stackable-secret-operator-crd-utils"; + name = "stackable-secret-operator-utils"; + packageId = "stackable-secret-operator-utils"; + features = [ "crd" ]; } { name = "strum"; @@ -10389,10 +10401,6 @@ rec { packageId = "uuid"; features = [ "v4" ]; } - { - name = "yasna"; - packageId = "yasna"; - } ]; buildDependencies = [ { @@ -10412,29 +10420,6 @@ rec { } ]; - }; - "stackable-secret-operator-crd-utils" = rec { - crateName = "stackable-secret-operator-crd-utils"; - version = "0.0.0-dev"; - edition = "2021"; - src = lib.cleanSourceWith { filter = sourceFilter; src = ./rust/crd-utils; }; - libName = "stackable_secret_operator_crd_utils"; - authors = [ - "Stackable GmbH " - ]; - dependencies = [ - { - name = "serde"; - packageId = "serde"; - features = [ "derive" ]; - } - { - name = "stackable-operator"; - packageId = "stackable-operator"; - features = [ "time" "telemetry" ]; - } - ]; - }; "stackable-secret-operator-olm-deployer" = rec { crateName = "stackable-secret-operator-olm-deployer"; @@ -10476,7 +10461,7 @@ rec { { name = "stackable-operator"; packageId = "stackable-operator"; - features = [ "time" "telemetry" ]; + features = [ "time" "telemetry" "versioned" ]; } { name = "tokio"; @@ -10501,6 +10486,56 @@ rec { ]; }; + "stackable-secret-operator-utils" = rec { + crateName = "stackable-secret-operator-utils"; + version = "0.0.0-dev"; + edition = "2021"; + src = lib.cleanSourceWith { filter = sourceFilter; src = ./rust/utils; }; + libName = "stackable_secret_operator_utils"; + authors = [ + "Stackable GmbH " + ]; + dependencies = [ + { + name = "openssl"; + packageId = "openssl"; + } + { + name = "p12"; + packageId = "p12"; + } + { + name = "serde"; + packageId = "serde"; + optional = true; + features = [ "derive" ]; + } + { + name = "snafu"; + packageId = "snafu 0.8.7"; + } + { + name = "stackable-operator"; + packageId = "stackable-operator"; + optional = true; + features = [ "time" "telemetry" "versioned" ]; + } + { + name = "yasna"; + packageId = "yasna"; + } + ]; + devDependencies = [ + { + name = "anyhow"; + packageId = "anyhow"; + } + ]; + features = { + "crd" = [ "dep:stackable-operator" "dep:serde" ]; + }; + resolvedDefaultFeatures = [ "crd" "default" ]; + }; "stackable-shared" = rec { crateName = "stackable-shared"; version = "0.0.2"; @@ -10686,6 +10721,51 @@ rec { }; resolvedDefaultFeatures = [ "clap" ]; }; + "stackable-truststore-merger" = rec { + crateName = "stackable-truststore-merger"; + version = "0.0.0-dev"; + edition = "2021"; + crateBin = [ + { + name = "stackable-truststore-merger"; + path = "src/main.rs"; + requiredFeatures = [ ]; + } + ]; + src = lib.cleanSourceWith { filter = sourceFilter; src = ./rust/truststore-merger; }; + authors = [ + "Stackable GmbH " + ]; + dependencies = [ + { + name = "anyhow"; + packageId = "anyhow"; + } + { + name = "clap"; + packageId = "clap"; + features = [ "derive" ]; + } + { + name = "openssl"; + packageId = "openssl"; + } + { + name = "stackable-secret-operator-utils"; + packageId = "stackable-secret-operator-utils"; + } + { + name = "tracing"; + packageId = "tracing"; + } + { + name = "tracing-subscriber"; + packageId = "tracing-subscriber"; + features = [ "env-filter" ]; + } + ]; + + }; "stackable-versioned" = rec { crateName = "stackable-versioned"; version = "0.8.1"; diff --git a/rust/crd-utils/Cargo.toml b/rust/crd-utils/Cargo.toml deleted file mode 100644 index a1f84185..00000000 --- a/rust/crd-utils/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "stackable-secret-operator-crd-utils" -version.workspace = true -authors.workspace = true -license.workspace = true -edition.workspace = true -repository.workspace = true -publish = false - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -serde.workspace = true -stackable-operator.workspace = true diff --git a/rust/krb5-provision-keytab/Cargo.toml b/rust/krb5-provision-keytab/Cargo.toml index b0e9b622..823a6102 100644 --- a/rust/krb5-provision-keytab/Cargo.toml +++ b/rust/krb5-provision-keytab/Cargo.toml @@ -9,7 +9,7 @@ repository.workspace = true publish = false [dependencies] -stackable-secret-operator-crd-utils = { path = "../crd-utils" } +stackable-secret-operator-utils = { path = "../utils", features = ["crd"] } krb5.workspace = true byteorder.workspace = true diff --git a/rust/krb5-provision-keytab/src/active_directory.rs b/rust/krb5-provision-keytab/src/active_directory.rs index f30de6b7..5aee86e9 100644 --- a/rust/krb5-provision-keytab/src/active_directory.rs +++ b/rust/krb5-provision-keytab/src/active_directory.rs @@ -14,7 +14,7 @@ use stackable_operator::{ k8s_openapi::api::core::v1::Secret, kube::{self, runtime::reflector::ObjectRef}, }; -use stackable_secret_operator_crd_utils::SecretReference; +use stackable_secret_operator_utils::crd::SecretReference; use crate::credential_cache::{self, CredentialCache}; diff --git a/rust/krb5-provision-keytab/src/credential_cache.rs b/rust/krb5-provision-keytab/src/credential_cache.rs index af201d02..01f9d498 100644 --- a/rust/krb5-provision-keytab/src/credential_cache.rs +++ b/rust/krb5-provision-keytab/src/credential_cache.rs @@ -8,7 +8,7 @@ use stackable_operator::{ runtime::reflector::ObjectRef, }, }; -use stackable_secret_operator_crd_utils::SecretReference; +use stackable_secret_operator_utils::crd::SecretReference; const OPERATOR_NAME: &str = "secrets.stackable.tech"; const FIELD_MANAGER_SCOPE: &str = "krb5-provision-keytab"; diff --git a/rust/krb5-provision-keytab/src/lib.rs b/rust/krb5-provision-keytab/src/lib.rs index 2bb1f5a8..7c94df92 100644 --- a/rust/krb5-provision-keytab/src/lib.rs +++ b/rust/krb5-provision-keytab/src/lib.rs @@ -7,7 +7,7 @@ use std::{ use serde::{Deserialize, Serialize}; use snafu::{ResultExt, Snafu}; -use stackable_secret_operator_crd_utils::SecretReference; +use stackable_secret_operator_utils::crd::SecretReference; use tokio::{io::AsyncWriteExt, process::Command}; #[derive(Serialize, Deserialize)] diff --git a/rust/operator-binary/Cargo.toml b/rust/operator-binary/Cargo.toml index f325c501..9c4e2a0d 100644 --- a/rust/operator-binary/Cargo.toml +++ b/rust/operator-binary/Cargo.toml @@ -10,7 +10,7 @@ publish = false [dependencies] stackable-krb5-provision-keytab = { path = "../krb5-provision-keytab" } -stackable-secret-operator-crd-utils = { path = "../crd-utils" } +stackable-secret-operator-utils = { path = "../utils", features = ["crd"] } p12 = { path = "../p12" } anyhow.workspace = true @@ -40,7 +40,6 @@ tonic-prost.workspace = true tonic-reflection.workspace = true tracing.workspace = true uuid.workspace = true -yasna.workspace = true rand.workspace = true const_format.workspace = true diff --git a/rust/operator-binary/src/backend/kerberos_keytab.rs b/rust/operator-binary/src/backend/kerberos_keytab.rs index 0efe4da4..70ecd009 100644 --- a/rust/operator-binary/src/backend/kerberos_keytab.rs +++ b/rust/operator-binary/src/backend/kerberos_keytab.rs @@ -10,7 +10,7 @@ use stackable_operator::{ k8s_openapi::api::core::v1::Secret, kube::runtime::reflector::ObjectRef, }; -use stackable_secret_operator_crd_utils::SecretReference; +use stackable_secret_operator_utils::crd::SecretReference; use tempfile::tempdir; use tokio::{ fs::File, diff --git a/rust/operator-binary/src/backend/tls/ca.rs b/rust/operator-binary/src/backend/tls/ca.rs index 1ee1d0d2..d7fdb2c8 100644 --- a/rust/operator-binary/src/backend/tls/ca.rs +++ b/rust/operator-binary/src/backend/tls/ca.rs @@ -32,7 +32,7 @@ use stackable_operator::{ }, shared::time::Duration, }; -use stackable_secret_operator_crd_utils::{ConfigMapReference, SecretReference}; +use stackable_secret_operator_utils::crd::{ConfigMapReference, SecretReference}; use time::OffsetDateTime; use tracing::{info, info_span, warn}; diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index 51a4d678..41b156f7 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -6,7 +6,7 @@ use stackable_operator::{ shared::time::Duration, versioned::versioned, }; -use stackable_secret_operator_crd_utils::{ConfigMapReference, SecretReference}; +use stackable_secret_operator_utils::crd::{ConfigMapReference, SecretReference}; use crate::format::SecretFormat; diff --git a/rust/operator-binary/src/format/convert.rs b/rust/operator-binary/src/format/convert.rs index d4cc3e79..a7d837e6 100644 --- a/rust/operator-binary/src/format/convert.rs +++ b/rust/operator-binary/src/format/convert.rs @@ -1,17 +1,14 @@ -use openssl::{ - error::ErrorStack as OpensslError, - pkcs12::Pkcs12, - pkey::PKey, - stack::Stack, - x509::{X509, X509Ref}, +use openssl::{pkcs12::Pkcs12, pkey::PKey, stack::Stack, x509::X509}; +use snafu::{ResultExt, Snafu}; +use stackable_secret_operator_utils::{ + pem::split_pem_certificates, + pkcs12::{TlsToPkcs12Error, pkcs12_truststore}, }; -use snafu::{OptionExt, ResultExt, Snafu}; use super::{ SecretFormat, WellKnownSecretData, well_known::{CompatibilityOptions, TlsPem, TlsPkcs12}, }; -use crate::format::utils::split_pem_certificates; pub fn convert( from: WellKnownSecretData, @@ -52,7 +49,7 @@ pub fn convert_tls_to_pkcs12( pem: TlsPem, p12_password: &str, ) -> Result { - use tls_to_pkcs12_error::*; + use stackable_secret_operator_utils::pkcs12::tls_to_pkcs12_error::*; let cert = pem .certificate_pem .map(|cert| X509::from_pem(&cert).context(LoadCertSnafu)) @@ -85,119 +82,3 @@ pub fn convert_tls_to_pkcs12( .transpose()?, }) } - -fn bmp_string(s: &str) -> Vec { - s.encode_utf16() - .chain([0]) // null-termination character - .flat_map(u16::to_be_bytes) - .collect() -} - -fn pkcs12_truststore<'a>( - ca_list: impl IntoIterator, - p12_password: &str, -) -> Result, TlsToPkcs12Error> { - // We can't use OpenSSL's `Pkcs12`, since it doesn't let us add new attributes to the SafeBags being created, - // and Java refuses to trust CA bags without the `java_trusted_ca_oid` attribute set. - // OpenSSL's current master branch contains the `PKCS12_create_ex2` function - // (https://www.openssl.org/docs/manmaster/man3/PKCS12_create_ex.html), but it is not currently in - // OpenSSL 3.1 (as of 3.1.1), and it is not wrapped by rust-openssl. - - // Required for Java to trust the certificate, from - // https://github.com/openjdk/jdk/blob/990e3a700dce3441bd9506ca571c1790e57849a9/src/java.base/share/classes/sun/security/util/KnownOIDs.java#L414-L415 - let java_oracle_trusted_key_usage_oid = - yasna::models::ObjectIdentifier::from_slice(&[2, 16, 840, 1, 113894, 746875, 1, 1]); - - // We don't care about actually encrypting the truststore securely, but if we use a random salt then the pkcs#12 bundle will be different for every write - // (=> TrustStore controller will get stuck reconciling indefinitely.) - // So let's just use a fixed salt instead. - struct DummyRng; - impl p12::Rng for DummyRng { - fn generate_salt(&mut self) -> Option<[u8; 8]> { - Some([0; 8]) - } - } - - let mut truststore_bags = Vec::new(); - for ca in ca_list { - truststore_bags.push(p12::SafeBag { - bag: p12::SafeBagKind::CertBag(p12::CertBag::X509( - ca.to_der() - .context(tls_to_pkcs12_error::SerializeCaForTruststoreSnafu)?, - )), - attributes: vec![p12::PKCS12Attribute::Other(p12::OtherAttribute { - oid: java_oracle_trusted_key_usage_oid.clone(), - data: Vec::new(), - })], - }); - } - let password_as_bmp_string = bmp_string(p12_password); - let encrypted_data = p12::ContentInfo::EncryptedData( - p12::EncryptedData::from_safe_bags( - &truststore_bags[..], - &password_as_bmp_string, - &mut DummyRng, - ) - .context(tls_to_pkcs12_error::EncryptDataForTruststoreSnafu)?, - ); - let truststore_data = yasna::construct_der(|w| { - w.write_sequence_of(|w| { - encrypted_data.write(w.next()); - }); - }); - Ok(p12::PFX { - version: 3, - mac_data: Some(p12::MacData::new( - &truststore_data, - &password_as_bmp_string, - &mut DummyRng, - )), - auth_safe: p12::ContentInfo::Data(truststore_data), - } - .to_der()) -} - -#[derive(Snafu, Debug)] -#[snafu(module)] -pub enum TlsToPkcs12Error { - #[snafu(display("failed to load certificate"))] - LoadCert { source: OpensslError }, - - #[snafu(display("failed to load private key"))] - LoadKey { source: OpensslError }, - - #[snafu(display("failed to load CA certificate"))] - LoadCa { source: OpensslError }, - - #[snafu(display("failed to build keystore"))] - BuildKeystore { source: OpensslError }, - - #[snafu(display("failed to serialize CA certificate for truststore"))] - SerializeCaForTruststore { source: OpensslError }, - - #[snafu(display("failed to encrypt data for truststore"))] - EncryptDataForTruststore, -} - -#[cfg(test)] -mod tests { - use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa, x509::X509}; - - use crate::format::convert::pkcs12_truststore; - - #[test] - fn pkcs12_truststore_should_be_deterministic() -> anyhow::Result<()> { - let pkey = PKey::try_from(Rsa::generate(2048)?)?; - let mut x509 = X509::builder()?; - x509.set_pubkey(&pkey)?; - x509.set_version(3 - 1)?; - x509.sign(&pkey, MessageDigest::sha256())?; - let cert = x509.build(); - let password = ""; - assert_eq!( - pkcs12_truststore([cert.as_ref()], password)?, - pkcs12_truststore([cert.as_ref()], password)?, - ); - Ok(()) - } -} diff --git a/rust/operator-binary/src/format/mod.rs b/rust/operator-binary/src/format/mod.rs index 9995a5f2..7a268b9c 100644 --- a/rust/operator-binary/src/format/mod.rs +++ b/rust/operator-binary/src/format/mod.rs @@ -10,7 +10,6 @@ pub use self::{ use crate::format::well_known::NamingOptions; mod convert; -mod utils; pub mod well_known; pub type SecretFiles = HashMap>; diff --git a/rust/operator-binary/src/format/utils.rs b/rust/operator-binary/src/format/utils.rs deleted file mode 100644 index 6799f5e4..00000000 --- a/rust/operator-binary/src/format/utils.rs +++ /dev/null @@ -1,60 +0,0 @@ -/// Splits a byte sequence of PEM-encoded certificates. -pub fn split_pem_certificates(pem: &[u8]) -> impl Iterator { - SplitPemCertificates { - pem_iter: pem.iter(), - } -} -struct SplitPemCertificates<'a> { - pem_iter: std::slice::Iter<'a, u8>, -} -impl<'a> Iterator for SplitPemCertificates<'a> { - type Item = &'a [u8]; - - fn next(&mut self) -> Option { - const HEADER: &[u8] = b"-----BEGIN CERTIFICATE-----"; - let slice = self.pem_iter.as_slice(); - if slice.is_empty() { - return None; - } - let mut len = 0; - while let Some(chr) = self.pem_iter.next() { - len += 1; - if *chr == b'\n' && self.pem_iter.as_slice().starts_with(HEADER) { - break; - } - } - Some(&slice[..len]) - } -} - -#[cfg(test)] -mod tests { - use crate::format::utils::split_pem_certificates; - - #[test] - fn test_split_pem_certificates() { - assert_eq!( - split_pem_certificates( - b"-----BEGIN CERTIFICATE----- -foo ------BEGIN CERTIFICATE----- -bar ------BEGIN CERTIFICATE----- -baz -" - ) - .collect::>(), - vec![ - b"-----BEGIN CERTIFICATE----- -foo -", - b"-----BEGIN CERTIFICATE----- -bar -", - b"-----BEGIN CERTIFICATE----- -baz -", - ] - ) - } -} diff --git a/rust/truststore-merger/Cargo.toml b/rust/truststore-merger/Cargo.toml new file mode 100644 index 00000000..5cce2a48 --- /dev/null +++ b/rust/truststore-merger/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "stackable-truststore-merger" +description = "A CLI tool to merge two pkcs12 truststores in such as way that they are accepted by the JVM" +version.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true +repository.workspace = true +publish = false + +[dependencies] +stackable-secret-operator-utils = { path = "../utils" } + +anyhow.workspace = true +clap = { workspace = true, features = ["derive"] } +openssl.workspace = true +tracing.workspace = true +tracing-subscriber = { workspace = true, features = ["env-filter"] } diff --git a/rust/truststore-merger/src/cli_args.rs b/rust/truststore-merger/src/cli_args.rs new file mode 100644 index 00000000..e4b33d7c --- /dev/null +++ b/rust/truststore-merger/src/cli_args.rs @@ -0,0 +1,162 @@ +use std::{ + fs, + io::Write, + path::PathBuf, + process::{Command, Stdio}, +}; + +use anyhow::{Context, bail}; +use clap::Parser; +use openssl::{pkcs12::Pkcs12, x509::X509}; +use stackable_secret_operator_utils::pem::split_pem_certificates; + +#[derive(Parser, Debug)] +#[command(version, about)] +pub struct Cli { + /// The path to output the resulting pkcs12 to + #[arg(long)] + pub out: PathBuf, + + /// List of PEM certificate(s) + #[arg(long = "pem")] + pub pems: Vec, + + /// List of PKCS12 certificate(s) + #[arg(long = "pkcs12")] + pub pkcs12s: Vec, +} +impl Cli { + pub fn certificate_sources(&self) -> Vec { + let pems = self.pems.iter().cloned().map(CertInput::Pem); + let pkcs12s = self.pkcs12s.iter().cloned().map(CertInput::Pkcs12); + pems.chain(pkcs12s).collect() + } +} + +#[derive(Debug)] +pub enum CertInput { + Pem(PathBuf), + Pkcs12(PathBuf), +} + +impl CertInput { + pub fn read(&self) -> anyhow::Result> { + let file_contents = + fs::read(self.path()).with_context(|| format!("failed to read file from {self:?}"))?; + + match self { + CertInput::Pem(_) => parse_pem_contents(&file_contents).with_context(|| { + format!( + "failed to parse PEM contents from {path:?}", + path = self.path() + ) + }), + CertInput::Pkcs12(_) => { + let password = ""; // TODO + parse_pkcs12_file_workaround(&file_contents, password) + } + } + } + + pub fn path(&self) -> &PathBuf { + match self { + CertInput::Pem(path) => path, + CertInput::Pkcs12(path) => path, + } + } +} + +fn parse_pem_contents(pem_bytes: &[u8]) -> anyhow::Result> { + let pems = split_pem_certificates(pem_bytes); + pems.into_iter() + .map(|pem| X509::from_pem(pem).context("failed to parse PEM encoded certificate")) + .collect() +} + +/// This function is how we would *should* do it. +/// +/// But with legacy old truststores generated by secret-operator (as of 2025-09), this fails with OpenSSL 3 because it +/// removed the old, legacy algorithms: +/// +/// `error:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupported:crypto/evp/evp_fetch.c:355:Global default library context, Algorithm (RC2-40-CBC : 0), Properties ()` +/// +/// I tried this code to load the legacy provider: +/// +/// ```ignore +/// const LEGACY_PROVIDER_NAME: &str = "legacy"; +/// +/// unsafe { +/// let provider_name = +/// std::ffi::CString::new(LEGACY_PROVIDER_NAME).expect("constant CString is always valid"); +/// let provider = ffi::OSSL_PROVIDER_load(ptr::null_mut(), provider_name.as_ptr()); +/// if provider.is_null() { +/// bail!("Failed to load OpenSSL provider {LEGACY_PROVIDER_NAME}"); +/// } +/// } +/// ``` +/// +/// It helped a bit, but we got the next error: +/// +/// `error:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupported:crypto/evp/evp_fetch.c:375:Global default library context, Algorithm (PKCS12KDF : 0), Properties (), error:1180006B:PKCS12 routines:pkcs12_gen_mac:key gen error:crypto/pkcs12/p12_mutl.c:267:, error:1180006D:PKCS12 routines:PKCS12_verify_mac:mac generation error:crypto/pkcs12/p12_mutl.c:331:, error:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupported:crypto/evp/evp_fetch.c:375:Global default library context, Algorithm (PKCS12KDF : 0), Properties (), error:1180006B:PKCS12 routines:pkcs12_gen_mac:key gen error:crypto/pkcs12/p12_mutl.c:267:, error:1180006D:PKCS12 routines:PKCS12_verify_mac:mac generation error:crypto/pkcs12/p12_mutl.c:331:, error:11800071:PKCS12 routines:PKCS12_parse:mac verify failure:crypto/pkcs12/p12_kiss.c:67:` +/// +/// So I ditched that effort and we are now shelling out to the CLI. Sorry! +/// The proper solution would be that secret-operator writes pkcs12 truststores using modern algorithms. +#[allow(unused)] +fn parse_pkcs12_file(file_contents: &[u8], password: &str) -> anyhow::Result> { + let parsed = Pkcs12::from_der(file_contents) + .context("failed to parse PKCS12 DER encoded file")? + .parse2(password) + .context("Failed to parse PKCS12 using the provided password")?; + + parsed + .ca + .context("pkcs12 truststore did not contain a CA")? + .into_iter() + .map(Ok) + .collect() +} + +/// Workaround for [`parse_pkcs12_file`]. Please read it's documentation for details. +/// +/// Yes, I hate it as well... +fn parse_pkcs12_file_workaround(file_contents: &[u8], password: &str) -> anyhow::Result> { + let mut child = Command::new("openssl") + .args(&[ + "pkcs12", + "-nokeys", + "-password", + &format!("pass:{}", password), + // That's the important part!!! + "-legacy", + ]) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .context("Failed to spawn openssl process")?; + + { + let stdin = child + .stdin + .as_mut() + .context("Failed to open openssl process stdin")?; + stdin + .write_all(file_contents) + .context("Failed to write PKCS12 data to openssl process stdin")?; + } + + let output = child + .wait_with_output() + .context("Failed to read openssl process output")?; + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + bail!("openssl process failed with STDERR: {stderr:?}"); + } + + parse_pem_contents(&output.stdout).with_context(|| { + format!( + "failed to parse openssl process output, which should be PEM. STDOUT: {stdout}?", + stdout = String::from_utf8_lossy(&output.stdout) + ) + }) +} diff --git a/rust/truststore-merger/src/main.rs b/rust/truststore-merger/src/main.rs new file mode 100644 index 00000000..5a47badc --- /dev/null +++ b/rust/truststore-merger/src/main.rs @@ -0,0 +1,84 @@ +use std::{collections::HashMap, fs}; + +use anyhow::{Context, ensure}; +use clap::Parser; +use cli_args::Cli; +use openssl::x509::X509; +use stackable_secret_operator_utils::pkcs12::pkcs12_truststore; +use tracing::{info, level_filters::LevelFilter, warn}; + +mod cli_args; + +pub fn main() -> anyhow::Result<()> { + let filter = tracing_subscriber::EnvFilter::builder() + .with_default_directive(LevelFilter::INFO.into()) + .from_env()?; + tracing_subscriber::fmt() + // Short running tool does not need any complex output + .with_target(false) + .without_time() + .with_env_filter(filter) + .init(); + + let cli = Cli::parse(); + + let certificate_sources = cli.certificate_sources(); + ensure!( + !certificate_sources.is_empty(), + "The list of certificate sources can not be empty. Please provide at least on --pem or --pkcs12." + ); + let certificate_sources = certificate_sources + .iter() + .map(|source| { + let certificate = source.read().with_context(|| { + format!( + "failed to read certificate source {path:?}", + path = source.path() + ) + })?; + Ok((source, certificate)) + }) + .collect::>>()?; + + let mut certificates = HashMap::, X509>::new(); + for (source, certificates_list) in certificate_sources.into_iter() { + for certificate in certificates_list { + let serial_bn = certificate + .serial_number() + .to_bn() + .context("failed to get certificate serial number as BigNumber")?; + let serial = serial_bn.to_vec(); + if let Some(existing) = certificates.get(&serial) { + warn!( + serial = ?serial_bn.to_hex_str(), + ?source, + existing.not_after = ?existing.not_after(), + existing.subject = ?existing.subject_name(), + new.not_after = ?certificate.not_after(), + new.subject = ?certificate.subject_name(), + "Skipped certificate as it was already added", + ); + } else { + info!( + serial = ?serial_bn.to_hex_str(), + not_after = ?certificate.not_after(), + subject = ?certificate.subject_name(), + ?source, + "Added certificate" + ); + certificates.insert(serial, certificate); + } + } + } + + let pkcs12_truststore_bytes = pkcs12_truststore(certificates.values().map(|c| &**c), "") + .context("failed to create pkcs12 truststore from certificates")?; + fs::write(&cli.out, &pkcs12_truststore_bytes).with_context(|| { + format!( + "failed to write to output pkcs12 truststore at {:?}", + cli.out + ) + })?; + + Ok(()) +} diff --git a/rust/utils/Cargo.toml b/rust/utils/Cargo.toml new file mode 100644 index 00000000..f24b7049 --- /dev/null +++ b/rust/utils/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "stackable-secret-operator-utils" +version.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true +repository.workspace = true +publish = false + +[features] +default = [] +crd = ["dep:stackable-operator", "dep:serde"] + +[dependencies] +p12 = { path = "../p12" } +stackable-operator = { workspace = true, optional = true } + +openssl.workspace = true +serde = { workspace = true, optional = true } +snafu.workspace = true +yasna.workspace = true + +[dev-dependencies] +anyhow.workspace = true diff --git a/rust/crd-utils/src/lib.rs b/rust/utils/src/crd.rs similarity index 100% rename from rust/crd-utils/src/lib.rs rename to rust/utils/src/crd.rs diff --git a/rust/utils/src/lib.rs b/rust/utils/src/lib.rs new file mode 100644 index 00000000..6511a347 --- /dev/null +++ b/rust/utils/src/lib.rs @@ -0,0 +1,5 @@ +pub mod pem; +pub mod pkcs12; + +#[cfg(feature = "crd")] +pub mod crd; diff --git a/rust/utils/src/pem.rs b/rust/utils/src/pem.rs new file mode 100644 index 00000000..529ac4b1 --- /dev/null +++ b/rust/utils/src/pem.rs @@ -0,0 +1,169 @@ +/// Splits a byte sequence of PEM-encoded certificates. +/// +/// It can tolerate additional contents between the actual PEM certificates, as e.g. the +/// `openssl pkcs12` command produces. +pub fn split_pem_certificates(pem: &[u8]) -> Vec<&[u8]> { + const HEADER: &[u8] = b"-----BEGIN CERTIFICATE-----"; + const FOOTER: &[u8] = b"-----END CERTIFICATE-----"; + + let mut certs = Vec::new(); + let mut pos = 0; + + while pos + HEADER.len() <= pem.len() { + // Find the next header + if &pem[pos..pos + HEADER.len()] != HEADER { + pos += 1; + continue; + } + + let start = pos; + pos += HEADER.len(); + + // Find the matching footer + while pos + FOOTER.len() <= pem.len() { + if &pem[pos..pos + FOOTER.len()] == FOOTER { + pos += FOOTER.len(); // include footer + certs.push(&pem[start..pos]); + break; + } + pos += 1; + } + } + + certs +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_split_pem_certificates() { + assert_eq!( + split_pem_certificates( + b"-----BEGIN CERTIFICATE----- +foo +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +bar +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +baz +-----END CERTIFICATE----- +" + ), + vec![ + b"-----BEGIN CERTIFICATE----- +foo +-----END CERTIFICATE-----", + b"-----BEGIN CERTIFICATE----- +bar +-----END CERTIFICATE-----", + b"-----BEGIN CERTIFICATE----- +baz +-----END CERTIFICATE-----", + ] + ) + } + + #[test] + fn test_split_openssl_cli_pkcs12_output() { + // openssl pkcs12 -in truststore.p12 -password pass: -nokeys -legacy + let cli_output = b" +Bag Attributes + Trusted key usage (Oracle): +subject=CN=secret-operator self-signed +issuer=CN=secret-operator self-signed +-----BEGIN CERTIFICATE----- +MIIDGzCCAgOgAwIBAgIIKt7H+4AWKFYwDQYJKoZIhvcNAQELBQAwJjEkMCIGA1UE +Awwbc2VjcmV0LW9wZXJhdG9yIHNlbGYtc2lnbmVkMB4XDTI1MDkwMzExMDIwMVoX +DTI1MDkwMzExMDgwMVowJjEkMCIGA1UEAwwbc2VjcmV0LW9wZXJhdG9yIHNlbGYt +c2lnbmVkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAviEC2WtidVLN +qU6BO8qQ3PPThYBfia6UbfU8y5k8qPKHOJhYjtCKPTqCD82ht/UgzoXJ4zzqKL9B +2cBid+zj3/fxSRDKaPBMQvthC13M6zOz5ig/Ry24iaIaiz5ASDuqaQ9Hw/Y7viPB +pxkypTR59tYHa4+1D8xPtQCUixpxgxfRPAehZibrlP8TZrb6wSEjuicXljh9pevn +jw/TxFcNZVHgDw2N6RqhgaurcS/i4ScWxELXrdqi1K6G2twcWw2SPiU3xujXAMG7 +lGISeJJnecD/rHzMT13TYqmbu65tSrVfG9YRqbGqgMfk5faFzCoIZZ447OA2coE7 +JA/CJ3djVQIDAQABo00wSzAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQ1LwpK +L93s9BJgZpJ6vRnX2SzzMzAJBgNVHSMEAjAAMA4GA1UdDwEB/wQEAwIBhjANBgkq +hkiG9w0BAQsFAAOCAQEADOrNsdHBc4qf8wJNmm2RIRaepJDxsMiCmEh9A7JLFZEb +IsOIbynhAvfHCCZqSDeRNaMdxjKax6STy6QDy/U5jrwAFS1/1yFR7zqGE9IDOmrs +5uxDCYU23p1fIjvr+Uz7lk/EwXVTtqjzLAp+NP4YIQNFWfdKv6me3F2Czz4yfTRj +sZ5ggeW3l6nFHGTDkdXqGs9BSTvckUVIUV6o0x2Opl05gS4TrxiTYpVYK0a3ofib +nHJm5NEUs17nlq9n5u3zP49d0WEpoEseahRBBp7/coC/yc4M3JHnEHccW/zjZ2U0 +khi8URKEGEx1aL1Uu8D3Xd8BLXOjDjZWn8A0hznRGQ== +-----END CERTIFICATE----- +Bag Attributes + Trusted key usage (Oracle): +subject=CN=secret-operator self-signed +issuer=CN=secret-operator self-signed +-----BEGIN CERTIFICATE----- +MIIDGzCCAgOgAwIBAgIIDkvHj4cwRngwDQYJKoZIhvcNAQELBQAwJjEkMCIGA1UE +Awwbc2VjcmV0LW9wZXJhdG9yIHNlbGYtc2lnbmVkMB4XDTI1MDkwMzExMDI0MFoX +DTI1MDkwMzExMDg0MFowJjEkMCIGA1UEAwwbc2VjcmV0LW9wZXJhdG9yIHNlbGYt +c2lnbmVkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp5EBsqaJItLd +3UtgC9bWv3VIprp3+FQPobQCMQpW7jne4gC11QxBuuIDN+LqlAZSfwt8UHy5B/LW +MUzgu+kfvXxZkYZsKmGNDi9GSH/fPkOV+rBDG6BOsXdQCmSPJZjCLuNWuysYfI3L +CQVoV0rG8H+APrX7N3vLosph3chYWgwb0teaKlqlGROVFiFISuMezSdyCkJbHkXB +Qjicj96FGa+jJX9zULJt07AWl2wsFbCL/+bDyOQs4LNQ+yQnhxXhepo4M9haLxrM +sd6JvmeCKNf17OSVe4a1rGc0hpZ+80AJ3D+cfMBoBPGkAk7njAI7HzpqNd/qFZo+ +elfX3v4PQwIDAQABo00wSzAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQ3k3Wa +ZJsuVxSQaKZMFbmEviqTlzAJBgNVHSMEAjAAMA4GA1UdDwEB/wQEAwIBhjANBgkq +hkiG9w0BAQsFAAOCAQEAe851UwqPYYxKsCbOsIVqe9aamPOQ+70PNaEALWfkEpVS +kbJcsjw7m4wmG0m/Y4fTL+sdppYhShSPOV5vn09mW04hjOlaKRUyIGhkp9qaTIFh +bznnQ19zvnfHci8rs1LCIgjGL+iZ8aoUE8nyOeSbm1A8Yc0NcKw/WdC+MJ3jyFqe +hjfFpCCYu94nKhCQ5RhCfQBHmZ/IzwxTSDUUE3PoD2g4Rex9etISPY1CV5cJBjgv +VYIk9WlwUc6mepdY3CX/Oko6WEilm2y1zdKohGrsEnTeN6oG2l9XFdvqj71UBNop +iVAldveMLcVOv2D9jU48lYJFRagJc6wpCBOK0/Exjg== +-----END CERTIFICATE----- + "; + + assert_eq!( + split_pem_certificates(cli_output) + .iter() + .map(|bytes| String::from_utf8(bytes.to_vec()) + .expect("PEM certificate is not valid utf-8")) + .collect::>(), + vec![ + "-----BEGIN CERTIFICATE----- +MIIDGzCCAgOgAwIBAgIIKt7H+4AWKFYwDQYJKoZIhvcNAQELBQAwJjEkMCIGA1UE +Awwbc2VjcmV0LW9wZXJhdG9yIHNlbGYtc2lnbmVkMB4XDTI1MDkwMzExMDIwMVoX +DTI1MDkwMzExMDgwMVowJjEkMCIGA1UEAwwbc2VjcmV0LW9wZXJhdG9yIHNlbGYt +c2lnbmVkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAviEC2WtidVLN +qU6BO8qQ3PPThYBfia6UbfU8y5k8qPKHOJhYjtCKPTqCD82ht/UgzoXJ4zzqKL9B +2cBid+zj3/fxSRDKaPBMQvthC13M6zOz5ig/Ry24iaIaiz5ASDuqaQ9Hw/Y7viPB +pxkypTR59tYHa4+1D8xPtQCUixpxgxfRPAehZibrlP8TZrb6wSEjuicXljh9pevn +jw/TxFcNZVHgDw2N6RqhgaurcS/i4ScWxELXrdqi1K6G2twcWw2SPiU3xujXAMG7 +lGISeJJnecD/rHzMT13TYqmbu65tSrVfG9YRqbGqgMfk5faFzCoIZZ447OA2coE7 +JA/CJ3djVQIDAQABo00wSzAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQ1LwpK +L93s9BJgZpJ6vRnX2SzzMzAJBgNVHSMEAjAAMA4GA1UdDwEB/wQEAwIBhjANBgkq +hkiG9w0BAQsFAAOCAQEADOrNsdHBc4qf8wJNmm2RIRaepJDxsMiCmEh9A7JLFZEb +IsOIbynhAvfHCCZqSDeRNaMdxjKax6STy6QDy/U5jrwAFS1/1yFR7zqGE9IDOmrs +5uxDCYU23p1fIjvr+Uz7lk/EwXVTtqjzLAp+NP4YIQNFWfdKv6me3F2Czz4yfTRj +sZ5ggeW3l6nFHGTDkdXqGs9BSTvckUVIUV6o0x2Opl05gS4TrxiTYpVYK0a3ofib +nHJm5NEUs17nlq9n5u3zP49d0WEpoEseahRBBp7/coC/yc4M3JHnEHccW/zjZ2U0 +khi8URKEGEx1aL1Uu8D3Xd8BLXOjDjZWn8A0hznRGQ== +-----END CERTIFICATE-----", + "-----BEGIN CERTIFICATE----- +MIIDGzCCAgOgAwIBAgIIDkvHj4cwRngwDQYJKoZIhvcNAQELBQAwJjEkMCIGA1UE +Awwbc2VjcmV0LW9wZXJhdG9yIHNlbGYtc2lnbmVkMB4XDTI1MDkwMzExMDI0MFoX +DTI1MDkwMzExMDg0MFowJjEkMCIGA1UEAwwbc2VjcmV0LW9wZXJhdG9yIHNlbGYt +c2lnbmVkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp5EBsqaJItLd +3UtgC9bWv3VIprp3+FQPobQCMQpW7jne4gC11QxBuuIDN+LqlAZSfwt8UHy5B/LW +MUzgu+kfvXxZkYZsKmGNDi9GSH/fPkOV+rBDG6BOsXdQCmSPJZjCLuNWuysYfI3L +CQVoV0rG8H+APrX7N3vLosph3chYWgwb0teaKlqlGROVFiFISuMezSdyCkJbHkXB +Qjicj96FGa+jJX9zULJt07AWl2wsFbCL/+bDyOQs4LNQ+yQnhxXhepo4M9haLxrM +sd6JvmeCKNf17OSVe4a1rGc0hpZ+80AJ3D+cfMBoBPGkAk7njAI7HzpqNd/qFZo+ +elfX3v4PQwIDAQABo00wSzAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQ3k3Wa +ZJsuVxSQaKZMFbmEviqTlzAJBgNVHSMEAjAAMA4GA1UdDwEB/wQEAwIBhjANBgkq +hkiG9w0BAQsFAAOCAQEAe851UwqPYYxKsCbOsIVqe9aamPOQ+70PNaEALWfkEpVS +kbJcsjw7m4wmG0m/Y4fTL+sdppYhShSPOV5vn09mW04hjOlaKRUyIGhkp9qaTIFh +bznnQ19zvnfHci8rs1LCIgjGL+iZ8aoUE8nyOeSbm1A8Yc0NcKw/WdC+MJ3jyFqe +hjfFpCCYu94nKhCQ5RhCfQBHmZ/IzwxTSDUUE3PoD2g4Rex9etISPY1CV5cJBjgv +VYIk9WlwUc6mepdY3CX/Oko6WEilm2y1zdKohGrsEnTeN6oG2l9XFdvqj71UBNop +iVAldveMLcVOv2D9jU48lYJFRagJc6wpCBOK0/Exjg== +-----END CERTIFICATE-----" + ] + ); + } +} diff --git a/rust/utils/src/pkcs12.rs b/rust/utils/src/pkcs12.rs new file mode 100644 index 00000000..a0958eeb --- /dev/null +++ b/rust/utils/src/pkcs12.rs @@ -0,0 +1,118 @@ +use openssl::{error::ErrorStack as OpensslError, x509::X509Ref}; +use snafu::{OptionExt, ResultExt, Snafu}; + +#[derive(Snafu, Debug)] +#[snafu(module, visibility(pub))] +pub enum TlsToPkcs12Error { + #[snafu(display("failed to load certificate"))] + LoadCert { source: OpensslError }, + + #[snafu(display("failed to load private key"))] + LoadKey { source: OpensslError }, + + #[snafu(display("failed to load CA certificate"))] + LoadCa { source: OpensslError }, + + #[snafu(display("failed to build keystore"))] + BuildKeystore { source: OpensslError }, + + #[snafu(display("failed to serialize CA certificate for truststore"))] + SerializeCaForTruststore { source: OpensslError }, + + #[snafu(display("failed to encrypt data for truststore"))] + EncryptDataForTruststore, +} + +pub fn pkcs12_truststore<'a>( + ca_list: impl IntoIterator, + p12_password: &str, +) -> Result, TlsToPkcs12Error> { + // We can't use OpenSSL's `Pkcs12`, since it doesn't let us add new attributes to the SafeBags being created, + // and Java refuses to trust CA bags without the `java_trusted_ca_oid` attribute set. + // OpenSSL's current master branch contains the `PKCS12_create_ex2` function + // (https://www.openssl.org/docs/manmaster/man3/PKCS12_create_ex.html), but it is not currently in + // OpenSSL 3.1 (as of 3.1.1), and it is not wrapped by rust-openssl. + + // Required for Java to trust the certificate, from + // https://github.com/openjdk/jdk/blob/990e3a700dce3441bd9506ca571c1790e57849a9/src/java.base/share/classes/sun/security/util/KnownOIDs.java#L414-L415 + let java_oracle_trusted_key_usage_oid = + yasna::models::ObjectIdentifier::from_slice(&[2, 16, 840, 1, 113894, 746875, 1, 1]); + + // We don't care about actually encrypting the truststore securely, but if we use a random salt then the pkcs#12 bundle will be different for every write + // (=> TrustStore controller will get stuck reconciling indefinitely.) + // So let's just use a fixed salt instead. + struct DummyRng; + impl p12::Rng for DummyRng { + fn generate_salt(&mut self) -> Option<[u8; 8]> { + Some([0; 8]) + } + } + + let mut truststore_bags = Vec::new(); + for ca in ca_list { + truststore_bags.push(p12::SafeBag { + bag: p12::SafeBagKind::CertBag(p12::CertBag::X509( + ca.to_der() + .context(tls_to_pkcs12_error::SerializeCaForTruststoreSnafu)?, + )), + attributes: vec![p12::PKCS12Attribute::Other(p12::OtherAttribute { + oid: java_oracle_trusted_key_usage_oid.clone(), + data: Vec::new(), + })], + }); + } + let password_as_bmp_string = bmp_string(p12_password); + let encrypted_data = p12::ContentInfo::EncryptedData( + p12::EncryptedData::from_safe_bags( + &truststore_bags[..], + &password_as_bmp_string, + &mut DummyRng, + ) + .context(tls_to_pkcs12_error::EncryptDataForTruststoreSnafu)?, + ); + let truststore_data = yasna::construct_der(|w| { + w.write_sequence_of(|w| { + encrypted_data.write(w.next()); + }); + }); + Ok(p12::PFX { + version: 3, + mac_data: Some(p12::MacData::new( + &truststore_data, + &password_as_bmp_string, + &mut DummyRng, + )), + auth_safe: p12::ContentInfo::Data(truststore_data), + } + .to_der()) +} + +fn bmp_string(s: &str) -> Vec { + s.encode_utf16() + .chain([0]) // null-termination character + .flat_map(u16::to_be_bytes) + .collect() +} + +#[cfg(test)] +mod tests { + use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa, x509::X509}; + + use super::*; + + #[test] + fn pkcs12_truststore_should_be_deterministic() -> anyhow::Result<()> { + let pkey = PKey::try_from(Rsa::generate(2048)?)?; + let mut x509 = X509::builder()?; + x509.set_pubkey(&pkey)?; + x509.set_version(3 - 1)?; + x509.sign(&pkey, MessageDigest::sha256())?; + let cert = x509.build(); + let password = ""; + assert_eq!( + pkcs12_truststore([cert.as_ref()], password)?, + pkcs12_truststore([cert.as_ref()], password)?, + ); + Ok(()) + } +} From 2f4e3e02b5fef570aec040e329f8dba9dd074b1f Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Thu, 4 Sep 2025 11:26:29 +0200 Subject: [PATCH 02/24] Support specifing truststore passwords --- rust/truststore-merger/Cargo.toml | 2 +- rust/truststore-merger/src/cli_args.rs | 161 +++++++------------------ rust/truststore-merger/src/main.rs | 8 +- rust/truststore-merger/src/parsers.rs | 106 ++++++++++++++++ 4 files changed, 156 insertions(+), 121 deletions(-) create mode 100644 rust/truststore-merger/src/parsers.rs diff --git a/rust/truststore-merger/Cargo.toml b/rust/truststore-merger/Cargo.toml index 5cce2a48..99391011 100644 --- a/rust/truststore-merger/Cargo.toml +++ b/rust/truststore-merger/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "stackable-truststore-merger" -description = "A CLI tool to merge two pkcs12 truststores in such as way that they are accepted by the JVM" +description = "A CLI tool to merge two truststores in PEM or PKCS12 format in such as way that they are accepted by the JVM" version.workspace = true authors.workspace = true license.workspace = true diff --git a/rust/truststore-merger/src/cli_args.rs b/rust/truststore-merger/src/cli_args.rs index e4b33d7c..1396bff8 100644 --- a/rust/truststore-merger/src/cli_args.rs +++ b/rust/truststore-merger/src/cli_args.rs @@ -1,30 +1,59 @@ -use std::{ - fs, - io::Write, - path::PathBuf, - process::{Command, Stdio}, -}; +use std::{fs, path::PathBuf}; -use anyhow::{Context, bail}; +use anyhow::Context; use clap::Parser; -use openssl::{pkcs12::Pkcs12, x509::X509}; -use stackable_secret_operator_utils::pem::split_pem_certificates; +use openssl::x509::X509; + +use crate::parsers::{parse_pem_contents, parse_pkcs12_file_workaround}; #[derive(Parser, Debug)] #[command(version, about)] pub struct Cli { - /// The path to output the resulting pkcs12 to + /// The path to output the resulting PKCS12 to #[arg(long)] pub out: PathBuf, + /// The password used to encrypt the outputted PKCS12 truststore. Defaults to an empty string. + #[arg(long, default_value = "")] + pub out_password: String, + /// List of PEM certificate(s) #[arg(long = "pem")] pub pems: Vec, - /// List of PKCS12 certificate(s) - #[arg(long = "pkcs12")] - pub pkcs12s: Vec, + /// List of PKCS12 truststore(s) + /// + /// You can either use `truststore.p12` (which uses an empty password by default), or specify + /// the password using `truststore.p12:changeit`. + #[arg(long = "pkcs12", value_parser = parse_cli_pkcs12_source)] + pub pkcs12s: Vec, +} + +#[derive(Debug)] +pub enum CertInput { + Pem(PathBuf), + Pkcs12(Pkcs12Source), +} + +#[derive(Clone, Debug)] +pub struct Pkcs12Source { + path: PathBuf, + password: String, +} + +fn parse_cli_pkcs12_source(cli_argument: &str) -> Result { + let mut parts = cli_argument.splitn(2, ':'); + let path = parts + .next() + .ok_or_else(|| "missing path part".to_string())?; + let password = parts.next().unwrap_or("").to_string(); + + Ok(Pkcs12Source { + path: PathBuf::from(path), + password, + }) } + impl Cli { pub fn certificate_sources(&self) -> Vec { let pems = self.pems.iter().cloned().map(CertInput::Pem); @@ -33,12 +62,6 @@ impl Cli { } } -#[derive(Debug)] -pub enum CertInput { - Pem(PathBuf), - Pkcs12(PathBuf), -} - impl CertInput { pub fn read(&self) -> anyhow::Result> { let file_contents = @@ -51,8 +74,7 @@ impl CertInput { path = self.path() ) }), - CertInput::Pkcs12(_) => { - let password = ""; // TODO + CertInput::Pkcs12(Pkcs12Source { password, .. }) => { parse_pkcs12_file_workaround(&file_contents, password) } } @@ -61,102 +83,7 @@ impl CertInput { pub fn path(&self) -> &PathBuf { match self { CertInput::Pem(path) => path, - CertInput::Pkcs12(path) => path, + CertInput::Pkcs12(Pkcs12Source { path, .. }) => path, } } } - -fn parse_pem_contents(pem_bytes: &[u8]) -> anyhow::Result> { - let pems = split_pem_certificates(pem_bytes); - pems.into_iter() - .map(|pem| X509::from_pem(pem).context("failed to parse PEM encoded certificate")) - .collect() -} - -/// This function is how we would *should* do it. -/// -/// But with legacy old truststores generated by secret-operator (as of 2025-09), this fails with OpenSSL 3 because it -/// removed the old, legacy algorithms: -/// -/// `error:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupported:crypto/evp/evp_fetch.c:355:Global default library context, Algorithm (RC2-40-CBC : 0), Properties ()` -/// -/// I tried this code to load the legacy provider: -/// -/// ```ignore -/// const LEGACY_PROVIDER_NAME: &str = "legacy"; -/// -/// unsafe { -/// let provider_name = -/// std::ffi::CString::new(LEGACY_PROVIDER_NAME).expect("constant CString is always valid"); -/// let provider = ffi::OSSL_PROVIDER_load(ptr::null_mut(), provider_name.as_ptr()); -/// if provider.is_null() { -/// bail!("Failed to load OpenSSL provider {LEGACY_PROVIDER_NAME}"); -/// } -/// } -/// ``` -/// -/// It helped a bit, but we got the next error: -/// -/// `error:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupported:crypto/evp/evp_fetch.c:375:Global default library context, Algorithm (PKCS12KDF : 0), Properties (), error:1180006B:PKCS12 routines:pkcs12_gen_mac:key gen error:crypto/pkcs12/p12_mutl.c:267:, error:1180006D:PKCS12 routines:PKCS12_verify_mac:mac generation error:crypto/pkcs12/p12_mutl.c:331:, error:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupported:crypto/evp/evp_fetch.c:375:Global default library context, Algorithm (PKCS12KDF : 0), Properties (), error:1180006B:PKCS12 routines:pkcs12_gen_mac:key gen error:crypto/pkcs12/p12_mutl.c:267:, error:1180006D:PKCS12 routines:PKCS12_verify_mac:mac generation error:crypto/pkcs12/p12_mutl.c:331:, error:11800071:PKCS12 routines:PKCS12_parse:mac verify failure:crypto/pkcs12/p12_kiss.c:67:` -/// -/// So I ditched that effort and we are now shelling out to the CLI. Sorry! -/// The proper solution would be that secret-operator writes pkcs12 truststores using modern algorithms. -#[allow(unused)] -fn parse_pkcs12_file(file_contents: &[u8], password: &str) -> anyhow::Result> { - let parsed = Pkcs12::from_der(file_contents) - .context("failed to parse PKCS12 DER encoded file")? - .parse2(password) - .context("Failed to parse PKCS12 using the provided password")?; - - parsed - .ca - .context("pkcs12 truststore did not contain a CA")? - .into_iter() - .map(Ok) - .collect() -} - -/// Workaround for [`parse_pkcs12_file`]. Please read it's documentation for details. -/// -/// Yes, I hate it as well... -fn parse_pkcs12_file_workaround(file_contents: &[u8], password: &str) -> anyhow::Result> { - let mut child = Command::new("openssl") - .args(&[ - "pkcs12", - "-nokeys", - "-password", - &format!("pass:{}", password), - // That's the important part!!! - "-legacy", - ]) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .context("Failed to spawn openssl process")?; - - { - let stdin = child - .stdin - .as_mut() - .context("Failed to open openssl process stdin")?; - stdin - .write_all(file_contents) - .context("Failed to write PKCS12 data to openssl process stdin")?; - } - - let output = child - .wait_with_output() - .context("Failed to read openssl process output")?; - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - bail!("openssl process failed with STDERR: {stderr:?}"); - } - - parse_pem_contents(&output.stdout).with_context(|| { - format!( - "failed to parse openssl process output, which should be PEM. STDOUT: {stdout}?", - stdout = String::from_utf8_lossy(&output.stdout) - ) - }) -} diff --git a/rust/truststore-merger/src/main.rs b/rust/truststore-merger/src/main.rs index 5a47badc..ce5ca33e 100644 --- a/rust/truststore-merger/src/main.rs +++ b/rust/truststore-merger/src/main.rs @@ -8,6 +8,7 @@ use stackable_secret_operator_utils::pkcs12::pkcs12_truststore; use tracing::{info, level_filters::LevelFilter, warn}; mod cli_args; +mod parsers; pub fn main() -> anyhow::Result<()> { let filter = tracing_subscriber::EnvFilter::builder() @@ -71,11 +72,12 @@ pub fn main() -> anyhow::Result<()> { } } - let pkcs12_truststore_bytes = pkcs12_truststore(certificates.values().map(|c| &**c), "") - .context("failed to create pkcs12 truststore from certificates")?; + let pkcs12_truststore_bytes = + pkcs12_truststore(certificates.values().map(|c| &**c), &cli.out_password) + .context("failed to create PKCS12 truststore from certificates")?; fs::write(&cli.out, &pkcs12_truststore_bytes).with_context(|| { format!( - "failed to write to output pkcs12 truststore at {:?}", + "failed to write to output PKCS12 truststore at {:?}", cli.out ) })?; diff --git a/rust/truststore-merger/src/parsers.rs b/rust/truststore-merger/src/parsers.rs new file mode 100644 index 00000000..d8e5a7d4 --- /dev/null +++ b/rust/truststore-merger/src/parsers.rs @@ -0,0 +1,106 @@ +use std::{ + io::Write, + process::{Command, Stdio}, +}; + +use anyhow::{Context, bail}; +use openssl::{pkcs12::Pkcs12, x509::X509}; +use stackable_secret_operator_utils::pem::split_pem_certificates; + +pub fn parse_pem_contents(pem_bytes: &[u8]) -> anyhow::Result> { + let pems = split_pem_certificates(pem_bytes); + pems.into_iter() + .map(|pem| X509::from_pem(pem).context("failed to parse PEM encoded certificate")) + .collect() +} + +/// This function is how we would *should* do it. +/// +/// But with legacy old truststores generated by secret-operator (as of 2025-09), this fails with OpenSSL 3 because it +/// removed the old, legacy algorithms: +/// +/// `error:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupported:crypto/evp/evp_fetch.c:355:Global default library context, Algorithm (RC2-40-CBC : 0), Properties ()` +/// +/// I tried this code to load the legacy provider: +/// +/// ```ignore +/// const LEGACY_PROVIDER_NAME: &str = "legacy"; +/// +/// unsafe { +/// let provider_name = +/// std::ffi::CString::new(LEGACY_PROVIDER_NAME).expect("constant CString is always valid"); +/// let provider = ffi::OSSL_PROVIDER_load(ptr::null_mut(), provider_name.as_ptr()); +/// if provider.is_null() { +/// bail!("Failed to load OpenSSL provider {LEGACY_PROVIDER_NAME}"); +/// } +/// } +/// ``` +/// +/// It helped a bit, but we got the next error: +/// +/// `error:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupported:crypto/evp/evp_fetch.c:375:Global default library context, Algorithm (PKCS12KDF : 0), Properties (), error:1180006B:PKCS12 routines:pkcs12_gen_mac:key gen error:crypto/pkcs12/p12_mutl.c:267:, error:1180006D:PKCS12 routines:PKCS12_verify_mac:mac generation error:crypto/pkcs12/p12_mutl.c:331:, error:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupported:crypto/evp/evp_fetch.c:375:Global default library context, Algorithm (PKCS12KDF : 0), Properties (), error:1180006B:PKCS12 routines:pkcs12_gen_mac:key gen error:crypto/pkcs12/p12_mutl.c:267:, error:1180006D:PKCS12 routines:PKCS12_verify_mac:mac generation error:crypto/pkcs12/p12_mutl.c:331:, error:11800071:PKCS12 routines:PKCS12_parse:mac verify failure:crypto/pkcs12/p12_kiss.c:67:` +/// +/// So I ditched that effort and we are now shelling out to the CLI. Sorry! +/// The proper solution would be that secret-operator writes PKCS12 truststores using modern algorithms. +#[allow(unused)] +pub fn parse_pkcs12_file(file_contents: &[u8], password: &str) -> anyhow::Result> { + let parsed = Pkcs12::from_der(file_contents) + .context("failed to parse PKCS12 DER encoded file")? + .parse2(password) + .context("Failed to parse PKCS12 using the provided password")?; + + parsed + .ca + .context("pkcs12 truststore did not contain a CA")? + .into_iter() + .map(Ok) + .collect() +} + +/// Workaround for [`parse_pkcs12_file`]. Please read it's documentation for details. +/// +/// Yes, I hate it as well... +pub fn parse_pkcs12_file_workaround( + file_contents: &[u8], + password: &str, +) -> anyhow::Result> { + let mut child = Command::new("openssl") + .args(&[ + "pkcs12", + "-nokeys", + "-password", + &format!("pass:{}", password), + // That's the important part!!! + "-legacy", + ]) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .context("Failed to spawn openssl process")?; + + { + let stdin = child + .stdin + .as_mut() + .context("Failed to open openssl process stdin")?; + stdin + .write_all(file_contents) + .context("Failed to write PKCS12 data to openssl process stdin")?; + } + + let output = child + .wait_with_output() + .context("Failed to read openssl process output")?; + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + bail!("openssl process failed with STDERR: {stderr:?}"); + } + + parse_pem_contents(&output.stdout).with_context(|| { + format!( + "failed to parse openssl process output, which should be PEM. STDOUT: {stdout}?", + stdout = String::from_utf8_lossy(&output.stdout) + ) + }) +} From 87e7ac390a1d1cabe9dc512097e7f28415247112 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 5 Sep 2025 11:11:22 +0200 Subject: [PATCH 03/24] Set version to 0.0.1 --- Cargo.lock | 2 +- rust/truststore-merger/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 29abbed4..4a962f21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3246,7 +3246,7 @@ dependencies = [ [[package]] name = "stackable-truststore-merger" -version = "0.0.0-dev" +version = "0.0.1" dependencies = [ "anyhow", "clap", diff --git a/rust/truststore-merger/Cargo.toml b/rust/truststore-merger/Cargo.toml index 99391011..0f70d9ee 100644 --- a/rust/truststore-merger/Cargo.toml +++ b/rust/truststore-merger/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "stackable-truststore-merger" description = "A CLI tool to merge two truststores in PEM or PKCS12 format in such as way that they are accepted by the JVM" -version.workspace = true +version = "0.0.1" authors.workspace = true license.workspace = true edition.workspace = true From 51c0f826a5c00ecd7a226bd0c85a5f284199ce51 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 5 Sep 2025 11:25:02 +0200 Subject: [PATCH 04/24] Rename to truststore-merger --- Cargo.lock | 24 ++++++++++++------------ rust/truststore-merger/Cargo.toml | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4a962f21..a3e64cbe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3244,18 +3244,6 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "stackable-truststore-merger" -version = "0.0.1" -dependencies = [ - "anyhow", - "clap", - "openssl", - "stackable-secret-operator-utils", - "tracing", - "tracing-subscriber", -] - [[package]] name = "stackable-versioned" version = "0.8.1" @@ -3819,6 +3807,18 @@ dependencies = [ "tracing-serde", ] +[[package]] +name = "truststore-merger" +version = "0.0.1" +dependencies = [ + "anyhow", + "clap", + "openssl", + "stackable-secret-operator-utils", + "tracing", + "tracing-subscriber", +] + [[package]] name = "try-lock" version = "0.2.5" diff --git a/rust/truststore-merger/Cargo.toml b/rust/truststore-merger/Cargo.toml index 0f70d9ee..b3708460 100644 --- a/rust/truststore-merger/Cargo.toml +++ b/rust/truststore-merger/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "stackable-truststore-merger" +name = "truststore-merger" description = "A CLI tool to merge two truststores in PEM or PKCS12 format in such as way that they are accepted by the JVM" version = "0.0.1" authors.workspace = true From 57f10a7e3928e793d705e001a26daf477969873c Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 5 Sep 2025 14:10:26 +0200 Subject: [PATCH 05/24] Add some doc comments --- rust/truststore-merger/src/parsers.rs | 1 + rust/utils/src/pkcs12.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/rust/truststore-merger/src/parsers.rs b/rust/truststore-merger/src/parsers.rs index d8e5a7d4..59dded3e 100644 --- a/rust/truststore-merger/src/parsers.rs +++ b/rust/truststore-merger/src/parsers.rs @@ -42,6 +42,7 @@ pub fn parse_pem_contents(pem_bytes: &[u8]) -> anyhow::Result> { /// /// So I ditched that effort and we are now shelling out to the CLI. Sorry! /// The proper solution would be that secret-operator writes PKCS12 truststores using modern algorithms. +/// For that we probably(?) drop the p12 crate? #[allow(unused)] pub fn parse_pkcs12_file(file_contents: &[u8], password: &str) -> anyhow::Result> { let parsed = Pkcs12::from_der(file_contents) diff --git a/rust/utils/src/pkcs12.rs b/rust/utils/src/pkcs12.rs index a0958eeb..4c6cde13 100644 --- a/rust/utils/src/pkcs12.rs +++ b/rust/utils/src/pkcs12.rs @@ -32,6 +32,7 @@ pub fn pkcs12_truststore<'a>( // OpenSSL's current master branch contains the `PKCS12_create_ex2` function // (https://www.openssl.org/docs/manmaster/man3/PKCS12_create_ex.html), but it is not currently in // OpenSSL 3.1 (as of 3.1.1), and it is not wrapped by rust-openssl. + // See https://github.com/sfackler/rust-openssl/blob/d21f42333698edeebd7327a4e25412b191757e31/openssl/src/pkcs12.rs#L225 // Required for Java to trust the certificate, from // https://github.com/openjdk/jdk/blob/990e3a700dce3441bd9506ca571c1790e57849a9/src/java.base/share/classes/sun/security/util/KnownOIDs.java#L414-L415 From 80d27275fd476b68c260d095729937bf2b5edb8b Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Mon, 8 Sep 2025 12:04:57 +0200 Subject: [PATCH 06/24] Detect duplicates by hash, not serial --- Cargo.lock | 7 +++++++ Cargo.toml | 1 + rust/truststore-merger/Cargo.toml | 1 + rust/truststore-merger/src/cert_ext.rs | 26 ++++++++++++++++++++++++++ rust/truststore-merger/src/main.rs | 26 +++++++++++++++----------- 5 files changed, 50 insertions(+), 11 deletions(-) create mode 100644 rust/truststore-merger/src/cert_ext.rs diff --git a/Cargo.lock b/Cargo.lock index a3e64cbe..799af4d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1136,6 +1136,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hex-literal" version = "0.3.4" @@ -3813,6 +3819,7 @@ version = "0.0.1" dependencies = [ "anyhow", "clap", + "hex", "openssl", "stackable-secret-operator-utils", "tracing", diff --git a/Cargo.toml b/Cargo.toml index a3386bdd..32b5ee66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ clap = "4.5" const_format = "0.2.34" futures = { version = "0.3", features = ["compat"] } h2 = "0.4" +hex = "0.4" kube-runtime = { version = "1.0", features = ["unstable-runtime-stream-control"] } ldap3 = { version = "0.11", default-features = false, features = ["gssapi", "tls"] } libc = "0.2" diff --git a/rust/truststore-merger/Cargo.toml b/rust/truststore-merger/Cargo.toml index b3708460..d7fb13f4 100644 --- a/rust/truststore-merger/Cargo.toml +++ b/rust/truststore-merger/Cargo.toml @@ -13,6 +13,7 @@ stackable-secret-operator-utils = { path = "../utils" } anyhow.workspace = true clap = { workspace = true, features = ["derive"] } +hex.workspace = true openssl.workspace = true tracing.workspace = true tracing-subscriber = { workspace = true, features = ["env-filter"] } diff --git a/rust/truststore-merger/src/cert_ext.rs b/rust/truststore-merger/src/cert_ext.rs new file mode 100644 index 00000000..6a06e30e --- /dev/null +++ b/rust/truststore-merger/src/cert_ext.rs @@ -0,0 +1,26 @@ +use anyhow::Context; +use openssl::{ + hash::{DigestBytes, MessageDigest}, + string::OpensslString, + x509::X509, +}; + +pub trait CertExt { + fn serial_as_hex(&self) -> anyhow::Result; + fn sha256_digest(&self) -> anyhow::Result; +} + +impl CertExt for X509 { + fn serial_as_hex(&self) -> anyhow::Result { + self.serial_number() + .to_bn() + .context("failed to get certificate serial number as BigNumber")? + .to_hex_str() + .context("failed to convert certificate serial number to hex string") + } + + fn sha256_digest(&self) -> anyhow::Result { + self.digest(MessageDigest::sha256()) + .context("failed to get certificate digest") + } +} diff --git a/rust/truststore-merger/src/main.rs b/rust/truststore-merger/src/main.rs index ce5ca33e..c0693cea 100644 --- a/rust/truststore-merger/src/main.rs +++ b/rust/truststore-merger/src/main.rs @@ -1,12 +1,14 @@ use std::{collections::HashMap, fs}; use anyhow::{Context, ensure}; +use cert_ext::CertExt; use clap::Parser; use cli_args::Cli; use openssl::x509::X509; use stackable_secret_operator_utils::pkcs12::pkcs12_truststore; use tracing::{info, level_filters::LevelFilter, warn}; +mod cert_ext; mod cli_args; mod parsers; @@ -44,30 +46,32 @@ pub fn main() -> anyhow::Result<()> { let mut certificates = HashMap::, X509>::new(); for (source, certificates_list) in certificate_sources.into_iter() { for certificate in certificates_list { - let serial_bn = certificate - .serial_number() - .to_bn() - .context("failed to get certificate serial number as BigNumber")?; - let serial = serial_bn.to_vec(); - if let Some(existing) = certificates.get(&serial) { + let sha256 = certificate.sha256_digest()?; + + if let Some(existing) = certificates.get(&*sha256) { warn!( - serial = ?serial_bn.to_hex_str(), ?source, + sha25 = hex::encode(sha256), + existing.not_before = ?existing.not_before(), existing.not_after = ?existing.not_after(), existing.subject = ?existing.subject_name(), + existing.serial = ?existing.serial_as_hex()?, + new.not_before = ?certificate.not_before(), new.not_after = ?certificate.not_after(), new.subject = ?certificate.subject_name(), - "Skipped certificate as it was already added", + new.serial = ?existing.serial_as_hex()?, + "Skipped certificate as a cert with the same SHA256 hash was already added", ); } else { info!( - serial = ?serial_bn.to_hex_str(), - not_after = ?certificate.not_after(), subject = ?certificate.subject_name(), + not_before = ?certificate.not_before(), + not_after = ?certificate.not_after(), + serial = ?certificate.serial_as_hex()?, ?source, "Added certificate" ); - certificates.insert(serial, certificate); + certificates.insert(sha256.to_vec(), certificate); } } } From 2d894b426448545c88942d95812323a51303caf7 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Mon, 8 Sep 2025 12:57:00 +0200 Subject: [PATCH 07/24] Set version to 0.0.0-dev --- Cargo.lock | 2 +- Cargo.nix | 2 +- rust/truststore-merger/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0bad8d6d..0ca18cf9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3793,7 +3793,7 @@ dependencies = [ [[package]] name = "truststore-merger" -version = "0.0.1" +version = "0.0.0-dev" dependencies = [ "anyhow", "clap", diff --git a/Cargo.nix b/Cargo.nix index 94eb753d..3d74f936 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -12797,7 +12797,7 @@ rec { }; "truststore-merger" = rec { crateName = "truststore-merger"; - version = "0.0.1"; + version = "0.0.0-dev"; edition = "2021"; crateBin = [ { diff --git a/rust/truststore-merger/Cargo.toml b/rust/truststore-merger/Cargo.toml index d7fb13f4..e07b2d05 100644 --- a/rust/truststore-merger/Cargo.toml +++ b/rust/truststore-merger/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "truststore-merger" description = "A CLI tool to merge two truststores in PEM or PKCS12 format in such as way that they are accepted by the JVM" -version = "0.0.1" +version = "0.0.0-dev" authors.workspace = true license.workspace = true edition.workspace = true From cfce35cb185f8d83f71daf5271b4ba67bd4618f8 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Mon, 8 Sep 2025 14:06:39 +0200 Subject: [PATCH 08/24] Make sure PEM contains certificates --- rust/truststore-merger/src/cli_args.rs | 21 +++++++++++++++------ rust/truststore-merger/src/main.rs | 2 ++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/rust/truststore-merger/src/cli_args.rs b/rust/truststore-merger/src/cli_args.rs index 1396bff8..b853ae3e 100644 --- a/rust/truststore-merger/src/cli_args.rs +++ b/rust/truststore-merger/src/cli_args.rs @@ -1,6 +1,6 @@ use std::{fs, path::PathBuf}; -use anyhow::Context; +use anyhow::{Context, ensure}; use clap::Parser; use openssl::x509::X509; @@ -68,12 +68,21 @@ impl CertInput { fs::read(self.path()).with_context(|| format!("failed to read file from {self:?}"))?; match self { - CertInput::Pem(_) => parse_pem_contents(&file_contents).with_context(|| { - format!( - "failed to parse PEM contents from {path:?}", + CertInput::Pem(_) => { + let certs = parse_pem_contents(&file_contents).with_context(|| { + format!( + "failed to parse PEM contents from {path:?}", + path = self.path() + ) + })?; + ensure!( + !certs.is_empty(), + "The PEM file {path:?} contained no certificates", path = self.path() - ) - }), + ); + + Ok(certs) + } CertInput::Pkcs12(Pkcs12Source { password, .. }) => { parse_pkcs12_file_workaround(&file_contents, password) } diff --git a/rust/truststore-merger/src/main.rs b/rust/truststore-merger/src/main.rs index c0693cea..57279940 100644 --- a/rust/truststore-merger/src/main.rs +++ b/rust/truststore-merger/src/main.rs @@ -45,6 +45,8 @@ pub fn main() -> anyhow::Result<()> { let mut certificates = HashMap::, X509>::new(); for (source, certificates_list) in certificate_sources.into_iter() { + info!(?source, "Importing certificates"); + for certificate in certificates_list { let sha256 = certificate.sha256_digest()?; From 0779d8fb024c857867cec82b10cb9f0b596f8937 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Thu, 11 Sep 2025 16:48:38 +0200 Subject: [PATCH 09/24] Rename to cert-tool --- Cargo.lock | 26 +++---- Cargo.nix | 118 +++++++++++++++--------------- rust/truststore-merger/Cargo.toml | 2 +- 3 files changed, 73 insertions(+), 73 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0ca18cf9..03cb922f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -366,6 +366,19 @@ dependencies = [ "shlex", ] +[[package]] +name = "cert-tool" +version = "0.0.0-dev" +dependencies = [ + "anyhow", + "clap", + "hex", + "openssl", + "stackable-secret-operator-utils", + "tracing", + "tracing-subscriber", +] + [[package]] name = "cexpr" version = "0.6.0" @@ -3791,19 +3804,6 @@ dependencies = [ "tracing-serde", ] -[[package]] -name = "truststore-merger" -version = "0.0.0-dev" -dependencies = [ - "anyhow", - "clap", - "hex", - "openssl", - "stackable-secret-operator-utils", - "tracing", - "tracing-subscriber", -] - [[package]] name = "try-lock" version = "0.2.5" diff --git a/Cargo.nix b/Cargo.nix index 3d74f936..8777d0f8 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -38,6 +38,16 @@ rec { # You can override the features with # workspaceMembers."${crateName}".build.override { features = [ "default" "feature1" ... ]; }. workspaceMembers = { + "cert-tool" = rec { + packageId = "cert-tool"; + build = internal.buildRustCrateWithFeatures { + packageId = "cert-tool"; + }; + + # Debug support which might change between releases. + # File a bug if you depend on any for non-debug work! + debug = internal.debugCrate { inherit packageId; }; + }; "p12" = rec { packageId = "p12"; build = internal.buildRustCrateWithFeatures { @@ -84,16 +94,6 @@ rec { packageId = "stackable-secret-operator-utils"; }; - # Debug support which might change between releases. - # File a bug if you depend on any for non-debug work! - debug = internal.debugCrate { inherit packageId; }; - }; - "truststore-merger" = rec { - packageId = "truststore-merger"; - build = internal.buildRustCrateWithFeatures { - packageId = "truststore-merger"; - }; - # Debug support which might change between releases. # File a bug if you depend on any for non-debug work! debug = internal.debugCrate { inherit packageId; }; @@ -1229,6 +1229,55 @@ rec { }; resolvedDefaultFeatures = [ "parallel" ]; }; + "cert-tool" = rec { + crateName = "cert-tool"; + version = "0.0.0-dev"; + edition = "2021"; + crateBin = [ + { + name = "cert-tool"; + path = "src/main.rs"; + requiredFeatures = [ ]; + } + ]; + src = lib.cleanSourceWith { filter = sourceFilter; src = ./rust/truststore-merger; }; + authors = [ + "Stackable GmbH " + ]; + dependencies = [ + { + name = "anyhow"; + packageId = "anyhow"; + } + { + name = "clap"; + packageId = "clap"; + features = [ "derive" ]; + } + { + name = "hex"; + packageId = "hex"; + } + { + name = "openssl"; + packageId = "openssl"; + } + { + name = "stackable-secret-operator-utils"; + packageId = "stackable-secret-operator-utils"; + } + { + name = "tracing"; + packageId = "tracing"; + } + { + name = "tracing-subscriber"; + packageId = "tracing-subscriber"; + features = [ "env-filter" ]; + } + ]; + + }; "cexpr" = rec { crateName = "cexpr"; version = "0.6.0"; @@ -12795,55 +12844,6 @@ rec { }; resolvedDefaultFeatures = [ "alloc" "ansi" "default" "env-filter" "fmt" "json" "matchers" "nu-ansi-term" "once_cell" "registry" "serde" "serde_json" "sharded-slab" "smallvec" "std" "thread_local" "tracing" "tracing-log" "tracing-serde" ]; }; - "truststore-merger" = rec { - crateName = "truststore-merger"; - version = "0.0.0-dev"; - edition = "2021"; - crateBin = [ - { - name = "truststore-merger"; - path = "src/main.rs"; - requiredFeatures = [ ]; - } - ]; - src = lib.cleanSourceWith { filter = sourceFilter; src = ./rust/truststore-merger; }; - authors = [ - "Stackable GmbH " - ]; - dependencies = [ - { - name = "anyhow"; - packageId = "anyhow"; - } - { - name = "clap"; - packageId = "clap"; - features = [ "derive" ]; - } - { - name = "hex"; - packageId = "hex"; - } - { - name = "openssl"; - packageId = "openssl"; - } - { - name = "stackable-secret-operator-utils"; - packageId = "stackable-secret-operator-utils"; - } - { - name = "tracing"; - packageId = "tracing"; - } - { - name = "tracing-subscriber"; - packageId = "tracing-subscriber"; - features = [ "env-filter" ]; - } - ]; - - }; "try-lock" = rec { crateName = "try-lock"; version = "0.2.5"; diff --git a/rust/truststore-merger/Cargo.toml b/rust/truststore-merger/Cargo.toml index e07b2d05..fde11588 100644 --- a/rust/truststore-merger/Cargo.toml +++ b/rust/truststore-merger/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "truststore-merger" +name = "cert-tool" description = "A CLI tool to merge two truststores in PEM or PKCS12 format in such as way that they are accepted by the JVM" version = "0.0.0-dev" authors.workspace = true From 77a4965d2fb5171181c36e8935b2c7a2c67d674e Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Thu, 11 Sep 2025 16:56:32 +0200 Subject: [PATCH 10/24] Rename to cert-tools --- Cargo.lock | 2 +- Cargo.nix | 12 ++++++------ rust/truststore-merger/Cargo.toml | 2 +- rust/truststore-merger/src/cli_args.rs | 10 ++++++++-- rust/truststore-merger/src/main.rs | 18 +++++++++++++----- 5 files changed, 29 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 03cb922f..1e1fcfdb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -367,7 +367,7 @@ dependencies = [ ] [[package]] -name = "cert-tool" +name = "cert-tools" version = "0.0.0-dev" dependencies = [ "anyhow", diff --git a/Cargo.nix b/Cargo.nix index 8777d0f8..608178ec 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -38,10 +38,10 @@ rec { # You can override the features with # workspaceMembers."${crateName}".build.override { features = [ "default" "feature1" ... ]; }. workspaceMembers = { - "cert-tool" = rec { - packageId = "cert-tool"; + "cert-tools" = rec { + packageId = "cert-tools"; build = internal.buildRustCrateWithFeatures { - packageId = "cert-tool"; + packageId = "cert-tools"; }; # Debug support which might change between releases. @@ -1229,13 +1229,13 @@ rec { }; resolvedDefaultFeatures = [ "parallel" ]; }; - "cert-tool" = rec { - crateName = "cert-tool"; + "cert-tools" = rec { + crateName = "cert-tools"; version = "0.0.0-dev"; edition = "2021"; crateBin = [ { - name = "cert-tool"; + name = "cert-tools"; path = "src/main.rs"; requiredFeatures = [ ]; } diff --git a/rust/truststore-merger/Cargo.toml b/rust/truststore-merger/Cargo.toml index fde11588..90fac53e 100644 --- a/rust/truststore-merger/Cargo.toml +++ b/rust/truststore-merger/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "cert-tool" +name = "cert-tools" description = "A CLI tool to merge two truststores in PEM or PKCS12 format in such as way that they are accepted by the JVM" version = "0.0.0-dev" authors.workspace = true diff --git a/rust/truststore-merger/src/cli_args.rs b/rust/truststore-merger/src/cli_args.rs index b853ae3e..22d7462a 100644 --- a/rust/truststore-merger/src/cli_args.rs +++ b/rust/truststore-merger/src/cli_args.rs @@ -8,7 +8,13 @@ use crate::parsers::{parse_pem_contents, parse_pkcs12_file_workaround}; #[derive(Parser, Debug)] #[command(version, about)] -pub struct Cli { +pub enum Cli { + /// Generate PKCS12 truststore files from PEM or PKCS12 files + GeneratePkcs12Truststore(GeneratePkcs12), +} + +#[derive(Parser, Debug)] +pub struct GeneratePkcs12 { /// The path to output the resulting PKCS12 to #[arg(long)] pub out: PathBuf, @@ -54,7 +60,7 @@ fn parse_cli_pkcs12_source(cli_argument: &str) -> Result { }) } -impl Cli { +impl GeneratePkcs12 { pub fn certificate_sources(&self) -> Vec { let pems = self.pems.iter().cloned().map(CertInput::Pem); let pkcs12s = self.pkcs12s.iter().cloned().map(CertInput::Pkcs12); diff --git a/rust/truststore-merger/src/main.rs b/rust/truststore-merger/src/main.rs index 57279940..320c39c0 100644 --- a/rust/truststore-merger/src/main.rs +++ b/rust/truststore-merger/src/main.rs @@ -3,7 +3,7 @@ use std::{collections::HashMap, fs}; use anyhow::{Context, ensure}; use cert_ext::CertExt; use clap::Parser; -use cli_args::Cli; +use cli_args::{Cli, GeneratePkcs12}; use openssl::x509::X509; use stackable_secret_operator_utils::pkcs12::pkcs12_truststore; use tracing::{info, level_filters::LevelFilter, warn}; @@ -25,7 +25,15 @@ pub fn main() -> anyhow::Result<()> { let cli = Cli::parse(); - let certificate_sources = cli.certificate_sources(); + match cli { + Cli::GeneratePkcs12Truststore(cli_args) => generate_pkcs12_truststore(cli_args)?, + } + + Ok(()) +} + +fn generate_pkcs12_truststore(cli_args: GeneratePkcs12) -> anyhow::Result<()> { + let certificate_sources = cli_args.certificate_sources(); ensure!( !certificate_sources.is_empty(), "The list of certificate sources can not be empty. Please provide at least on --pem or --pkcs12." @@ -79,12 +87,12 @@ pub fn main() -> anyhow::Result<()> { } let pkcs12_truststore_bytes = - pkcs12_truststore(certificates.values().map(|c| &**c), &cli.out_password) + pkcs12_truststore(certificates.values().map(|c| &**c), &cli_args.out_password) .context("failed to create PKCS12 truststore from certificates")?; - fs::write(&cli.out, &pkcs12_truststore_bytes).with_context(|| { + fs::write(&cli_args.out, &pkcs12_truststore_bytes).with_context(|| { format!( "failed to write to output PKCS12 truststore at {:?}", - cli.out + cli_args.out ) })?; From 9de0643f0d63f89de5e3f4b90d3a3e921185e74c Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 12 Sep 2025 08:21:13 +0200 Subject: [PATCH 11/24] Rename folder as well --- rust/{truststore-merger => cert-tools}/Cargo.toml | 0 rust/{truststore-merger => cert-tools}/src/cert_ext.rs | 0 rust/{truststore-merger => cert-tools}/src/cli_args.rs | 0 rust/{truststore-merger => cert-tools}/src/main.rs | 0 rust/{truststore-merger => cert-tools}/src/parsers.rs | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename rust/{truststore-merger => cert-tools}/Cargo.toml (100%) rename rust/{truststore-merger => cert-tools}/src/cert_ext.rs (100%) rename rust/{truststore-merger => cert-tools}/src/cli_args.rs (100%) rename rust/{truststore-merger => cert-tools}/src/main.rs (100%) rename rust/{truststore-merger => cert-tools}/src/parsers.rs (100%) diff --git a/rust/truststore-merger/Cargo.toml b/rust/cert-tools/Cargo.toml similarity index 100% rename from rust/truststore-merger/Cargo.toml rename to rust/cert-tools/Cargo.toml diff --git a/rust/truststore-merger/src/cert_ext.rs b/rust/cert-tools/src/cert_ext.rs similarity index 100% rename from rust/truststore-merger/src/cert_ext.rs rename to rust/cert-tools/src/cert_ext.rs diff --git a/rust/truststore-merger/src/cli_args.rs b/rust/cert-tools/src/cli_args.rs similarity index 100% rename from rust/truststore-merger/src/cli_args.rs rename to rust/cert-tools/src/cli_args.rs diff --git a/rust/truststore-merger/src/main.rs b/rust/cert-tools/src/main.rs similarity index 100% rename from rust/truststore-merger/src/main.rs rename to rust/cert-tools/src/main.rs diff --git a/rust/truststore-merger/src/parsers.rs b/rust/cert-tools/src/parsers.rs similarity index 100% rename from rust/truststore-merger/src/parsers.rs rename to rust/cert-tools/src/parsers.rs From 7a4b8cf64710cc70d3ceea96f81bd35b5a783080 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 12 Sep 2025 08:39:03 +0200 Subject: [PATCH 12/24] anyhow -> snafu --- Cargo.lock | 2 +- rust/cert-tools/Cargo.toml | 2 +- rust/cert-tools/src/cert_ext.rs | 16 ++++++++-------- rust/cert-tools/src/cli_args.rs | 16 ++++++++-------- rust/cert-tools/src/main.rs | 20 +++++++++++--------- rust/cert-tools/src/parsers.rs | 31 +++++++++++++++++-------------- 6 files changed, 46 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1e1fcfdb..a21744c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -370,10 +370,10 @@ dependencies = [ name = "cert-tools" version = "0.0.0-dev" dependencies = [ - "anyhow", "clap", "hex", "openssl", + "snafu 0.8.7", "stackable-secret-operator-utils", "tracing", "tracing-subscriber", diff --git a/rust/cert-tools/Cargo.toml b/rust/cert-tools/Cargo.toml index 90fac53e..3d8ff076 100644 --- a/rust/cert-tools/Cargo.toml +++ b/rust/cert-tools/Cargo.toml @@ -11,9 +11,9 @@ publish = false [dependencies] stackable-secret-operator-utils = { path = "../utils" } -anyhow.workspace = true clap = { workspace = true, features = ["derive"] } hex.workspace = true openssl.workspace = true +snafu.workspace = true tracing.workspace = true tracing-subscriber = { workspace = true, features = ["env-filter"] } diff --git a/rust/cert-tools/src/cert_ext.rs b/rust/cert-tools/src/cert_ext.rs index 6a06e30e..019a0dfc 100644 --- a/rust/cert-tools/src/cert_ext.rs +++ b/rust/cert-tools/src/cert_ext.rs @@ -1,26 +1,26 @@ -use anyhow::Context; use openssl::{ hash::{DigestBytes, MessageDigest}, string::OpensslString, x509::X509, }; +use snafu::ResultExt; pub trait CertExt { - fn serial_as_hex(&self) -> anyhow::Result; - fn sha256_digest(&self) -> anyhow::Result; + fn serial_as_hex(&self) -> Result; + fn sha256_digest(&self) -> Result; } impl CertExt for X509 { - fn serial_as_hex(&self) -> anyhow::Result { + fn serial_as_hex(&self) -> Result { self.serial_number() .to_bn() - .context("failed to get certificate serial number as BigNumber")? + .whatever_context("failed to get certificate serial number as BigNumber")? .to_hex_str() - .context("failed to convert certificate serial number to hex string") + .whatever_context("failed to convert certificate serial number to hex string") } - fn sha256_digest(&self) -> anyhow::Result { + fn sha256_digest(&self) -> Result { self.digest(MessageDigest::sha256()) - .context("failed to get certificate digest") + .whatever_context("failed to get certificate digest") } } diff --git a/rust/cert-tools/src/cli_args.rs b/rust/cert-tools/src/cli_args.rs index 22d7462a..d5a76882 100644 --- a/rust/cert-tools/src/cli_args.rs +++ b/rust/cert-tools/src/cli_args.rs @@ -1,8 +1,8 @@ use std::{fs, path::PathBuf}; -use anyhow::{Context, ensure}; use clap::Parser; use openssl::x509::X509; +use snafu::{ResultExt, ensure_whatever}; use crate::parsers::{parse_pem_contents, parse_pkcs12_file_workaround}; @@ -69,22 +69,22 @@ impl GeneratePkcs12 { } impl CertInput { - pub fn read(&self) -> anyhow::Result> { - let file_contents = - fs::read(self.path()).with_context(|| format!("failed to read file from {self:?}"))?; + pub fn read(&self) -> Result, snafu::Whatever> { + let file_contents = fs::read(self.path()) + .with_whatever_context(|_| format!("failed to read file from {self:?}"))?; match self { CertInput::Pem(_) => { - let certs = parse_pem_contents(&file_contents).with_context(|| { + let certs = parse_pem_contents(&file_contents).with_whatever_context(|_| { format!( "failed to parse PEM contents from {path:?}", path = self.path() ) })?; - ensure!( + let path = self.path(); + ensure_whatever!( !certs.is_empty(), - "The PEM file {path:?} contained no certificates", - path = self.path() + "The PEM file at {path:?} contained no certificates", ); Ok(certs) diff --git a/rust/cert-tools/src/main.rs b/rust/cert-tools/src/main.rs index 320c39c0..e66cbee5 100644 --- a/rust/cert-tools/src/main.rs +++ b/rust/cert-tools/src/main.rs @@ -1,10 +1,10 @@ use std::{collections::HashMap, fs}; -use anyhow::{Context, ensure}; use cert_ext::CertExt; use clap::Parser; use cli_args::{Cli, GeneratePkcs12}; use openssl::x509::X509; +use snafu::{ResultExt, ensure_whatever}; use stackable_secret_operator_utils::pkcs12::pkcs12_truststore; use tracing::{info, level_filters::LevelFilter, warn}; @@ -12,10 +12,12 @@ mod cert_ext; mod cli_args; mod parsers; -pub fn main() -> anyhow::Result<()> { +#[snafu::report] +pub fn main() -> Result<(), snafu::Whatever> { let filter = tracing_subscriber::EnvFilter::builder() .with_default_directive(LevelFilter::INFO.into()) - .from_env()?; + .from_env() + .whatever_context("failed to create tracing subscriber EnvFilter")?; tracing_subscriber::fmt() // Short running tool does not need any complex output .with_target(false) @@ -32,16 +34,16 @@ pub fn main() -> anyhow::Result<()> { Ok(()) } -fn generate_pkcs12_truststore(cli_args: GeneratePkcs12) -> anyhow::Result<()> { +fn generate_pkcs12_truststore(cli_args: GeneratePkcs12) -> Result<(), snafu::Whatever> { let certificate_sources = cli_args.certificate_sources(); - ensure!( + ensure_whatever!( !certificate_sources.is_empty(), "The list of certificate sources can not be empty. Please provide at least on --pem or --pkcs12." ); let certificate_sources = certificate_sources .iter() .map(|source| { - let certificate = source.read().with_context(|| { + let certificate = source.read().with_whatever_context(|_| { format!( "failed to read certificate source {path:?}", path = source.path() @@ -49,7 +51,7 @@ fn generate_pkcs12_truststore(cli_args: GeneratePkcs12) -> anyhow::Result<()> { })?; Ok((source, certificate)) }) - .collect::>>()?; + .collect::, _>>()?; let mut certificates = HashMap::, X509>::new(); for (source, certificates_list) in certificate_sources.into_iter() { @@ -88,8 +90,8 @@ fn generate_pkcs12_truststore(cli_args: GeneratePkcs12) -> anyhow::Result<()> { let pkcs12_truststore_bytes = pkcs12_truststore(certificates.values().map(|c| &**c), &cli_args.out_password) - .context("failed to create PKCS12 truststore from certificates")?; - fs::write(&cli_args.out, &pkcs12_truststore_bytes).with_context(|| { + .whatever_context("failed to create PKCS12 truststore from certificates")?; + fs::write(&cli_args.out, &pkcs12_truststore_bytes).with_whatever_context(|_| { format!( "failed to write to output PKCS12 truststore at {:?}", cli_args.out diff --git a/rust/cert-tools/src/parsers.rs b/rust/cert-tools/src/parsers.rs index 59dded3e..c1930fc8 100644 --- a/rust/cert-tools/src/parsers.rs +++ b/rust/cert-tools/src/parsers.rs @@ -3,14 +3,14 @@ use std::{ process::{Command, Stdio}, }; -use anyhow::{Context, bail}; use openssl::{pkcs12::Pkcs12, x509::X509}; +use snafu::{OptionExt, ResultExt, whatever}; use stackable_secret_operator_utils::pem::split_pem_certificates; -pub fn parse_pem_contents(pem_bytes: &[u8]) -> anyhow::Result> { +pub fn parse_pem_contents(pem_bytes: &[u8]) -> Result, snafu::Whatever> { let pems = split_pem_certificates(pem_bytes); pems.into_iter() - .map(|pem| X509::from_pem(pem).context("failed to parse PEM encoded certificate")) + .map(|pem| X509::from_pem(pem).whatever_context("failed to parse PEM encoded certificate")) .collect() } @@ -44,15 +44,18 @@ pub fn parse_pem_contents(pem_bytes: &[u8]) -> anyhow::Result> { /// The proper solution would be that secret-operator writes PKCS12 truststores using modern algorithms. /// For that we probably(?) drop the p12 crate? #[allow(unused)] -pub fn parse_pkcs12_file(file_contents: &[u8], password: &str) -> anyhow::Result> { +pub fn parse_pkcs12_file( + file_contents: &[u8], + password: &str, +) -> Result, snafu::Whatever> { let parsed = Pkcs12::from_der(file_contents) - .context("failed to parse PKCS12 DER encoded file")? + .whatever_context("failed to parse PKCS12 DER encoded file")? .parse2(password) - .context("Failed to parse PKCS12 using the provided password")?; + .whatever_context("Failed to parse PKCS12 using the provided password")?; parsed .ca - .context("pkcs12 truststore did not contain a CA")? + .whatever_context("pkcs12 truststore did not contain a CA")? .into_iter() .map(Ok) .collect() @@ -64,7 +67,7 @@ pub fn parse_pkcs12_file(file_contents: &[u8], password: &str) -> anyhow::Result pub fn parse_pkcs12_file_workaround( file_contents: &[u8], password: &str, -) -> anyhow::Result> { +) -> Result, snafu::Whatever> { let mut child = Command::new("openssl") .args(&[ "pkcs12", @@ -78,27 +81,27 @@ pub fn parse_pkcs12_file_workaround( .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() - .context("Failed to spawn openssl process")?; + .whatever_context("Failed to spawn openssl process")?; { let stdin = child .stdin .as_mut() - .context("Failed to open openssl process stdin")?; + .whatever_context("Failed to open openssl process stdin")?; stdin .write_all(file_contents) - .context("Failed to write PKCS12 data to openssl process stdin")?; + .whatever_context("Failed to write PKCS12 data to openssl process stdin")?; } let output = child .wait_with_output() - .context("Failed to read openssl process output")?; + .whatever_context("Failed to read openssl process output")?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); - bail!("openssl process failed with STDERR: {stderr:?}"); + whatever!("openssl process failed with STDERR: {stderr:?}"); } - parse_pem_contents(&output.stdout).with_context(|| { + parse_pem_contents(&output.stdout).with_whatever_context(|_| { format!( "failed to parse openssl process output, which should be PEM. STDOUT: {stdout}?", stdout = String::from_utf8_lossy(&output.stdout) From aa30f9ca8204564e291f436d227f41e891e71d3a Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 12 Sep 2025 08:53:29 +0200 Subject: [PATCH 13/24] regenerate nix --- Cargo.nix | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.nix b/Cargo.nix index 608178ec..ae7236f0 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -1240,15 +1240,11 @@ rec { requiredFeatures = [ ]; } ]; - src = lib.cleanSourceWith { filter = sourceFilter; src = ./rust/truststore-merger; }; + src = lib.cleanSourceWith { filter = sourceFilter; src = ./rust/cert-tools; }; authors = [ "Stackable GmbH " ]; dependencies = [ - { - name = "anyhow"; - packageId = "anyhow"; - } { name = "clap"; packageId = "clap"; @@ -1262,6 +1258,10 @@ rec { name = "openssl"; packageId = "openssl"; } + { + name = "snafu"; + packageId = "snafu 0.8.7"; + } { name = "stackable-secret-operator-utils"; packageId = "stackable-secret-operator-utils"; From c3d8b8b19d613faa77c0a7fa7aaba8334efde242 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 12 Sep 2025 09:18:09 +0200 Subject: [PATCH 14/24] Use stackable-tracing --- Cargo.lock | 27 ++++++++++++++++++++++++++- rust/cert-tools/Cargo.toml | 1 + rust/cert-tools/src/cli_args.rs | 14 ++++++++++++-- rust/cert-tools/src/main.rs | 25 ++++++++++--------------- 4 files changed, 49 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a21744c4..2ae18a06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -375,6 +375,7 @@ dependencies = [ "openssl", "snafu 0.8.7", "stackable-secret-operator-utils", + "stackable-telemetry 0.6.1 (git+https://github.com/stackabletech/operator-rs.git?tag=stackable-telemetry-0.6.1)", "tracing", "tracing-subscriber", ] @@ -3110,7 +3111,7 @@ dependencies = [ "snafu 0.8.7", "stackable-operator-derive", "stackable-shared", - "stackable-telemetry", + "stackable-telemetry 0.6.1 (git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.96.0)", "stackable-versioned", "strum", "tokio", @@ -3241,6 +3242,30 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "stackable-telemetry" +version = "0.6.1" +source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-telemetry-0.6.1#958f62f2befdd984ad9fcd272d0214055c3a7601" +dependencies = [ + "axum", + "clap", + "futures-util", + "opentelemetry", + "opentelemetry-appender-tracing", + "opentelemetry-otlp", + "opentelemetry-semantic-conventions", + "opentelemetry_sdk", + "pin-project", + "snafu 0.8.7", + "strum", + "tokio", + "tower", + "tracing", + "tracing-appender", + "tracing-opentelemetry", + "tracing-subscriber", +] + [[package]] name = "stackable-versioned" version = "0.8.1" diff --git a/rust/cert-tools/Cargo.toml b/rust/cert-tools/Cargo.toml index 3d8ff076..1172ce53 100644 --- a/rust/cert-tools/Cargo.toml +++ b/rust/cert-tools/Cargo.toml @@ -10,6 +10,7 @@ publish = false [dependencies] stackable-secret-operator-utils = { path = "../utils" } +stackable-telemetry = { git = "https://github.com/stackabletech/operator-rs.git", features = ["clap"], tag = "stackable-telemetry-0.6.1" } clap = { workspace = true, features = ["derive"] } hex.workspace = true diff --git a/rust/cert-tools/src/cli_args.rs b/rust/cert-tools/src/cli_args.rs index d5a76882..8a44c3af 100644 --- a/rust/cert-tools/src/cli_args.rs +++ b/rust/cert-tools/src/cli_args.rs @@ -1,14 +1,24 @@ use std::{fs, path::PathBuf}; -use clap::Parser; +use clap::{Parser, Subcommand}; use openssl::x509::X509; use snafu::{ResultExt, ensure_whatever}; +use stackable_telemetry::tracing::TelemetryOptions; use crate::parsers::{parse_pem_contents, parse_pkcs12_file_workaround}; #[derive(Parser, Debug)] #[command(version, about)] -pub enum Cli { +pub struct Cli { + #[command(subcommand)] + pub command: CliCommand, + + #[command(flatten, next_help_heading = "Tracing options")] + pub telemetry: TelemetryOptions, +} + +#[derive(Subcommand, Debug)] +pub enum CliCommand { /// Generate PKCS12 truststore files from PEM or PKCS12 files GeneratePkcs12Truststore(GeneratePkcs12), } diff --git a/rust/cert-tools/src/main.rs b/rust/cert-tools/src/main.rs index e66cbee5..640ed8a9 100644 --- a/rust/cert-tools/src/main.rs +++ b/rust/cert-tools/src/main.rs @@ -2,11 +2,12 @@ use std::{collections::HashMap, fs}; use cert_ext::CertExt; use clap::Parser; -use cli_args::{Cli, GeneratePkcs12}; +use cli_args::{Cli, CliCommand, GeneratePkcs12}; use openssl::x509::X509; use snafu::{ResultExt, ensure_whatever}; use stackable_secret_operator_utils::pkcs12::pkcs12_truststore; -use tracing::{info, level_filters::LevelFilter, warn}; +use stackable_telemetry::Tracing; +use tracing::{info, warn}; mod cert_ext; mod cli_args; @@ -14,21 +15,15 @@ mod parsers; #[snafu::report] pub fn main() -> Result<(), snafu::Whatever> { - let filter = tracing_subscriber::EnvFilter::builder() - .with_default_directive(LevelFilter::INFO.into()) - .from_env() - .whatever_context("failed to create tracing subscriber EnvFilter")?; - tracing_subscriber::fmt() - // Short running tool does not need any complex output - .with_target(false) - .without_time() - .with_env_filter(filter) - .init(); - let cli = Cli::parse(); - match cli { - Cli::GeneratePkcs12Truststore(cli_args) => generate_pkcs12_truststore(cli_args)?, + // Use `CONSOLE_LOG_LEVEL` to modify the console log level + let _tracing_guard = Tracing::pre_configured("cert-tools", cli.telemetry) + .init() + .whatever_context("failed to initialize tracing")?; + + match cli.command { + CliCommand::GeneratePkcs12Truststore(cli_args) => generate_pkcs12_truststore(cli_args)?, } Ok(()) From 229fd455887cd283705a5846006250e0aafb5417 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 12 Sep 2025 09:21:31 +0200 Subject: [PATCH 15/24] Use path from match --- rust/cert-tools/src/cli_args.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/rust/cert-tools/src/cli_args.rs b/rust/cert-tools/src/cli_args.rs index 8a44c3af..af275eb0 100644 --- a/rust/cert-tools/src/cli_args.rs +++ b/rust/cert-tools/src/cli_args.rs @@ -80,18 +80,17 @@ impl GeneratePkcs12 { impl CertInput { pub fn read(&self) -> Result, snafu::Whatever> { - let file_contents = fs::read(self.path()) - .with_whatever_context(|_| format!("failed to read file from {self:?}"))?; - match self { - CertInput::Pem(_) => { + CertInput::Pem(path) => { + let file_contents = fs::read(path) + .with_whatever_context(|_| format!("failed to read file from {self:?}"))?; + let certs = parse_pem_contents(&file_contents).with_whatever_context(|_| { format!( "failed to parse PEM contents from {path:?}", path = self.path() ) })?; - let path = self.path(); ensure_whatever!( !certs.is_empty(), "The PEM file at {path:?} contained no certificates", @@ -99,7 +98,10 @@ impl CertInput { Ok(certs) } - CertInput::Pkcs12(Pkcs12Source { password, .. }) => { + CertInput::Pkcs12(Pkcs12Source { path, password }) => { + let file_contents = fs::read(path) + .with_whatever_context(|_| format!("failed to read file from {self:?}"))?; + parse_pkcs12_file_workaround(&file_contents, password) } } From 8d5eddc2be9554a8f1cc0b8e1404d2baad6b2853 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 12 Sep 2025 09:23:26 +0200 Subject: [PATCH 16/24] refactor out into read_file_fn --- rust/cert-tools/src/cli_args.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/rust/cert-tools/src/cli_args.rs b/rust/cert-tools/src/cli_args.rs index af275eb0..873c47c3 100644 --- a/rust/cert-tools/src/cli_args.rs +++ b/rust/cert-tools/src/cli_args.rs @@ -80,10 +80,13 @@ impl GeneratePkcs12 { impl CertInput { pub fn read(&self) -> Result, snafu::Whatever> { + let read_file_fn = |path| { + fs::read(path).with_whatever_context(|_| format!("failed to read file from {self:?}")) + }; + match self { CertInput::Pem(path) => { - let file_contents = fs::read(path) - .with_whatever_context(|_| format!("failed to read file from {self:?}"))?; + let file_contents = read_file_fn(path)?; let certs = parse_pem_contents(&file_contents).with_whatever_context(|_| { format!( @@ -99,8 +102,7 @@ impl CertInput { Ok(certs) } CertInput::Pkcs12(Pkcs12Source { path, password }) => { - let file_contents = fs::read(path) - .with_whatever_context(|_| format!("failed to read file from {self:?}"))?; + let file_contents = read_file_fn(path)?; parse_pkcs12_file_workaround(&file_contents, password) } From d40505673ca9f7a8f4a2a49fcccd38710706063a Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 12 Sep 2025 09:24:08 +0200 Subject: [PATCH 17/24] better error wording --- rust/cert-tools/src/cli_args.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/cert-tools/src/cli_args.rs b/rust/cert-tools/src/cli_args.rs index 873c47c3..5ff1e212 100644 --- a/rust/cert-tools/src/cli_args.rs +++ b/rust/cert-tools/src/cli_args.rs @@ -81,7 +81,7 @@ impl GeneratePkcs12 { impl CertInput { pub fn read(&self) -> Result, snafu::Whatever> { let read_file_fn = |path| { - fs::read(path).with_whatever_context(|_| format!("failed to read file from {self:?}")) + fs::read(path).with_whatever_context(|_| format!("failed to read from file {self:?}")) }; match self { From 254c508808d3b99ca4e7946c80fde1530a6f2501 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 12 Sep 2025 09:24:27 +0200 Subject: [PATCH 18/24] nix --- Cargo.nix | 347 ++++++++++++++++++++++++++++++---------------- crate-hashes.json | 1 + 2 files changed, 232 insertions(+), 116 deletions(-) diff --git a/Cargo.nix b/Cargo.nix index ae7236f0..464a50e2 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -1250,6 +1250,11 @@ rec { packageId = "clap"; features = [ "derive" ]; } + { + name = "stackable-telemetry"; + packageId = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-telemetry-0.6.1#stackable-telemetry@0.6.1"; + features = [ "clap" ]; + } { name = "hex"; packageId = "hex"; @@ -3312,6 +3317,226 @@ rec { }; resolvedDefaultFeatures = [ "read" "read-core" ]; }; + "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.96.0#stackable-telemetry@0.6.1" = rec { + crateName = "stackable-telemetry"; + version = "0.6.1"; + edition = "2024"; + workspace_member = null; + src = pkgs.fetchgit { + url = "https://github.com/stackabletech/operator-rs.git"; + rev = "89f484ca4e86b565e083e9ad7573e21dbe29a3af"; + sha256 = "05xhfz0bd09095ljkaj950r80bchdb202d8nka95cq356y4wha4c"; + }; + libName = "stackable_telemetry"; + authors = [ + "Stackable GmbH " + ]; + dependencies = [ + { + name = "axum"; + packageId = "axum"; + features = [ "http2" ]; + } + { + name = "clap"; + packageId = "clap"; + optional = true; + features = [ "derive" "cargo" "env" ]; + } + { + name = "futures-util"; + packageId = "futures-util"; + } + { + name = "opentelemetry"; + packageId = "opentelemetry"; + features = [ "logs" ]; + } + { + name = "opentelemetry-appender-tracing"; + packageId = "opentelemetry-appender-tracing"; + } + { + name = "opentelemetry-otlp"; + packageId = "opentelemetry-otlp"; + features = [ "grpc-tonic" "gzip-tonic" "logs" ]; + } + { + name = "opentelemetry-semantic-conventions"; + packageId = "opentelemetry-semantic-conventions"; + } + { + name = "opentelemetry_sdk"; + packageId = "opentelemetry_sdk"; + features = [ "rt-tokio" "logs" "rt-tokio" "spec_unstable_logs_enabled" ]; + } + { + name = "pin-project"; + packageId = "pin-project"; + } + { + name = "snafu"; + packageId = "snafu 0.8.7"; + } + { + name = "strum"; + packageId = "strum"; + features = [ "derive" ]; + } + { + name = "tokio"; + packageId = "tokio"; + features = [ "macros" "rt-multi-thread" "fs" ]; + } + { + name = "tower"; + packageId = "tower"; + features = [ "util" ]; + } + { + name = "tracing"; + packageId = "tracing"; + } + { + name = "tracing-appender"; + packageId = "tracing-appender"; + } + { + name = "tracing-opentelemetry"; + packageId = "tracing-opentelemetry"; + } + { + name = "tracing-subscriber"; + packageId = "tracing-subscriber"; + features = [ "env-filter" "json" "env-filter" ]; + } + ]; + devDependencies = [ + { + name = "tokio"; + packageId = "tokio"; + features = [ "macros" "rt-multi-thread" "fs" ]; + } + { + name = "tracing-opentelemetry"; + packageId = "tracing-opentelemetry"; + } + ]; + features = { + "clap" = [ "dep:clap" ]; + }; + resolvedDefaultFeatures = [ "clap" ]; + }; + "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-telemetry-0.6.1#stackable-telemetry@0.6.1" = rec { + crateName = "stackable-telemetry"; + version = "0.6.1"; + edition = "2024"; + workspace_member = null; + src = pkgs.fetchgit { + url = "https://github.com/stackabletech/operator-rs.git"; + rev = "958f62f2befdd984ad9fcd272d0214055c3a7601"; + sha256 = "0hiymhr40ix4jv9dmvp5d009xs6v0frvllr2xkf5mw43rcg44mgd"; + }; + libName = "stackable_telemetry"; + authors = [ + "Stackable GmbH " + ]; + dependencies = [ + { + name = "axum"; + packageId = "axum"; + features = [ "http2" ]; + } + { + name = "clap"; + packageId = "clap"; + optional = true; + features = [ "derive" "cargo" "env" ]; + } + { + name = "futures-util"; + packageId = "futures-util"; + } + { + name = "opentelemetry"; + packageId = "opentelemetry"; + features = [ "logs" ]; + } + { + name = "opentelemetry-appender-tracing"; + packageId = "opentelemetry-appender-tracing"; + } + { + name = "opentelemetry-otlp"; + packageId = "opentelemetry-otlp"; + features = [ "grpc-tonic" "gzip-tonic" "logs" ]; + } + { + name = "opentelemetry-semantic-conventions"; + packageId = "opentelemetry-semantic-conventions"; + } + { + name = "opentelemetry_sdk"; + packageId = "opentelemetry_sdk"; + features = [ "rt-tokio" "logs" "rt-tokio" "spec_unstable_logs_enabled" ]; + } + { + name = "pin-project"; + packageId = "pin-project"; + } + { + name = "snafu"; + packageId = "snafu 0.8.7"; + } + { + name = "strum"; + packageId = "strum"; + features = [ "derive" ]; + } + { + name = "tokio"; + packageId = "tokio"; + features = [ "macros" "rt-multi-thread" "fs" ]; + } + { + name = "tower"; + packageId = "tower"; + features = [ "util" ]; + } + { + name = "tracing"; + packageId = "tracing"; + } + { + name = "tracing-appender"; + packageId = "tracing-appender"; + } + { + name = "tracing-opentelemetry"; + packageId = "tracing-opentelemetry"; + } + { + name = "tracing-subscriber"; + packageId = "tracing-subscriber"; + features = [ "env-filter" "json" "env-filter" ]; + } + ]; + devDependencies = [ + { + name = "tokio"; + packageId = "tokio"; + features = [ "macros" "rt-multi-thread" "fs" ]; + } + { + name = "tracing-opentelemetry"; + packageId = "tracing-opentelemetry"; + } + ]; + features = { + "clap" = [ "dep:clap" ]; + }; + resolvedDefaultFeatures = [ "clap" ]; + }; "git2" = rec { crateName = "git2"; version = "0.20.2"; @@ -10108,6 +10333,12 @@ rec { name = "futures"; packageId = "futures 0.3.31"; } + { + name = "stackable-telemetry"; + packageId = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.96.0#stackable-telemetry@0.6.1"; + optional = true; + features = [ "clap" ]; + } { name = "http"; packageId = "http"; @@ -10174,12 +10405,6 @@ rec { name = "stackable-shared"; packageId = "stackable-shared"; } - { - name = "stackable-telemetry"; - packageId = "stackable-telemetry"; - optional = true; - features = [ "clap" ]; - } { name = "stackable-versioned"; packageId = "stackable-versioned"; @@ -10626,116 +10851,6 @@ rec { }; resolvedDefaultFeatures = [ "default" "time" ]; }; - "stackable-telemetry" = rec { - crateName = "stackable-telemetry"; - version = "0.6.1"; - edition = "2024"; - workspace_member = null; - src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "89f484ca4e86b565e083e9ad7573e21dbe29a3af"; - sha256 = "05xhfz0bd09095ljkaj950r80bchdb202d8nka95cq356y4wha4c"; - }; - libName = "stackable_telemetry"; - authors = [ - "Stackable GmbH " - ]; - dependencies = [ - { - name = "axum"; - packageId = "axum"; - features = [ "http2" ]; - } - { - name = "clap"; - packageId = "clap"; - optional = true; - features = [ "derive" "cargo" "env" ]; - } - { - name = "futures-util"; - packageId = "futures-util"; - } - { - name = "opentelemetry"; - packageId = "opentelemetry"; - features = [ "logs" ]; - } - { - name = "opentelemetry-appender-tracing"; - packageId = "opentelemetry-appender-tracing"; - } - { - name = "opentelemetry-otlp"; - packageId = "opentelemetry-otlp"; - features = [ "grpc-tonic" "gzip-tonic" "logs" ]; - } - { - name = "opentelemetry-semantic-conventions"; - packageId = "opentelemetry-semantic-conventions"; - } - { - name = "opentelemetry_sdk"; - packageId = "opentelemetry_sdk"; - features = [ "rt-tokio" "logs" "rt-tokio" "spec_unstable_logs_enabled" ]; - } - { - name = "pin-project"; - packageId = "pin-project"; - } - { - name = "snafu"; - packageId = "snafu 0.8.7"; - } - { - name = "strum"; - packageId = "strum"; - features = [ "derive" ]; - } - { - name = "tokio"; - packageId = "tokio"; - features = [ "macros" "rt-multi-thread" "fs" ]; - } - { - name = "tower"; - packageId = "tower"; - features = [ "util" ]; - } - { - name = "tracing"; - packageId = "tracing"; - } - { - name = "tracing-appender"; - packageId = "tracing-appender"; - } - { - name = "tracing-opentelemetry"; - packageId = "tracing-opentelemetry"; - } - { - name = "tracing-subscriber"; - packageId = "tracing-subscriber"; - features = [ "env-filter" "json" "env-filter" ]; - } - ]; - devDependencies = [ - { - name = "tokio"; - packageId = "tokio"; - features = [ "macros" "rt-multi-thread" "fs" ]; - } - { - name = "tracing-opentelemetry"; - packageId = "tracing-opentelemetry"; - } - ]; - features = { - "clap" = [ "dep:clap" ]; - }; - resolvedDefaultFeatures = [ "clap" ]; - }; "stackable-versioned" = rec { crateName = "stackable-versioned"; version = "0.8.1"; diff --git a/crate-hashes.json b/crate-hashes.json index 30a54dc0..12b48568 100644 --- a/crate-hashes.json +++ b/crate-hashes.json @@ -8,5 +8,6 @@ "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.96.0#stackable-telemetry@0.6.1": "05xhfz0bd09095ljkaj950r80bchdb202d8nka95cq356y4wha4c", "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.96.0#stackable-versioned-macros@0.8.1": "05xhfz0bd09095ljkaj950r80bchdb202d8nka95cq356y4wha4c", "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.96.0#stackable-versioned@0.8.1": "05xhfz0bd09095ljkaj950r80bchdb202d8nka95cq356y4wha4c", + "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-telemetry-0.6.1#stackable-telemetry@0.6.1": "0hiymhr40ix4jv9dmvp5d009xs6v0frvllr2xkf5mw43rcg44mgd", "git+https://github.com/stackabletech/product-config.git?tag=0.7.0#product-config@0.7.0": "0gjsm80g6r75pm3824dcyiz4ysq1ka4c1if6k1mjm9cnd5ym0gny" } \ No newline at end of file From b2a7c96f2d3a0097ccc1ad12efb2110c3005ac44 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 12 Sep 2025 11:42:06 +0200 Subject: [PATCH 19/24] Try to use semantic conventions --- rust/cert-tools/src/main.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/rust/cert-tools/src/main.rs b/rust/cert-tools/src/main.rs index 640ed8a9..ab20a89d 100644 --- a/rust/cert-tools/src/main.rs +++ b/rust/cert-tools/src/main.rs @@ -55,23 +55,28 @@ fn generate_pkcs12_truststore(cli_args: GeneratePkcs12) -> Result<(), snafu::Wha for certificate in certificates_list { let sha256 = certificate.sha256_digest()?; + // Trying to stick to https://opentelemetry.io/docs/specs/semconv/registry/attributes/tls/#tls-attributes + // Converting `Asn1TimeRef` to a ISO 8601 timestamp really sucks, so we omitted that. if let Some(existing) = certificates.get(&*sha256) { warn!( ?source, - sha25 = hex::encode(sha256), + hash.sha256 = hex::encode(sha256).to_uppercase(), existing.not_before = ?existing.not_before(), existing.not_after = ?existing.not_after(), existing.subject = ?existing.subject_name(), + existing.issuer = ?existing.issuer_name(), existing.serial = ?existing.serial_as_hex()?, new.not_before = ?certificate.not_before(), new.not_after = ?certificate.not_after(), new.subject = ?certificate.subject_name(), + new.issuer = ?certificate.issuer_name(), new.serial = ?existing.serial_as_hex()?, "Skipped certificate as a cert with the same SHA256 hash was already added", ); } else { info!( subject = ?certificate.subject_name(), + issuer = ?certificate.issuer_name(), not_before = ?certificate.not_before(), not_after = ?certificate.not_after(), serial = ?certificate.serial_as_hex()?, From 446ddeceb8c694eeb600c69d724118fda5eedb71 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 12 Sep 2025 13:48:39 +0200 Subject: [PATCH 20/24] Update rust/cert-tools/src/cli_args.rs Co-authored-by: Nick <10092581+NickLarsenNZ@users.noreply.github.com> --- rust/cert-tools/src/cli_args.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/rust/cert-tools/src/cli_args.rs b/rust/cert-tools/src/cli_args.rs index 5ff1e212..d6250214 100644 --- a/rust/cert-tools/src/cli_args.rs +++ b/rust/cert-tools/src/cli_args.rs @@ -91,7 +91,6 @@ impl CertInput { let certs = parse_pem_contents(&file_contents).with_whatever_context(|_| { format!( "failed to parse PEM contents from {path:?}", - path = self.path() ) })?; ensure_whatever!( From a0fb813a73506acc44f3d3694ce99a4388a0382a Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 12 Sep 2025 13:48:59 +0200 Subject: [PATCH 21/24] Update rust/cert-tools/src/parsers.rs Co-authored-by: Nick <10092581+NickLarsenNZ@users.noreply.github.com> --- rust/cert-tools/src/parsers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/cert-tools/src/parsers.rs b/rust/cert-tools/src/parsers.rs index c1930fc8..bbd24059 100644 --- a/rust/cert-tools/src/parsers.rs +++ b/rust/cert-tools/src/parsers.rs @@ -73,7 +73,7 @@ pub fn parse_pkcs12_file_workaround( "pkcs12", "-nokeys", "-password", - &format!("pass:{}", password), + &format!("pass:{password}"), // That's the important part!!! "-legacy", ]) From a878d348f34a095566711566b136d43e83e9d1d6 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 12 Sep 2025 13:49:59 +0200 Subject: [PATCH 22/24] Update rust/cert-tools/src/parsers.rs Co-authored-by: Nick <10092581+NickLarsenNZ@users.noreply.github.com> --- rust/cert-tools/src/parsers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/cert-tools/src/parsers.rs b/rust/cert-tools/src/parsers.rs index bbd24059..8b89c93a 100644 --- a/rust/cert-tools/src/parsers.rs +++ b/rust/cert-tools/src/parsers.rs @@ -98,7 +98,7 @@ pub fn parse_pkcs12_file_workaround( .whatever_context("Failed to read openssl process output")?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); - whatever!("openssl process failed with STDERR: {stderr:?}"); + whatever!("openssl process failed with STDERR:\n{stderr}"); } parse_pem_contents(&output.stdout).with_whatever_context(|_| { From e2c39a2ce0682d966e5abea99f0f932a8336d818 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 12 Sep 2025 13:50:08 +0200 Subject: [PATCH 23/24] Update rust/cert-tools/src/parsers.rs Co-authored-by: Nick <10092581+NickLarsenNZ@users.noreply.github.com> --- rust/cert-tools/src/parsers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/cert-tools/src/parsers.rs b/rust/cert-tools/src/parsers.rs index 8b89c93a..57bdee41 100644 --- a/rust/cert-tools/src/parsers.rs +++ b/rust/cert-tools/src/parsers.rs @@ -103,7 +103,7 @@ pub fn parse_pkcs12_file_workaround( parse_pem_contents(&output.stdout).with_whatever_context(|_| { format!( - "failed to parse openssl process output, which should be PEM. STDOUT: {stdout}?", + "failed to parse openssl process output, which should be PEM. STDOUT:\n{stdout}", stdout = String::from_utf8_lossy(&output.stdout) ) }) From d275f20b6c86549ac4802ef078607ed1060a533d Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 12 Sep 2025 13:57:25 +0200 Subject: [PATCH 24/24] fmt --- rust/cert-tools/src/cli_args.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/rust/cert-tools/src/cli_args.rs b/rust/cert-tools/src/cli_args.rs index d6250214..3eb63929 100644 --- a/rust/cert-tools/src/cli_args.rs +++ b/rust/cert-tools/src/cli_args.rs @@ -89,9 +89,7 @@ impl CertInput { let file_contents = read_file_fn(path)?; let certs = parse_pem_contents(&file_contents).with_whatever_context(|_| { - format!( - "failed to parse PEM contents from {path:?}", - ) + format!("failed to parse PEM contents from {path:?}",) })?; ensure_whatever!( !certs.is_empty(),