ACME: a renewal that isn't due yet is rescheduled, not failed for good #93

Merged
jcoffey-dev merged 1 commits from fix/acme-not-due-reschedule into main 2026-09-28 21:52:18 +00:00
2 changed files with 46 additions and 5 deletions
+19 -5
View File
@@ -1,7 +1,10 @@
/* /*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]> * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
* SPDX-FileCopyrightText: 2026 Coffey Labs
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*
* Modified by Coffey Labs in 2026 for INBUXA.
*/ */
use crate::{ use crate::{
@@ -72,11 +75,22 @@ impl Server {
.acme_certificate_renewal_due(&domains, renew_before, now()) .acme_certificate_renewal_due(&domains, renew_before, now())
.await? .await?
{ {
return Err(AcmeError::NotDue(format!( // INBUXA: a certificate already covering these names (one stored by
"Certificate for domain {} is still valid; renewal is not due until {}", // hand before the domain was switched to automatic, say) isn't a
domain.name, // failure: schedule the renewal for when it falls due. Returning
UTCDateTime::from_timestamp(renew_at as i64) // 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 { let dns_parameters = match &domain.dns_management {
+27
View File
@@ -1,7 +1,10 @@
/* /*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]> * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
* SPDX-FileCopyrightText: 2026 Coffey Labs
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*
* Modified by Coffey Labs in 2026 for INBUXA.
*/ */
use crate::utils::server::TestServer; use crate::utils::server::TestServer;
@@ -286,6 +289,30 @@ pub async fn test(test: &TestServer) {
not_valid_before + length / 2, not_valid_before + length / 2,
task.due_timestamp() as i64 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::Certificate).await;
account.registry_destroy_all(ObjectType::Task).await; account.registry_destroy_all(ObjectType::Task).await;