Skip to content

Commit 70fdf8d

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 eda0f80 commit 70fdf8d

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(),
@@ -300,7 +283,47 @@ async fn ngx_http_acme_update_certificates_for_issuer(
300283
}
301284

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

306329
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)