From 56741d98f1c2bf43fbf631357bc3107b5203403f Mon Sep 17 00:00:00 2001 From: Techassi Date: Fri, 30 Jan 2026 13:00:06 +0100 Subject: [PATCH 1/4] feat(webhook): Improve instrumentation for certificate rotation --- crates/stackable-webhook/src/lib.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/crates/stackable-webhook/src/lib.rs b/crates/stackable-webhook/src/lib.rs index 00dfaba7c..4424d4a85 100644 --- a/crates/stackable-webhook/src/lib.rs +++ b/crates/stackable-webhook/src/lib.rs @@ -187,9 +187,21 @@ impl WebhookServer { // run associated function consumes self. This in turn means that when the receiver is // polled, it will return `Ok(Ready(None))`, which will cause this while loop to break // and the future to complete. - while let Some(cert) = cert_rx.recv().await { + while let Some(certificate) = cert_rx.recv().await { + // NOTE (@Techassi): There are currently NO semantic conventions for X509 certificates + // and as such, these are pretty much made up and potentially not ideal. + #[rustfmt::skip] + tracing::info!( + x509.not_before = certificate.tbs_certificate.validity.not_before.to_string(), + x509.not_after = certificate.tbs_certificate.validity.not_after.to_string(), + x509.serial_number = certificate.tbs_certificate.serial_number.to_string(), + x509.subject = certificate.tbs_certificate.subject.to_string(), + x509.issuer = certificate.tbs_certificate.issuer.to_string(), + "rotate certificate for registered webhooks" + ); + // The caBundle needs to be provided as a base64-encoded PEM envelope. - let ca_bundle = cert + let ca_bundle = certificate .to_pem(LineEnding::LF) .context(EncodeCertificateAuthorityAsPemSnafu)?; let ca_bundle = ByteString(ca_bundle.as_bytes().to_vec()); From 0a122ec709f11c4556c365fccc6a937aa7c25da3 Mon Sep 17 00:00:00 2001 From: Techassi Date: Fri, 30 Jan 2026 13:04:00 +0100 Subject: [PATCH 2/4] chore(webhook): Adjust CA bundle handling --- .../src/webhooks/conversion_webhook.rs | 15 +++++++-------- crates/stackable-webhook/src/webhooks/mod.rs | 2 +- .../src/webhooks/mutating_webhook.rs | 10 +++++----- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/crates/stackable-webhook/src/webhooks/conversion_webhook.rs b/crates/stackable-webhook/src/webhooks/conversion_webhook.rs index f7870e41c..64cd58f2d 100644 --- a/crates/stackable-webhook/src/webhooks/conversion_webhook.rs +++ b/crates/stackable-webhook/src/webhooks/conversion_webhook.rs @@ -22,8 +22,7 @@ use snafu::{ResultExt, Snafu}; use tokio::sync::oneshot; use tracing::instrument; -use super::{Webhook, WebhookError}; -use crate::WebhookServerOptions; +use crate::{Webhook, WebhookError, WebhookServerOptions}; #[derive(Debug, Snafu)] pub enum ConversionWebhookError { @@ -138,7 +137,7 @@ impl ConversionWebhook { } #[instrument( - skip(self, crd, crd_api, new_ca_bundle), + skip(self, crd, crd_api, ca_bundle), fields( name = crd.name_any(), kind = &crd.spec.names.kind @@ -148,7 +147,7 @@ impl ConversionWebhook { &self, mut crd: CustomResourceDefinition, crd_api: &Api, - new_ca_bundle: &ByteString, + ca_bundle: ByteString, options: &WebhookServerOptions, ) -> Result<(), WebhookError> { let crd_kind = &crd.spec.names.kind; @@ -178,7 +177,7 @@ impl ConversionWebhook { port: Some(options.socket_addr.port().into()), }), // Here, ByteString takes care of encoding the provided content as base64. - ca_bundle: Some(new_ca_bundle.to_owned()), + ca_bundle: Some(ca_bundle), url: None, }), }), @@ -247,15 +246,15 @@ where self.options.disable_crd_maintenance } - #[instrument(skip(self, new_ca_bundle))] + #[instrument(skip(self, ca_bundle))] async fn handle_certificate_rotation( &mut self, - new_ca_bundle: &ByteString, + ca_bundle: &ByteString, options: &WebhookServerOptions, ) -> Result<(), WebhookError> { let crd_api: Api = Api::all(self.client.clone()); for (crd, _) in &self.crds_and_handlers { - self.reconcile_crd(crd.clone(), &crd_api, new_ca_bundle, options) + self.reconcile_crd(crd.clone(), &crd_api, ca_bundle.to_owned(), options) .await?; } diff --git a/crates/stackable-webhook/src/webhooks/mod.rs b/crates/stackable-webhook/src/webhooks/mod.rs index 23cbca7ef..ac165efac 100644 --- a/crates/stackable-webhook/src/webhooks/mod.rs +++ b/crates/stackable-webhook/src/webhooks/mod.rs @@ -48,7 +48,7 @@ pub trait Webhook { /// Webhooks are informed about new certificates by this function and can react accordingly. async fn handle_certificate_rotation( &mut self, - new_ca_bundle: &ByteString, + ca_bundle: &ByteString, options: &WebhookServerOptions, ) -> Result<(), WebhookError>; } diff --git a/crates/stackable-webhook/src/webhooks/mutating_webhook.rs b/crates/stackable-webhook/src/webhooks/mutating_webhook.rs index 67172fc80..9ff9192fd 100644 --- a/crates/stackable-webhook/src/webhooks/mutating_webhook.rs +++ b/crates/stackable-webhook/src/webhooks/mutating_webhook.rs @@ -12,8 +12,7 @@ use serde::{Serialize, de::DeserializeOwned}; use snafu::{ResultExt, Snafu}; use tracing::instrument; -use super::{Webhook, WebhookError}; -use crate::{WebhookServerOptions, webhooks::create_webhook_client_config}; +use crate::{Webhook, WebhookError, WebhookServerOptions, webhooks::create_webhook_client_config}; #[derive(Debug, Snafu)] pub enum MutatingWebhookError { @@ -209,14 +208,15 @@ where self.options.disable_mwc_maintenance } - #[instrument(skip(self, new_ca_bundle))] + #[instrument(skip(self, ca_bundle))] async fn handle_certificate_rotation( &mut self, - new_ca_bundle: &ByteString, + ca_bundle: &ByteString, options: &WebhookServerOptions, ) -> Result<(), WebhookError> { let mut mutating_webhook_configuration = self.mutating_webhook_configuration.clone(); let mwc_name = mutating_webhook_configuration.name_any(); + tracing::info!( k8s.mutatingwebhookconfiguration.name = mwc_name, "reconciling mutating webhook configurations" @@ -225,7 +225,7 @@ where for webhook in mutating_webhook_configuration.webhooks.iter_mut().flatten() { // We know how we can be called (and with what certificate), so we can always set that webhook.client_config = - create_webhook_client_config(options, new_ca_bundle.to_owned(), self.http_path()); + create_webhook_client_config(options, ca_bundle.to_owned(), self.http_path()); } let mwc_api: Api = Api::all(self.client.clone()); From fd256cfa809f3a7019e07d6f43125c7f295d285f Mon Sep 17 00:00:00 2001 From: Techassi Date: Fri, 30 Jan 2026 13:05:53 +0100 Subject: [PATCH 3/4] chore(certs): Fix typo in dev comment --- crates/stackable-certs/src/ca/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/stackable-certs/src/ca/mod.rs b/crates/stackable-certs/src/ca/mod.rs index c3bb57099..7f7160f09 100644 --- a/crates/stackable-certs/src/ca/mod.rs +++ b/crates/stackable-certs/src/ca/mod.rs @@ -247,7 +247,7 @@ where // // The root profile doesn't add the AuthorityKeyIdentifier extension. // We manually add it below by using the 160-bit SHA-1 hash of the - // subject pulic key. This conforms to one of the outlined methods for + // subject public key. This conforms to one of the outlined methods for // generating key identifiers outlined in RFC 5280, section 4.2.1.2. // // Prepare extensions so we can avoid clones. From fed414374d0e2d71a4664455caa77e5551bc364d Mon Sep 17 00:00:00 2001 From: Techassi Date: Tue, 3 Feb 2026 10:50:02 +0100 Subject: [PATCH 4/4] chore: Add changelog entry --- crates/stackable-webhook/CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/stackable-webhook/CHANGELOG.md b/crates/stackable-webhook/CHANGELOG.md index e48c77b18..1265a2aa9 100644 --- a/crates/stackable-webhook/CHANGELOG.md +++ b/crates/stackable-webhook/CHANGELOG.md @@ -10,7 +10,12 @@ All notable changes to this project will be documented in this file. Both `WebhookServer::run` and `TlsServer::run` now require passing a shutdown signal, which is any `Future` ([#1144]). +### Changed + +- Improve certificate rotation instrumentation ([#1145]). + [#1144]: https://github.com/stackabletech/operator-rs/pull/1144 +[#1145]: https://github.com/stackabletech/operator-rs/pull/1145 ## [0.8.1] - 2026-01-07