diff --git a/crates/common/src/network/acme/renew.rs b/crates/common/src/network/acme/renew.rs index a829456..45ca5c4 100644 --- a/crates/common/src/network/acme/renew.rs +++ b/crates/common/src/network/acme/renew.rs @@ -1,7 +1,10 @@ /* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * SPDX-FileCopyrightText: 2026 Coffey Labs * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + * + * Modified by Coffey Labs in 2026 for INBUXA. */ use crate::{ @@ -72,11 +75,22 @@ impl Server { .acme_certificate_renewal_due(&domains, renew_before, now()) .await? { - return Err(AcmeError::NotDue(format!( - "Certificate for domain {} is still valid; renewal is not due until {}", - domain.name, - UTCDateTime::from_timestamp(renew_at as i64) - ))); + // INBUXA: a certificate already covering these names (one stored by + // hand before the domain was switched to automatic, say) isn't a + // failure: schedule the renewal for when it falls due. Returning + // NotDue here ended the task for good, and nothing renewed the + // certificate before it expired. + trc::event!( + Acme(trc::AcmeEvent::RenewBackoff), + Domain = domain.name.clone(), + Hostname = domains.as_slice(), + Details = "A valid certificate already covers these names", + NextRetry = trc::Value::Timestamp(renew_at), + ); + return Ok(vec![Task::AcmeRenewal(TaskDomainManagement { + domain_id, + status: TaskStatus::at(renew_at as i64), + })]); } let dns_parameters = match &domain.dns_management { diff --git a/tests/src/automation/acme.rs b/tests/src/automation/acme.rs index 80fa4ca..b1189b9 100644 --- a/tests/src/automation/acme.rs +++ b/tests/src/automation/acme.rs @@ -1,7 +1,10 @@ /* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * SPDX-FileCopyrightText: 2026 Coffey Labs * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + * + * Modified by Coffey Labs in 2026 for INBUXA. */ use crate::utils::server::TestServer; @@ -286,6 +289,30 @@ pub async fn test(test: &TestServer) { not_valid_before + length / 2, task.due_timestamp() as i64 ); + + // inbuxa: renewing while a valid certificate already covers the names + // (say, one stored by hand before the domain went automatic) schedules + // the renewal for when it falls due. It used to end the task for good. + let rescheduled = test + .server + .acme_renew(tls_domain_id) + .await + .ok() + .expect("a renewal that isn't due yet to be rescheduled, not to fail"); + assert!( + matches!( + rescheduled.as_slice(), + [Task::AcmeRenewal(TaskDomainManagement { domain_id, .. })] if *domain_id == tls_domain_id + ), + "Expected one rescheduled ACME renewal, found: {:?}", + rescheduled + ); + assert_eq!( + rescheduled[0].due_timestamp() as i64, + not_valid_before + length / 2, + "The rescheduled renewal should fall due when the certificate does" + ); + account.registry_destroy_all(ObjectType::Certificate).await; account.registry_destroy_all(ObjectType::Task).await;