ACME: a renewal that isn't due yet is rescheduled, not failed for good
When a valid certificate already covered a domain's names (one stored by hand before the domain was switched to automatic, for instance), the renewal task ended with NotDue, which the task manager treats as a permanent failure. Nothing rescheduled it, so the certificate expired unrenewed. The renewal now returns a new AcmeRenewal task due when the certificate falls due, the same way a successful renewal does, and logs it as a backoff. The ACME integration suite checks that renewing again right after issuance hands back one AcmeRenewal for that domain, due at the certificate's renewal point.
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user