Skip to content

Commit 13454a2

Browse files
committed
ACME account login refactoring:
* Implement backoff for login errors * Log new account URL at INFO * Write account URL to state directory
1 parent 179a3dd commit 13454a2

File tree

4 files changed

+83
-20
lines changed

4 files changed

+83
-20
lines changed

src/acme.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ const DEFAULT_RETRY_INTERVAL: Duration = Duration::from_secs(1);
3737
const MAX_RETRY_INTERVAL: Duration = Duration::from_secs(8);
3838
static REPLAY_NONCE: http::HeaderName = http::HeaderName::from_static("replay-nonce");
3939

40+
pub enum NewAccountOutput<'a> {
41+
Created(&'a str),
42+
Found(&'a str),
43+
}
44+
4045
pub struct NewCertificateOutput {
4146
pub chain: Bytes,
4247
pub pkey: PKey<Private>,
@@ -246,7 +251,7 @@ where
246251
Ok(res)
247252
}
248253

249-
pub async fn new_account(&mut self) -> Result<(), NewAccountError> {
254+
pub async fn new_account(&mut self) -> Result<NewAccountOutput<'_>, NewAccountError> {
250255
self.directory = self
251256
.get_directory()
252257
.await
@@ -297,7 +302,11 @@ where
297302

298303
self.account = Some(key_id.to_string());
299304

300-
Ok(())
305+
let key_id = self.account.as_ref().unwrap();
306+
match res.status() {
307+
http::StatusCode::CREATED => Ok(NewAccountOutput::Created(key_id)),
308+
_ => Ok(NewAccountOutput::Found(key_id)),
309+
}
301310
}
302311

303312
pub fn is_ready(&self) -> bool {

src/conf/issuer.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,15 @@ impl Issuer {
118118
.is_some_and(|x| x.read().state != IssuerState::Invalid)
119119
}
120120

121+
/// Marks the last issuer login attempt as failed.
122+
pub fn set_error(&self, err: &dyn StdError) -> Time {
123+
if let Some(data) = self.data.as_ref() {
124+
data.write().set_error(err)
125+
} else {
126+
Time::MAX
127+
}
128+
}
129+
121130
/// Marks the issuer as misconfigured or otherwise unusable.
122131
pub fn set_invalid(&self, err: &dyn StdError) {
123132
if let Some(data) = self.data.as_ref() {

src/lib.rs

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -212,23 +212,6 @@ async fn ngx_http_acme_update_certificates(amcf: &AcmeMainConfig) -> Time {
212212
let issuer_next = match ngx_http_acme_update_certificates_for_issuer(amcf, issuer).await {
213213
Ok(x) => x,
214214
Err(err) => {
215-
// Check if the server rejected this ACME account configuration.
216-
if err
217-
.downcast_ref::<acme::error::NewAccountError>()
218-
.is_some_and(|err| err.is_invalid())
219-
{
220-
ngx_log_error!(
221-
NGX_LOG_ERR,
222-
log.as_ptr(),
223-
"acme issuer \"{}\" is not valid: {}",
224-
issuer.name,
225-
err
226-
);
227-
228-
issuer.set_invalid(err.as_ref());
229-
continue;
230-
}
231-
232215
ngx_log_error!(
233216
NGX_LOG_INFO,
234217
log.as_ptr(),
@@ -301,7 +284,47 @@ async fn ngx_http_acme_update_certificates_for_issuer(
301284
}
302285

303286
if !client.is_ready() {
304-
client.new_account().await?;
287+
match client.new_account().await {
288+
Ok(acme::NewAccountOutput::Created(x)) => {
289+
ngx_log_error!(
290+
NGX_LOG_INFO,
291+
log.as_ptr(),
292+
"acme account \"{}\" created for issuer \"{}\"",
293+
x,
294+
issuer.name
295+
);
296+
let _ = issuer.write_state_file("account.url", x.as_bytes());
297+
}
298+
Ok(acme::NewAccountOutput::Found(x)) => {
299+
ngx_log_debug!(
300+
log.as_ptr(),
301+
"acme account \"{}\" found for issuer \"{}\"",
302+
x,
303+
issuer.name
304+
);
305+
}
306+
Err(err) if err.is_invalid() => {
307+
ngx_log_error!(
308+
NGX_LOG_ERR,
309+
log.as_ptr(),
310+
"acme account validation failed for issuer \"{}\": {}",
311+
issuer.name,
312+
err
313+
);
314+
issuer.set_invalid(&err);
315+
return Ok(Time::MAX);
316+
}
317+
Err(err) => {
318+
ngx_log_error!(
319+
NGX_LOG_WARN,
320+
log.as_ptr(),
321+
"acme account retrieval failed for issuer \"{}\": {}",
322+
issuer.name,
323+
err
324+
);
325+
return Ok(issuer.set_error(&err));
326+
}
327+
}
305328
}
306329

307330
let alloc = crate::util::OwnedPool::new(nginx_sys::NGX_DEFAULT_POOL_SIZE as _, log)

src/state/issuer.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
use core::error::Error as StdError;
77
use core::ptr;
8+
use core::time::Duration;
89

910
use ngx::allocator::{AllocError, TryCloneIn};
1011
use ngx::collections::Queue;
@@ -13,10 +14,12 @@ use ngx::sync::RwLock;
1314

1415
use super::certificate::{CertificateContext, CertificateContextInner, SharedCertificateContext};
1516
use crate::conf::issuer::Issuer;
17+
use crate::time::{jitter, Time};
1618

1719
#[derive(Debug, Eq, PartialEq)]
1820
pub enum IssuerState {
1921
Idle,
22+
Error { fails: usize },
2023
Invalid,
2124
}
2225

@@ -49,6 +52,25 @@ impl IssuerContext {
4952
})
5053
}
5154

55+
pub fn set_error(&mut self, _err: &dyn StdError) -> Time {
56+
let fails = match self.state {
57+
IssuerState::Error { fails } => fails + 1,
58+
IssuerState::Invalid => return Time::MAX,
59+
_ => 1,
60+
};
61+
62+
self.state = IssuerState::Error { fails };
63+
64+
let interval = Duration::from_secs(match fails {
65+
1 => 60,
66+
2 => 600,
67+
3 => 6000,
68+
_ => 24 * 60 * 60,
69+
});
70+
71+
Time::now() + jitter(interval, 2)
72+
}
73+
5274
pub fn set_invalid(&mut self, _err: &dyn StdError) {
5375
self.state = IssuerState::Invalid;
5476
}

0 commit comments

Comments
 (0)