ACME freshness check improvements

This commit is contained in:
Maurus Decimus
2026-06-16 11:17:13 +02:00
parent 3a871742ef
commit 8f627fbd69
5 changed files with 48 additions and 36 deletions

View File

@@ -150,9 +150,22 @@ jobs:
- name: Checkout
uses: actions/checkout@v6.0.2
- name: Free disk space (heavy ARM targets)
if: contains(matrix.target, 'arm')
run: |
df -h /mnt /
sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/.ghcup /usr/local/share/powershell /usr/share/swift /opt/hostedtoolcache/CodeQL
sudo docker image prune --all --force || true
df -h /mnt /
- name: Add swap (heavy ARM targets)
if: contains(matrix.target, 'arm')
run: |
mnt_avail=$(df --output=avail -k /mnt | tail -1)
if [ "$mnt_avail" -lt 18874368 ]; then
echo "Insufficient space on /mnt (${mnt_avail}K available), aborting swap setup"
exit 1
fi
sudo fallocate -l 16G /mnt/swapfile
sudo chmod 600 /mnt/swapfile
sudo mkswap /mnt/swapfile

View File

@@ -238,8 +238,8 @@ impl Server {
.addresses
.iter()
.any(|address| {
address.local_part.as_ref() == local_part
&& address.domain_id == domain_id
address.domain_id == domain_id
&& address.local_part.as_ref() == local_part
})
{
EmailCache::Account(item_id)
@@ -250,8 +250,8 @@ impl Server {
ObjectType::MailingList => {
if let Some(list) = self.try_list(item_id).await?
&& !list.addresses.iter().any(|address| {
address.local_part.as_ref() == local_part
&& address.domain_id == domain_id
address.domain_id == domain_id
&& address.local_part.as_ref() == local_part
})
{
EmailCache::DisabledListAddress(item_id)

View File

@@ -32,6 +32,7 @@ pub enum AcmeError {
Json(serde_json::Error),
Crypto(String),
Invalid(String),
NotDue(String),
Dns(String),
AuthInvalid(String),
OrderInvalid(String),
@@ -257,6 +258,7 @@ impl Display for AcmeError {
AcmeError::Dns(err) => write!(f, "DNS error: {}", err),
AcmeError::Crypto(err) => write!(f, "Cryptographic error: {}", err),
AcmeError::Invalid(err) => write!(f, "Invalid request: {}", err),
AcmeError::NotDue(err) => write!(f, "{}", err),
AcmeError::AuthInvalid(status) => write!(f, "Authentication failed: {:?}", status),
AcmeError::OrderTimeout { .. } => write!(f, "Order processing timed out"),
AcmeError::OrderInvalid(reason) => write!(f, "Order is invalid: {}", reason),

View File

@@ -65,10 +65,11 @@ impl Server {
);
if let Some(renew_at) = self.acme_certificate_renewal_due(&domains, renew_before, now()) {
return Ok(vec![Task::AcmeRenewal(TaskDomainManagement {
domain_id,
status: TaskStatus::at(renew_at as i64),
})]);
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)
)));
}
let dns_parameters = match &domain.dns_management {
@@ -168,29 +169,15 @@ impl Server {
self.cluster_broadcast(BroadcastEvent::RegistryChange(change))
.await;
// Schedule next renewal
let mut tasks = Vec::new();
let renew_in = match renew_before {
AcmeRenewBefore::R12 => {
// 1/2 of the remaining time until expiration
expires_in / 2
}
AcmeRenewBefore::R23 => {
// 2/3 of the remaining time until expiration
expires_in * 2 / 3
}
AcmeRenewBefore::R34 => {
// 3/4 of the remaining time until expiration
expires_in * 3 / 4
}
AcmeRenewBefore::R45 => {
// 4/5 of the remaining time until expiration
expires_in * 4 / 5
}
};
let renew_at = Self::acme_renewal_due_at(
parsed_cert.valid_not_before.timestamp(),
parsed_cert.valid_not_after.timestamp(),
renew_before,
);
tasks.push(Task::AcmeRenewal(TaskDomainManagement {
domain_id,
status: TaskStatus::at((now + renew_in) as i64),
status: TaskStatus::at(renew_at),
}));
// Update TLSA records
@@ -245,14 +232,8 @@ impl Server {
return None;
}
let not_valid_before = parsed.valid_not_before.timestamp();
let total = not_valid_after.saturating_sub(not_valid_before);
let (numerator, denominator) = match renew_before {
AcmeRenewBefore::R12 => (1, 2),
AcmeRenewBefore::R23 => (2, 3),
AcmeRenewBefore::R34 => (3, 4),
AcmeRenewBefore::R45 => (4, 5),
};
let renew_at = not_valid_before + total * numerator / denominator;
let renew_at =
Self::acme_renewal_due_at(not_valid_before, not_valid_after, renew_before);
return if now < renew_at {
Some(renew_at as u64)
} else {
@@ -262,4 +243,19 @@ impl Server {
None
}
fn acme_renewal_due_at(
not_valid_before: i64,
not_valid_after: i64,
renew_before: AcmeRenewBefore,
) -> i64 {
let total = not_valid_after.saturating_sub(not_valid_before);
let (numerator, denominator) = match renew_before {
AcmeRenewBefore::R12 => (1, 2),
AcmeRenewBefore::R23 => (2, 3),
AcmeRenewBefore::R34 => (3, 4),
AcmeRenewBefore::R45 => (4, 5),
};
not_valid_before + total * numerator / denominator
}
}

View File

@@ -48,6 +48,7 @@ async fn acme_management(server: &Server, task: &TaskDomainManagement) -> trc::R
Err(err) => match err {
AcmeError::Crypto(_)
| AcmeError::Invalid(_)
| AcmeError::NotDue(_)
| AcmeError::ChallengeNotSupported { .. }
| AcmeError::OrderInvalid(_)
| AcmeError::Json(_)