From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [IPv6:2a0f:8001:1:32::40]) by lore.proxmox.com (Postfix) with ESMTPS id E93AD1FF0E0 for ; Thu, 06 Aug 2026 15:09:50 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 928312153E; Thu, 06 Aug 2026 15:09:50 +0200 (CEST) Date: Thu, 06 Aug 2026 15:09:37 +0200 From: Fabian =?iso-8859-1?q?Gr=FCnbichler?= Subject: Re: [PATCH proxmox v3 03/16] tls-certificates: add self_signed_cert_expires_in to check certificates To: pbs-devel@lists.proxmox.com, Shannon Sterz References: <20260805155308.519896-2-s.sterz@proxmox.com> <20260805155308.519896-5-s.sterz@proxmox.com> In-Reply-To: <20260805155308.519896-5-s.sterz@proxmox.com> MIME-Version: 1.0 User-Agent: astroid/0.17.0 (https://github.com/astroidmail/astroid) Message-Id: <1786019255.vl8y2g6kbs.astroid@yuna.none> Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1786021765794 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.111 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) RCVD_IN_DNSWL_LOW -0.7 Sender listed at https://www.dnswl.org/, low trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: M3KFXBZFWHXOBLMNFJ2A2VPX4MEBRZWU X-Message-ID-Hash: M3KFXBZFWHXOBLMNFJ2A2VPX4MEBRZWU X-MailFrom: f.gruenbichler@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox Backup Server development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: On August 5, 2026 5:52 pm, Shannon Sterz wrote: > this is useful when self-signed certificates are in use. it > heuristically checks whether a certificate is self-signed by the > specified node and returns for how many more days the certificate is > valid if it is a self-signed certificate. >=20 > Signed-off-by: Shannon Sterz > --- > proxmox-tls-certificates/Cargo.toml | 2 + > proxmox-tls-certificates/src/lib.rs | 2 +- > proxmox-tls-certificates/src/util.rs | 55 ++++++++++++++++++++++++++++ > 3 files changed, 58 insertions(+), 1 deletion(-) >=20 > diff --git a/proxmox-tls-certificates/Cargo.toml b/proxmox-tls-certificat= es/Cargo.toml > index 3a7b97cd..658a700d 100644 > --- a/proxmox-tls-certificates/Cargo.toml > +++ b/proxmox-tls-certificates/Cargo.toml > @@ -16,6 +16,7 @@ hex =3D { workspace =3D true, optional =3D true } > libc =3D { workspace =3D true, optional =3D true } > openssl =3D { workspace =3D true, optional =3D true } > serde =3D { workspace =3D true, features =3D [ "derive" ] } > +regex =3D { workspace =3D true, optional =3D true } > =20 > proxmox-schema =3D { workspace =3D true, features =3D [ "api-macro" ] } > proxmox-time =3D { workspace =3D true, optional =3D true } > @@ -29,6 +30,7 @@ impl =3D [ > "dep:hex", > "dep:libc", > "dep:openssl", > + "dep:regex", > =20 > "dep:proxmox-time", > "dep:proxmox-uuid", > diff --git a/proxmox-tls-certificates/src/lib.rs b/proxmox-tls-certificat= es/src/lib.rs > index 454c700f..66def7d6 100644 > --- a/proxmox-tls-certificates/src/lib.rs > +++ b/proxmox-tls-certificates/src/lib.rs > @@ -4,4 +4,4 @@ pub use types::CertificateInfo; > #[cfg(feature =3D "impl")] > mod util; > #[cfg(feature =3D "impl")] > -pub use util::create_self_signed_cert; > +pub use util::{create_self_signed_cert, self_signed_cert_expires_in}; > diff --git a/proxmox-tls-certificates/src/util.rs b/proxmox-tls-certifica= tes/src/util.rs > index 98aa042e..a07903d8 100644 > --- a/proxmox-tls-certificates/src/util.rs > +++ b/proxmox-tls-certificates/src/util.rs > @@ -2,12 +2,54 @@ use std::mem::MaybeUninit; > =20 > use anyhow::{Error, bail, format_err}; > use foreign_types::ForeignTypeRef; > +use openssl::asn1::Asn1Time; > use openssl::pkey::{PKey, Private}; > use openssl::rsa::Rsa; > use openssl::x509::{X509, X509Builder}; > =20 > use crate::CertificateInfo; > =20 > +proxmox_schema::const_regex! { > + SELF_SIGNED_REGEX =3D > + // O =3D $product_name, OU =3D $uuid, CN =3D $nodename > + r#"^O\s?=3D\s(?P.*)?, OU\s?=3D\s?[0-9a-fA-F]{8}-(?:[0-9= a-fA-F]{4}-){3}[0-9a-fA-F]{12}, CN\s?=3D\s?(?P.*)$"#; > +} > + > +/// Check whether the current certificate is self-signed and returns the= remaining days the > +/// certificate is valid for. I am a bit confused by this description - do we want to check that it is self-signed? then we should check that cert.subject =3D=3D cert.issuer (or better, actual_cert.issued(&actual_cert) via `openssl` ;)) or do we want to check that it seems to be created by our product stack? then we should maybe name this a bit differently and document what the semantics/expectations are? or should we maybe make this two helpers, one checking expiry, one checking self-signed/..-ness (and if needed for a smooth migration, keep a deprecated combined helper around for a bit)? since the next patch drops the compat-re-export of this helper, it seems to me we could rework the interface and then drop the old one entirely? > +pub fn self_signed_cert_expires_in( > + product_name: &str, > + nodename: &str, > + domain: Option<&str>, > + cert: CertificateInfo, > +) -> Result, Error> { > + let Some(captures) =3D (SELF_SIGNED_REGEX.regex_obj)().captures(&cer= t.issuer) else { > + return Ok(None); > + }; > + > + let mut fqdn =3D nodename.to_string(); > + > + if let Some(domain) =3D domain { > + fqdn =3D format!("{fqdn}.{domain}"); > + } > + > + if captures["product"] !=3D *product_name { > + return Ok(None); > + } > + > + if captures["fqdn"] !=3D fqdn { > + return Ok(None); > + } > + > + let now =3D Asn1Time::from_unix(proxmox_time::epoch_i64())?; > + let not_after =3D Asn1Time::from_unix(cert.notafter.ok_or_else(|| { > + format_err!("Could not get \"not after\" epoch for current certi= ficate.") > + })?)?; > + > + let diff =3D now.diff(¬_after)?; > + Ok(Some(diff.days)) > +} > + > /// Create a new self-signed certificate and its private key. > /// > /// * `product_name`: The name of the product, will be used as the organ= ization of the certificate. > @@ -237,6 +279,19 @@ mod test { > } > } > =20 > + #[test] > + fn self_signed_cert_expires_in_gets_correct_days() { > + let cert_info =3D > + get_self_signed_certificate_info("Proxmox Test Product", "na= me", Some("fqdn"), Some(2)); > + > + match self_signed_cert_expires_in("Proxmox Test Product", "name"= , Some("fqdn"), cert_info) { > + // allow the range [1,2] due to truncation, the test could t= ake too long to always yield "2" here > + Ok(Some(days)) =3D> assert!((1..=3D2).contains(&days)), > + Ok(None) =3D> panic!("not a self-signed certificate"), > + Err(e) =3D> panic!("error occurred checking certificate - {e= :#}"), > + } > + } > + > #[test] > fn self_signed_certificate_correct_with_product_and_name_only() { > let cert_info =3D > --=20 > 2.47.3 >=20 >=20 >=20 >=20 >=20 >=20