From: Shannon Sterz <s.sterz@proxmox.com>
To: pbs-devel@lists.proxmox.com
Subject: [PATCH proxmox v3 02/16] tls-certificates: add days_valid parameter to create_self_signed_cert
Date: Wed, 5 Aug 2026 17:52:55 +0200 [thread overview]
Message-ID: <20260805155308.519896-4-s.sterz@proxmox.com> (raw)
In-Reply-To: <20260805155308.519896-2-s.sterz@proxmox.com>
to allow specifying how long a certificate should be valid for. also
adds unit tests to verify this behavior.
Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
---
Notes:
imo, we could go down even more. as far as i am aware there is no real
limit that is being enforced here for self-signed certificates from a
browser perspective. they are already trusted on an exemption-basis
anyway. however, certificates signed by public CAs will only be valid
for a maximum of 47 days by 2029 [1].
hence, i would personally either adopt the same limit or go down to a
year, as a sensible middle-ground. certificate rotation should really
be automated even in self-signed scenarios. we also had cases in the
past, where customers already ran into an issue because they wanted to
limit the lifetime of their certificates below 30 days [2]. meaning
that there is a need out there for shorter-lived certificates (though,
in that case a custom CA & ACME setup was used).
[1]: https://github.com/cabforum/servercert/pull/553
[2]: https://bugzilla.proxmox.com/show_bug.cgi?id=6372
proxmox-tls-certificates/src/util.rs | 132 ++++++++++++++++++++++++++-
1 file changed, 131 insertions(+), 1 deletion(-)
diff --git a/proxmox-tls-certificates/src/util.rs b/proxmox-tls-certificates/src/util.rs
index 346872ac..98aa042e 100644
--- a/proxmox-tls-certificates/src/util.rs
+++ b/proxmox-tls-certificates/src/util.rs
@@ -15,10 +15,13 @@ use crate::CertificateInfo;
/// subject alternative names will be set based on this value.
/// * `domain`: The optional domain which the node is in. If set, it will be appended to `nodename`
/// to derive an FQDN as common and subject alternative name.
+/// * `days_valid`: The optional number of days that the certificate should be valid for. If not
+/// set, the certificate will be valid for 3650 days, almost ten years.
pub fn create_self_signed_cert(
product_name: &str,
nodename: &str,
domain: Option<&str>,
+ days_valid: Option<u32>,
) -> Result<(PKey<Private>, X509), Error> {
let rsa = Rsa::generate(4096).unwrap();
@@ -28,7 +31,7 @@ pub fn create_self_signed_cert(
let today = openssl::asn1::Asn1Time::days_from_now(0)?;
x509.set_not_before(&today)?;
- let expire = openssl::asn1::Asn1Time::days_from_now(365 * 10)?;
+ let expire = openssl::asn1::Asn1Time::days_from_now(days_valid.unwrap_or(365 * 10))?;
x509.set_not_after(&expire)?;
let mut fqdn = nodename.to_owned();
@@ -207,3 +210,130 @@ fn asn1_time_to_unix(time: &openssl::asn1::Asn1TimeRef) -> Result<i64, Error> {
let mut c_tm = unsafe { c_tm.assume_init() };
proxmox_time::timegm(&mut c_tm)
}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ fn get_self_signed_certificate_info(
+ product_name: &str,
+ nodename: &str,
+ domain: Option<&str>,
+ days_valid: Option<u32>,
+ ) -> CertificateInfo {
+ let cert = match create_self_signed_cert(product_name, nodename, domain, days_valid) {
+ Ok((_priv_key, certificate)) => certificate,
+ Err(e) => panic!("could not create self signed certificate - {e}"),
+ };
+
+ let pem_bytes = match cert.to_pem() {
+ Ok(pem) => pem,
+ Err(e) => panic!("could not get pem bytes from signed certificate - {e}"),
+ };
+
+ match CertificateInfo::from_pem("", &pem_bytes) {
+ Ok(cert_info) => cert_info,
+ Err(e) => panic!("could not parse cert pem to cert info - {e}"),
+ }
+ }
+
+ #[test]
+ fn self_signed_certificate_correct_with_product_and_name_only() {
+ let cert_info =
+ get_self_signed_certificate_info("Proxmox Test Product", "name", None, None);
+
+ assert!(cert_info.subject.contains("O = Proxmox Test Product"));
+ assert!(cert_info.subject.contains("CN = name"));
+ assert_eq!(
+ cert_info.san,
+ vec![
+ "IP: [127, 0, 0, 1]",
+ "IP: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]",
+ "DNS: localhost",
+ "DNS: name"
+ ]
+ );
+ }
+
+ #[test]
+ fn self_signed_certificate_correct_with_domain() {
+ let cert_info =
+ get_self_signed_certificate_info("Proxmox Test Product", "name", Some("fqdn"), None);
+
+ assert!(cert_info.subject.contains("O = Proxmox Test Product"));
+ assert!(cert_info.subject.contains("CN = name.fqdn"));
+ assert_eq!(
+ cert_info.san,
+ vec![
+ "IP: [127, 0, 0, 1]",
+ "IP: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]",
+ "DNS: localhost",
+ "DNS: name",
+ "DNS: name.fqdn"
+ ]
+ );
+ }
+
+ #[test]
+ fn self_signed_certificate_correct_with_days_valid() {
+ let in_two_days = proxmox_time::epoch_i64() + 2 * 24 * 60 * 60;
+ let cert_info =
+ get_self_signed_certificate_info("Proxmox Test Product", "name", None, Some(2));
+
+ assert!(
+ cert_info
+ .notafter
+ .map(|a| a >= in_two_days)
+ .unwrap_or_default()
+ );
+ assert!(
+ cert_info
+ .notafter
+ .map(|a| a < in_two_days + 24 * 60 * 60)
+ .unwrap_or_default()
+ );
+ assert!(cert_info.subject.contains("O = Proxmox Test Product"));
+ assert!(cert_info.subject.contains("CN = name"));
+ assert_eq!(
+ cert_info.san,
+ vec![
+ "IP: [127, 0, 0, 1]",
+ "IP: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]",
+ "DNS: localhost",
+ "DNS: name",
+ ]
+ );
+ }
+
+ #[test]
+ fn self_signed_certificate_correct_with_domain_and_days_valid() {
+ let in_two_days = proxmox_time::epoch_i64() + 2 * 24 * 60 * 60;
+ let cert_info =
+ get_self_signed_certificate_info("Proxmox Test Product", "name", Some("fqdn"), Some(2));
+
+ assert!(
+ cert_info
+ .notafter
+ .map(|a| a >= in_two_days)
+ .unwrap_or_default()
+ );
+ assert!(
+ cert_info
+ .notafter
+ .map(|a| a < in_two_days + 24 * 60 * 60)
+ .unwrap_or_default()
+ );
+ assert!(cert_info.subject.contains("O = Proxmox Test Product"));
+ assert!(cert_info.subject.contains("CN = name.fqdn"));
+ assert_eq!(
+ cert_info.san,
+ vec![
+ "IP: [127, 0, 0, 1]",
+ "IP: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]",
+ "DNS: localhost",
+ "DNS: name",
+ "DNS: name.fqdn"
+ ]
+ );
+ }
+}
--
2.47.3
next prev parent reply other threads:[~2026-08-05 15:53 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-05 15:52 [PATCH datacenter-manager/proxmox{,-backup} v3 00/16] TLS Certificate Rotation Shannon Sterz
2026-08-05 15:52 ` [PATCH proxmox v3 01/16] acme-api/tls-certificates: add new crate collecting tls related helpers Shannon Sterz
2026-08-05 15:52 ` Shannon Sterz [this message]
2026-08-05 15:52 ` [PATCH proxmox v3 03/16] tls-certificates: add self_signed_cert_expires_in to check certificates Shannon Sterz
2026-08-05 15:52 ` [PATCH proxmox v3 04/16] acme-api: stop re-exporting create_self_signed_cert Shannon Sterz
2026-08-05 15:52 ` [PATCH proxmox-backup v3 05/16] config: use proxmox_tls_certificates for generating self-signed certificates Shannon Sterz
2026-08-05 15:52 ` [PATCH proxmox-backup v3 06/16] config/server/api: add certificate renewal logic including notifications Shannon Sterz
2026-08-05 15:53 ` [PATCH proxmox-backup v3 07/16] daily update: warn about excessive self-signed certificate lifetime Shannon Sterz
2026-08-05 15:53 ` [PATCH proxmox-backup v3 08/16] docs: document force refreshing long-lived certificates Shannon Sterz
2026-08-05 15:53 ` [PATCH proxmox-backup v3 09/16] backup-manager cli: `cert update` can create auth and csrf key Shannon Sterz
2026-08-05 15:53 ` [PATCH proxmox-backup v3 10/16] notifications: use `Severity::Error` for acme renewal failures Shannon Sterz
2026-08-05 15:53 ` [PATCH datacenter-manager v3 11/16] certs: use proxmox-tls-certificates directly, add days_valid parameter Shannon Sterz
2026-08-05 15:53 ` [PATCH datacenter-manager v3 12/16] api/auth/bin: add certificate renewal logic Shannon Sterz
2026-08-05 15:53 ` [PATCH datacenter-manager v3 13/16] cli: expose certificate management endpoints via the cli Shannon Sterz
2026-08-05 15:53 ` [PATCH datacenter-manager v3 14/16] daily-update: warn about certificates with excessive lifetimes Shannon Sterz
2026-08-05 15:53 ` [PATCH datacenter-manager v3 15/16] docs: add section on forcing a new self-signed certificate Shannon Sterz
2026-08-05 15:53 ` [PATCH datacenter-manager v3 16/16] docs/certificates: use correct certificate file name Shannon Sterz
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260805155308.519896-4-s.sterz@proxmox.com \
--to=s.sterz@proxmox.com \
--cc=pbs-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.