From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id 563751FF13C for ; Thu, 25 Jun 2026 16:14:19 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 4FCD01370B; Thu, 25 Jun 2026 16:14:15 +0200 (CEST) From: Manuel Federanko To: pbs-devel@lists.proxmox.com, pdm-devel@lists.proxmox.com Subject: [PATCH proxmox 3/7] acme: allow specifying the certificate that is replaced by an order Date: Thu, 25 Jun 2026 16:13:33 +0200 Message-ID: <20260625141337.181684-4-m.federanko@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260625141337.181684-1-m.federanko@proxmox.com> References: <20260625141337.181684-1-m.federanko@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 1 AWL -1.812 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy HEADER_FROM_DIFFERENT_DOMAINS 0.249 From and EnvelopeFrom 2nd level mail domains are different KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment KAM_LAZY_DOMAIN_SECURITY 1 Sending domain does not have any anti-forgery methods RCVD_IN_SBL_CSS 3.335 Received via a relay in Spamhaus SBL-CSS RDNS_NONE 0.793 Delivered to internal network by a host with no rDNS SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_NONE 0.001 SPF: sender does not publish an SPF Record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [order.rs] Message-ID-Hash: CT6LOV5ZVIAZLWZXRSJ4V3S62MYQNSIN X-Message-ID-Hash: CT6LOV5ZVIAZLWZXRSJ4V3S62MYQNSIN X-MailFrom: mfederanko@dev.localdomain 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: There isn't a foolproof way of determining if the id should be sent along yet. If the request fails try again without the ari id. Signed-off-by: Manuel Federanko --- proxmox-acme-api/src/certificate_helpers.rs | 27 ++++++++++++++++++--- proxmox-acme/src/async_client.rs | 18 +++++++++++--- proxmox-acme/src/order.rs | 13 ++++++++++ 3 files changed, 51 insertions(+), 7 deletions(-) diff --git a/proxmox-acme-api/src/certificate_helpers.rs b/proxmox-acme-api/src/certificate_helpers.rs index 7dc06c2d..5d35f86a 100644 --- a/proxmox-acme-api/src/certificate_helpers.rs +++ b/proxmox-acme-api/src/certificate_helpers.rs @@ -65,6 +65,7 @@ pub async fn order_certificate( worker: Arc, acme_config: &AcmeConfig, domains: &[AcmeDomain], + replaces_certificate_id: Option<&str>, ) -> Result, Error> { use proxmox_acme::authorization::Status; use proxmox_acme::order::Identifier; @@ -88,10 +89,30 @@ pub async fn order_certificate( let (plugins, _) = super::plugin_config::plugin_config()?; info!("Placing ACME order"); - let order = acme - .new_order(domains.iter().map(|d| d.domain.to_ascii_lowercase())) - .await?; + .new_order( + domains.iter().map(|d| d.domain.to_ascii_lowercase()), + replaces_certificate_id, + ) + .await; + // there isn't really a nice way to know if a specific certificate + // is issued by the acme server, retry without specifying the id + // on failures + let order = match order { + Ok(o) => o, + Err(e) => if replaces_certificate_id.is_some() { + info!("Failed to place order, retrying without ARI id: {}", e); + acme + .new_order( + domains.iter().map(|d| d.domain.to_ascii_lowercase()), + None, + ) + .await? + } else { + Err(e)? + }, + + }; info!("Order URL: {}", order.location); diff --git a/proxmox-acme/src/async_client.rs b/proxmox-acme/src/async_client.rs index c133a54e..7738346e 100644 --- a/proxmox-acme/src/async_client.rs +++ b/proxmox-acme/src/async_client.rs @@ -172,15 +172,25 @@ impl AcmeClient { /// /// Please remember to persist the order somewhere (ideally along with the account data) in /// order to finish & query it later on. - pub async fn new_order(&mut self, domains: I) -> Result + pub async fn new_order( + &mut self, + domains: I, + replaces_certificate_id: Option<&str>, + ) -> Result where I: IntoIterator, { let account = Self::need_account(&self.account)?; - let order = domains - .into_iter() - .fold(OrderData::new(), |order, domain| order.domain(domain)); + let order = { + let mut order = domains + .into_iter() + .fold(OrderData::new(), |order, domain| order.domain(domain)); + if let Some(replaces_certificate_id) = replaces_certificate_id { + order = order.replaces(replaces_certificate_id); + } + order + }; let mut retry = retry(); loop { diff --git a/proxmox-acme/src/order.rs b/proxmox-acme/src/order.rs index d75fbde1..36ee5fe1 100644 --- a/proxmox-acme/src/order.rs +++ b/proxmox-acme/src/order.rs @@ -81,6 +81,10 @@ pub struct OrderData { /// List of identifiers to order for the certificate. pub identifiers: Vec, + /// Reference to the certificate being replaced + #[serde(skip_serializing_if = "Option::is_none")] + pub replaces: Option, + /// An RFC3339 formatted time string. It is up to the user to choose a dev dependency for this /// shit. #[serde(skip_serializing_if = "Option::is_none")] @@ -120,6 +124,15 @@ impl OrderData { self.identifiers.push(Identifier::Dns(domain)); self } + + /// Builder-style method to specify which certificate this order replaces. + pub fn replaces(mut self, ari_id: K) -> Self + where + K: ToString, + { + self.replaces = Some(ari_id.to_string()); + self + } } /// Represents an order for a new certificate. This combines the order's own location (URL) with -- 2.47.3