all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Shannon Sterz <s.sterz@proxmox.com>
To: pbs-devel@lists.proxmox.com
Subject: [PATCH datacenter-manager 12/16] api/auth/bin: add certificate renewal logic
Date: Thu, 30 Jul 2026 15:31:54 +0200	[thread overview]
Message-ID: <20260730133158.418015-13-s.sterz@proxmox.com> (raw)
In-Reply-To: <20260730133158.418015-1-s.sterz@proxmox.com>

the daily-update service is used to check whether a self-signed
certificate is in use and renews it if it would expire within the next
15 days.

a self-signed certificate is detected by parsing the issuer field of
the certificate. if it aligns with how self-signed certificates are
created by this instance of proxmox datacenter manager, it will be
deemed self-signed.

15 days was chosen as that should give a reasonable trade-off between
not failing if the daily-update service can't run on the specific day
that the certificate would expire and not refreshing the certificate
too often. note that 15 days before expiry corresponds to let's
encrypt's recommendation for when to update new certificates that are
valid for 45 days:

> Acceptable behavior includes renewing certificates at approximately
> two thirds of the way through the current certificate’s lifetime.
>
> -  https://letsencrypt.org/2025/12/02/from-90-to-45#action-required

15 days before expiry is about two thirds of the lifetime of a
certificate that lasts for 45 days.

Signed-off-by: Shannon Sterz <s.sterz@proxmox.com>
---
 server/src/api/nodes/certificates.rs          | 19 +++++------
 server/src/auth/certs.rs                      | 16 ++++++++-
 ...proxmox-datacenter-manager-daily-update.rs | 34 +++++++++++++++++++
 3 files changed, 57 insertions(+), 12 deletions(-)

diff --git a/server/src/api/nodes/certificates.rs b/server/src/api/nodes/certificates.rs
index 0f391499..515c4802 100644
--- a/server/src/api/nodes/certificates.rs
+++ b/server/src/api/nodes/certificates.rs
@@ -15,7 +15,7 @@ use proxmox_schema::api_types::NODE_SCHEMA;
 
 use pdm_api_types::PRIV_SYS_MODIFY;
 
-use crate::auth::certs::{API_CERT_FN, API_KEY_FN};
+use crate::auth::certs::{API_CERT_FN, API_KEY_FN, get_certificate_info, get_certificate_pem};
 
 pub const ROUTER: Router = Router::new()
     .get(&list_subdirs_api_method!(SUBDIRS))
@@ -43,16 +43,6 @@ const ACME_SUBDIRS: SubdirMap = &[(
         .put(&API_METHOD_RENEW_ACME_CERT),
 )];
 
-fn get_certificate_pem() -> Result<Vec<u8>, Error> {
-    let cert_pem = proxmox_sys::fs::file_get_contents(API_CERT_FN)?;
-    Ok(cert_pem)
-}
-
-fn get_certificate_info() -> Result<CertificateInfo, Error> {
-    let cert_pem = get_certificate_pem()?;
-    CertificateInfo::from_pem("proxy.pem", &cert_pem)
-}
-
 #[api(
     input: {
         properties: {
@@ -247,6 +237,13 @@ pub fn cert_expires_soon() -> Result<bool, Error> {
         .map_err(|err| format_err!("Failed to check certificate expiration date: {}", err))
 }
 
+/// Renews the self signed certificate. The caller needs to make sure the current certificate is
+/// really a self-signed certificate and not an ACME or custom certificate.
+pub async fn renew_self_signed_cert() -> Result<(), Error> {
+    crate::auth::certs::update_self_signed_cert(true)?;
+    crate::reload_api_certificate().await
+}
+
 fn spawn_certificate_worker(
     name: &'static str,
     force: bool,
diff --git a/server/src/auth/certs.rs b/server/src/auth/certs.rs
index c208cf4f..4c7cafcb 100644
--- a/server/src/auth/certs.rs
+++ b/server/src/auth/certs.rs
@@ -2,11 +2,13 @@ use anyhow::{Error, format_err};
 use std::path::PathBuf;
 
 use proxmox_dns_api::read_etc_resolv_conf;
+use proxmox_tls_certificates::CertificateInfo;
 
 use pdm_buildcfg::configdir;
 
 pub const API_KEY_FN: &str = configdir!("/auth/api.key");
 pub const API_CERT_FN: &str = configdir!("/auth/api.pem");
+pub const PRODUCT_NAME: &str = "Proxmox Datacenter Manager";
 
 /// Update self signed node certificate.
 pub fn update_self_signed_cert(force: bool) -> Result<(), Error> {
@@ -20,7 +22,7 @@ pub fn update_self_signed_cert(force: bool) -> Result<(), Error> {
     let resolv_conf = read_etc_resolv_conf(None)?.config;
 
     let (priv_key, cert) = proxmox_tls_certificates::create_self_signed_cert(
-        "Proxmox Datacenter Manager",
+        PRODUCT_NAME,
         proxmox_sys::nodename(),
         resolv_conf.search.as_deref(),
         None,
@@ -45,3 +47,15 @@ pub(crate) fn set_api_certificate(cert_pem: &[u8], key_pem: &[u8]) -> Result<(),
 
     Ok(())
 }
+
+/// Get the certificate as bytes in PEM format.
+pub fn get_certificate_pem() -> Result<Vec<u8>, Error> {
+    let cert_pem = proxmox_sys::fs::file_get_contents("proxy.pem")?;
+    Ok(cert_pem)
+}
+
+/// Get the `CertificateInfo` struct for the current certificate.
+pub fn get_certificate_info() -> Result<CertificateInfo, Error> {
+    let cert_pem = get_certificate_pem()?;
+    CertificateInfo::from_pem(API_CERT_FN, &cert_pem)
+}
diff --git a/server/src/bin/proxmox-datacenter-manager-daily-update.rs b/server/src/bin/proxmox-datacenter-manager-daily-update.rs
index 314b3399..803b2fc0 100644
--- a/server/src/bin/proxmox-datacenter-manager-daily-update.rs
+++ b/server/src/bin/proxmox-datacenter-manager-daily-update.rs
@@ -59,6 +59,11 @@ async fn do_update(rpcenv: &mut dyn RpcEnvironment) -> Result<(), Error> {
         log::error!("error checking certificates: {err}");
     }
 
+    println!("check if self-signed certificate requires renewal");
+    if let Err(err) = renew_self_signed_certificate().await {
+        log::error!("error checking self-signed certificate renewal: {err:#}");
+    }
+
     // TODO: cleanup tasks like in PVE?
 
     Ok(())
@@ -87,6 +92,35 @@ async fn check_acme_certificates(rpcenv: &mut dyn RpcEnvironment) -> Result<(),
     Ok(())
 }
 
+async fn renew_self_signed_certificate() -> Result<(), Error> {
+    let days = match proxmox_tls_certificates::self_signed_cert_expires_in(
+        server::auth::certs::PRODUCT_NAME,
+        proxmox_sys::nodename(),
+        proxmox_dns_api::read_etc_resolv_conf(None)?
+            .config
+            .search
+            .as_deref(),
+        server::auth::certs::get_certificate_info()?,
+    )? {
+        None => {
+            log::debug!("Certificate is not self-signed, nothing to do.");
+            return Ok(());
+        }
+        Some(days) => days,
+    };
+
+    if days <= 15 {
+        log::info!("Certificate expires within 15 days, renewing certificate...");
+        let _err = &api::nodes::certificates::renew_self_signed_cert().await;
+        // fixme: send_self_signed_renewal_notification(&err)?;
+    } else if days <= 30 {
+        log::info!("Certificate expires within 30 days.");
+        // fixme: send_upcoming_self_signed_renewal_notification(earliest_renewal)?;
+    }
+
+    Ok(())
+}
+
 async fn run(rpcenv: &mut dyn RpcEnvironment) -> Result<(), Error> {
     let api_user = pdm_config::api_user()?;
     let file_opts = CreateOptions::new().owner(api_user.uid).group(api_user.gid);
-- 
2.47.3





  parent reply	other threads:[~2026-07-30 13:32 UTC|newest]

Thread overview: 18+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-30 13:31 [PATCH datacenter-manager/proxmox{,-backup} 00/16] TLS Certificate Rotation Shannon Sterz
2026-07-30 13:31 ` [PATCH proxmox 01/16] acme-api/tls-certificates: add new crate collecting tls realted helpers Shannon Sterz
2026-07-30 13:31 ` [PATCH proxmox 02/16] tls-certificates: add days_valid parameter to create_self_signed_cert Shannon Sterz
2026-07-30 13:31 ` [PATCH proxmox 03/16] tls-certificates: add self_signed_cert_expires_in to check certificates Shannon Sterz
2026-07-30 13:31 ` [PATCH proxmox 04/16] acme-api: stop re-exporting create_self_signed_cert Shannon Sterz
2026-07-30 13:31 ` [PATCH proxmox-backup 05/16] config: use proxmox_tls_certificates for generating self-signed certificates Shannon Sterz
2026-07-30 13:31 ` [PATCH proxmox-backup 06/16] config/server/api: add certificate renewal logic including notifications Shannon Sterz
2026-07-30 13:31 ` [PATCH proxmox-backup 07/16] daily update: warn about excessive self-signed certificate lifetime Shannon Sterz
2026-07-30 13:31 ` [PATCH proxmox-backup 08/16] docs: document force refreshing long-lived certificates Shannon Sterz
2026-07-30 13:31 ` [PATCH proxmox-backup 09/16] backup-manager cli: `cert update` can create auth and csrf key Shannon Sterz
2026-07-30 13:31 ` [PATCH proxmox-backup 10/16] notifications: use `Severity::Error` for acme renewal failures Shannon Sterz
2026-07-30 13:31 ` [PATCH datacenter-manager 11/16] certs: use proxmox-tls-certificates directly, add days_valid paramter Shannon Sterz
2026-07-30 13:31 ` Shannon Sterz [this message]
2026-07-30 13:31 ` [PATCH datacenter-manager 13/16] cli: expose certificate management endpoints via the cli Shannon Sterz
2026-07-30 13:31 ` [PATCH datacenter-manager 14/16] daily-update: warn about certificates with excessive lifetimes Shannon Sterz
2026-07-30 13:31 ` [PATCH datacenter-manager 15/16] docs: add section on forcing a new self-signed certificate Shannon Sterz
2026-07-30 13:31 ` [PATCH datacenter-manager 16/16] docs/certificates: use correct certificate file name Shannon Sterz
2026-07-30 13:44 ` [PATCH datacenter-manager/proxmox{,-backup} 00/16] TLS Certificate Rotation 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=20260730133158.418015-13-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.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal