ACME External Account Binding support (closes #379 closes ##650)

This commit is contained in:
mdecimus 2024-10-08 16:28:03 +02:00
parent a1ca7fa849
commit 8ff2438f04
6 changed files with 193 additions and 111 deletions

View file

@ -21,8 +21,6 @@
<a href="https://mastodon.social/@stalwartlabs"><img src="https://img.shields.io/mastodon/follow/109929667531941122?style=flat-square&logo=mastodon&color=%236364ff&label=Follow%20on%20Mastodon" alt="Mastodon"></a> <a href="https://mastodon.social/@stalwartlabs"><img src="https://img.shields.io/mastodon/follow/109929667531941122?style=flat-square&logo=mastodon&color=%236364ff&label=Follow%20on%20Mastodon" alt="Mastodon"></a>
&nbsp; &nbsp;
<a href="https://twitter.com/stalwartlabs"><img src="https://img.shields.io/twitter/follow/stalwartlabs?style=flat-square&logo=x&label=Follow%20on%20Twitter" alt="Twitter"></a> <a href="https://twitter.com/stalwartlabs"><img src="https://img.shields.io/twitter/follow/stalwartlabs?style=flat-square&logo=x&label=Follow%20on%20Twitter" alt="Twitter"></a>
&nbsp;
<a href="nostr:npub167hk2ermhky3pmudc3q0d2vnnhcesdgsrcqgywv447ls4xs5u89q5d6395"><img src="https://img.shields.io/nostr-band/followers/npub167hk2ermhky3pmudc3q0d2vnnhcesdgsrcqgywv447ls4xs5u89q5d6395?style=flat-square&logo=chatbot&label=Follow%20on%20Nostr" alt="Nostr"></a>
</p> </p>
<p align="center"> <p align="center">
<a href="https://discord.gg/jtgtCNj66U"><img src="https://img.shields.io/discord/923615863037390889?label=Join%20Discord&logo=discord&style=flat-square" alt="Discord"></a> <a href="https://discord.gg/jtgtCNj66U"><img src="https://img.shields.io/discord/923615863037390889?label=Join%20Discord&logo=discord&style=flat-square" alt="Discord"></a>

View file

@ -12,7 +12,10 @@ use std::{
}; };
use ahash::{AHashMap, AHashSet}; use ahash::{AHashMap, AHashSet};
use base64::{engine::general_purpose::STANDARD, Engine}; use base64::{
engine::general_purpose::{self, STANDARD},
Engine,
};
use dns_update::{providers::rfc2136::DnsAddress, DnsUpdater, TsigAlgorithm}; use dns_update::{providers::rfc2136::DnsAddress, DnsUpdater, TsigAlgorithm};
use rcgen::generate_simple_self_signed; use rcgen::generate_simple_self_signed;
use rustls::{ use rustls::{
@ -31,7 +34,9 @@ use x509_parser::{
}; };
use crate::listener::{ use crate::listener::{
acme::{directory::LETS_ENCRYPT_PRODUCTION_DIRECTORY, AcmeProvider, ChallengeSettings}, acme::{
directory::LETS_ENCRYPT_PRODUCTION_DIRECTORY, AcmeProvider, ChallengeSettings, EabSettings,
},
tls::AcmeProviders, tls::AcmeProviders,
}; };
@ -129,6 +134,34 @@ impl AcmeProviders {
continue 'outer; continue 'outer;
} }
// Obtain EAB settings
let eab = if let (Some(eab_kid), Some(eab_hmac_key)) = (
config
.value(("acme", acme_id, "eab.kid"))
.filter(|s| !s.is_empty()),
config
.value(("acme", acme_id, "eab.hmac-key"))
.filter(|s| !s.is_empty()),
) {
if let Ok(hmac_key) =
general_purpose::URL_SAFE_NO_PAD.decode(eab_hmac_key.trim().as_bytes())
{
EabSettings {
kid: eab_kid.to_string(),
hmac_key,
}
.into()
} else {
config.new_build_error(
format!("acme.{acme_id}.eab.hmac-key"),
"Failed to base64 decode HMAC key",
);
None
}
} else {
None
};
// This ACME manager is the default when SNI is not available // This ACME manager is the default when SNI is not available
let default = config let default = config
.property::<bool>(("acme", acme_id, "default")) .property::<bool>(("acme", acme_id, "default"))
@ -141,6 +174,7 @@ impl AcmeProviders {
domains, domains,
contact, contact,
challenge, challenge,
eab,
renew_before, renew_before,
default, default,
) { ) {

View file

@ -10,14 +10,16 @@ use reqwest::{Method, Response};
use ring::rand::SystemRandom; use ring::rand::SystemRandom;
use ring::signature::{EcdsaKeyPair, EcdsaSigningAlgorithm, ECDSA_P256_SHA256_FIXED_SIGNING}; use ring::signature::{EcdsaKeyPair, EcdsaSigningAlgorithm, ECDSA_P256_SHA256_FIXED_SIGNING};
use serde::Deserialize; use serde::Deserialize;
use serde_json::json;
use store::write::Bincode; use store::write::Bincode;
use store::Serialize; use store::Serialize;
use trc::event::conv::AssertSuccess; use trc::event::conv::AssertSuccess;
use trc::AddContext;
use super::jose::{ use super::jose::{
key_authorization, key_authorization_sha256, key_authorization_sha256_base64, sign, eab_sign, key_authorization, key_authorization_sha256, key_authorization_sha256_base64, sign,
Body,
}; };
use super::AcmeProvider;
pub const LETS_ENCRYPT_STAGING_DIRECTORY: &str = pub const LETS_ENCRYPT_STAGING_DIRECTORY: &str =
"https://acme-staging-v02.api.letsencrypt.org/directory"; "https://acme-staging-v02.api.letsencrypt.org/directory";
@ -32,6 +34,16 @@ pub struct Account {
pub kid: String, pub kid: String,
} }
#[derive(Debug, serde::Serialize)]
pub struct NewAccountPayload<'x> {
#[serde(rename = "termsOfServiceAgreed")]
tos_agreed: bool,
contact: &'x [String],
#[serde(rename = "externalAccountBinding")]
#[serde(skip_serializing_if = "Option::is_none")]
eab: Option<Body>,
}
static ALG: &EcdsaSigningAlgorithm = &ECDSA_P256_SHA256_FIXED_SIGNING; static ALG: &EcdsaSigningAlgorithm = &ECDSA_P256_SHA256_FIXED_SIGNING;
impl Account { impl Account {
@ -42,35 +54,39 @@ impl Account {
.to_vec() .to_vec()
} }
pub async fn create<'a, S, I>(directory: Directory, contact: I) -> trc::Result<Self> pub async fn create(directory: Directory, provider: &AcmeProvider) -> trc::Result<Self> {
where Self::create_with_keypair(directory, provider).await
S: AsRef<str> + 'a,
I: IntoIterator<Item = &'a S>,
{
Self::create_with_keypair(directory, contact, &Self::generate_key_pair()).await
} }
pub async fn create_with_keypair<'a, S, I>( pub async fn create_with_keypair(
directory: Directory, directory: Directory,
contact: I, provider: &AcmeProvider,
key_pair: &[u8], ) -> trc::Result<Self> {
) -> trc::Result<Self> let key_pair = EcdsaKeyPair::from_pkcs8(
where ALG,
S: AsRef<str> + 'a, provider.account_key.load().as_slice(),
I: IntoIterator<Item = &'a S>, &SystemRandom::new(),
{ )
let key_pair = .map_err(|err| {
EcdsaKeyPair::from_pkcs8(ALG, key_pair, &SystemRandom::new()).map_err(|err| {
trc::EventType::Acme(trc::AcmeEvent::Error) trc::EventType::Acme(trc::AcmeEvent::Error)
.reason(err) .reason(err)
.caused_by(trc::location!()) .caused_by(trc::location!())
})?; })?;
let contact: Vec<&'a str> = contact.into_iter().map(AsRef::<str>::as_ref).collect(); let eab = if let Some(eab) = &provider.eab {
let payload = json!({ eab_sign(&key_pair, &eab.kid, &eab.hmac_key, &directory.new_account)
"termsOfServiceAgreed": true, .caused_by(trc::location!())?
"contact": contact, .into()
} else {
None
};
let payload = serde_json::to_string(&NewAccountPayload {
tos_agreed: true,
contact: &provider.contact,
eab,
}) })
.to_string(); .unwrap_or_default();
let body = sign( let body = sign(
&key_pair, &key_pair,
None, None,

View file

@ -3,6 +3,7 @@
use base64::engine::general_purpose::URL_SAFE_NO_PAD; use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use base64::Engine; use base64::Engine;
use ring::digest::{digest, Digest, SHA256}; use ring::digest::{digest, Digest, SHA256};
use ring::hmac;
use ring::rand::SystemRandom; use ring::rand::SystemRandom;
use ring::signature::{EcdsaKeyPair, KeyPair}; use ring::signature::{EcdsaKeyPair, KeyPair};
use serde::Serialize; use serde::Serialize;
@ -18,7 +19,7 @@ pub(crate) fn sign(
None => Some(Jwk::new(key)), None => Some(Jwk::new(key)),
Some(_) => None, Some(_) => None,
}; };
let protected = Protected::base64(jwk, kid, nonce, url)?; let protected = Protected::encode("ES256", jwk, kid, nonce.into(), url)?;
let payload = URL_SAFE_NO_PAD.encode(payload); let payload = URL_SAFE_NO_PAD.encode(payload);
let combined = format!("{}.{}", &protected, &payload); let combined = format!("{}.{}", &protected, &payload);
let signature = key let signature = key
@ -28,15 +29,34 @@ pub(crate) fn sign(
.caused_by(trc::location!()) .caused_by(trc::location!())
.reason(err) .reason(err)
})?; })?;
let signature = URL_SAFE_NO_PAD.encode(signature.as_ref());
let body = Body { serde_json::to_string(&Body {
protected,
payload,
signature: URL_SAFE_NO_PAD.encode(signature.as_ref()),
})
.map_err(|err| trc::EventType::Acme(trc::AcmeEvent::Error).from_json_error(err))
}
pub(crate) fn eab_sign(
key: &EcdsaKeyPair,
kid: &str,
hmac_key: &[u8],
url: &str,
) -> trc::Result<Body> {
let protected = Protected::encode("HS256", None, kid.into(), None, url)?;
let payload = Jwk::new(key).base64()?;
let combined = format!("{}.{}", &protected, &payload);
let key = hmac::Key::new(hmac::HMAC_SHA256, hmac_key);
let tag = hmac::sign(&key, combined.as_bytes());
let signature = URL_SAFE_NO_PAD.encode(tag.as_ref());
Ok(Body {
protected, protected,
payload, payload,
signature, signature,
}; })
serde_json::to_string(&body)
.map_err(|err| trc::EventType::Acme(trc::AcmeEvent::Error).from_json_error(err))
} }
pub(crate) fn key_authorization(key: &EcdsaKeyPair, token: &str) -> trc::Result<String> { pub(crate) fn key_authorization(key: &EcdsaKeyPair, token: &str) -> trc::Result<String> {
@ -58,8 +78,8 @@ pub(crate) fn key_authorization_sha256_base64(
key_authorization_sha256(key, token).map(|s| URL_SAFE_NO_PAD.encode(s.as_ref())) key_authorization_sha256(key, token).map(|s| URL_SAFE_NO_PAD.encode(s.as_ref()))
} }
#[derive(Serialize)] #[derive(Debug, Serialize)]
struct Body { pub(crate) struct Body {
protected: String, protected: String,
payload: String, payload: String,
signature: String, signature: String,
@ -72,27 +92,28 @@ struct Protected<'a> {
jwk: Option<Jwk>, jwk: Option<Jwk>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
kid: Option<&'a str>, kid: Option<&'a str>,
nonce: String, #[serde(skip_serializing_if = "Option::is_none")]
nonce: Option<String>,
url: &'a str, url: &'a str,
} }
impl<'a> Protected<'a> { impl<'a> Protected<'a> {
fn base64( fn encode(
alg: &'static str,
jwk: Option<Jwk>, jwk: Option<Jwk>,
kid: Option<&'a str>, kid: Option<&'a str>,
nonce: String, nonce: Option<String>,
url: &'a str, url: &'a str,
) -> trc::Result<String> { ) -> trc::Result<String> {
let protected = Self { serde_json::to_vec(&Protected {
alg: "ES256", alg,
jwk, jwk,
kid, kid,
nonce, nonce,
url, url,
}; })
let protected = serde_json::to_vec(&protected) .map_err(|err| trc::EventType::Acme(trc::AcmeEvent::Error).from_json_error(err))
.map_err(|err| trc::EventType::Acme(trc::AcmeEvent::Error).from_json_error(err))?; .map(|v| URL_SAFE_NO_PAD.encode(v.as_slice()))
Ok(URL_SAFE_NO_PAD.encode(protected))
} }
} }
@ -119,17 +140,24 @@ impl Jwk {
y: URL_SAFE_NO_PAD.encode(y), y: URL_SAFE_NO_PAD.encode(y),
} }
} }
pub(crate) fn base64(&self) -> trc::Result<String> {
serde_json::to_vec(self)
.map_err(|err| trc::EventType::Acme(trc::AcmeEvent::Error).from_json_error(err))
.map(|v| URL_SAFE_NO_PAD.encode(v.as_slice()))
}
pub(crate) fn thumb_sha256_base64(&self) -> trc::Result<String> { pub(crate) fn thumb_sha256_base64(&self) -> trc::Result<String> {
let jwk_thumb = JwkThumb { Ok(URL_SAFE_NO_PAD.encode(digest(
&SHA256,
&serde_json::to_vec(&JwkThumb {
crv: self.crv, crv: self.crv,
kty: self.kty, kty: self.kty,
x: &self.x, x: &self.x,
y: &self.y, y: &self.y,
}; })
let json = serde_json::to_vec(&jwk_thumb) .map_err(|err| trc::EventType::Acme(trc::AcmeEvent::Error).from_json_error(err))?,
.map_err(|err| trc::EventType::Acme(trc::AcmeEvent::Error).from_json_error(err))?; )))
let hash = digest(&SHA256, &json);
Ok(URL_SAFE_NO_PAD.encode(hash))
} }
} }

View file

@ -26,11 +26,18 @@ pub struct AcmeProvider {
pub domains: Vec<String>, pub domains: Vec<String>,
pub contact: Vec<String>, pub contact: Vec<String>,
pub challenge: ChallengeSettings, pub challenge: ChallengeSettings,
pub eab: Option<EabSettings>,
renew_before: chrono::Duration, renew_before: chrono::Duration,
account_key: ArcSwap<Vec<u8>>, account_key: ArcSwap<Vec<u8>>,
default: bool, default: bool,
} }
#[derive(Clone)]
pub struct EabSettings {
pub kid: String,
pub hmac_key: Vec<u8>,
}
#[derive(Clone)] #[derive(Clone)]
pub enum ChallengeSettings { pub enum ChallengeSettings {
Http01, Http01,
@ -49,12 +56,14 @@ pub struct StaticResolver {
} }
impl AcmeProvider { impl AcmeProvider {
#[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
id: String, id: String,
directory_url: String, directory_url: String,
domains: Vec<String>, domains: Vec<String>,
contact: Vec<String>, contact: Vec<String>,
challenge: ChallengeSettings, challenge: ChallengeSettings,
eab: Option<EabSettings>,
renew_before: Duration, renew_before: Duration,
default: bool, default: bool,
) -> trc::Result<Self> { ) -> trc::Result<Self> {
@ -75,6 +84,7 @@ impl AcmeProvider {
domains, domains,
account_key: Default::default(), account_key: Default::default(),
challenge, challenge,
eab,
default, default,
}) })
} }
@ -142,6 +152,7 @@ impl Clone for AcmeProvider {
challenge: self.challenge.clone(), challenge: self.challenge.clone(),
renew_before: self.renew_before, renew_before: self.renew_before,
account_key: ArcSwap::from_pointee(self.account_key.load().as_ref().clone()), account_key: ArcSwap::from_pointee(self.account_key.load().as_ref().clone()),
eab: self.eab.clone(),
default: self.default, default: self.default,
} }
} }

View file

@ -9,6 +9,7 @@ use rustls::sign::CertifiedKey;
use rustls_pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer}; use rustls_pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use trc::{AcmeEvent, EventType};
use x509_parser::parse_x509_certificate; use x509_parser::parse_x509_certificate;
use crate::listener::acme::directory::Identifier; use crate::listener::acme::directory::Identifier;
@ -36,7 +37,7 @@ impl Server {
let renewal_date = validity[1] - provider.renew_before; let renewal_date = validity[1] - provider.renew_before;
trc::event!( trc::event!(
Acme(trc::AcmeEvent::ProcessCert), Acme(AcmeEvent::ProcessCert),
Id = provider.id.to_string(), Id = provider.id.to_string(),
Hostname = provider.domains.as_slice(), Hostname = provider.domains.as_slice(),
ValidFrom = trc::Value::Timestamp(validity[0].timestamp() as u64), ValidFrom = trc::Value::Timestamp(validity[0].timestamp() as u64),
@ -56,16 +57,18 @@ impl Server {
loop { loop {
match self.order(provider).await { match self.order(provider).await {
Ok(pem) => return self.process_cert(provider, pem, false).await, Ok(pem) => return self.process_cert(provider, pem, false).await,
Err(err) if backoff < 16 => { Err(err)
if !err.matches(EventType::Acme(AcmeEvent::OrderInvalid)) && backoff < 16 =>
{
trc::event!( trc::event!(
Acme(trc::AcmeEvent::RenewBackoff), Acme(AcmeEvent::RenewBackoff),
Id = provider.id.to_string(), Id = provider.id.to_string(),
Hostname = provider.domains.as_slice(), Hostname = provider.domains.as_slice(),
Total = backoff, Total = backoff,
NextRetry = 1 << backoff, NextRetry = 1 << backoff,
CausedBy = err, CausedBy = err,
); );
backoff = (backoff + 1).min(16); backoff += 1;
tokio::time::sleep(Duration::from_secs(1 << backoff)).await; tokio::time::sleep(Duration::from_secs(1 << backoff)).await;
} }
Err(err) => { Err(err) => {
@ -80,18 +83,13 @@ impl Server {
async fn order(&self, provider: &AcmeProvider) -> trc::Result<Vec<u8>> { async fn order(&self, provider: &AcmeProvider) -> trc::Result<Vec<u8>> {
let directory = Directory::discover(&provider.directory_url).await?; let directory = Directory::discover(&provider.directory_url).await?;
let account = Account::create_with_keypair( let account = Account::create_with_keypair(directory, provider).await?;
directory,
&provider.contact,
provider.account_key.load().as_slice(),
)
.await?;
let mut params = CertificateParams::new(provider.domains.clone()); let mut params = CertificateParams::new(provider.domains.clone());
params.distinguished_name = DistinguishedName::new(); params.distinguished_name = DistinguishedName::new();
params.alg = &PKCS_ECDSA_P256_SHA256; params.alg = &PKCS_ECDSA_P256_SHA256;
let cert = rcgen::Certificate::from_params(params).map_err(|err| { let cert = rcgen::Certificate::from_params(params).map_err(|err| {
trc::EventType::Acme(trc::AcmeEvent::Error) EventType::Acme(AcmeEvent::Error)
.caused_by(trc::location!()) .caused_by(trc::location!())
.reason(err) .reason(err)
})?; })?;
@ -106,7 +104,7 @@ impl Server {
.map(|url| self.authorize(provider, &account, url)); .map(|url| self.authorize(provider, &account, url));
try_join_all(auth_futures).await?; try_join_all(auth_futures).await?;
trc::event!( trc::event!(
Acme(trc::AcmeEvent::AuthCompleted), Acme(AcmeEvent::AuthCompleted),
Id = provider.id.to_string(), Id = provider.id.to_string(),
Hostname = provider.domains.as_slice(), Hostname = provider.domains.as_slice(),
); );
@ -115,7 +113,7 @@ impl Server {
OrderStatus::Processing => { OrderStatus::Processing => {
for i in 0u64..10 { for i in 0u64..10 {
trc::event!( trc::event!(
Acme(trc::AcmeEvent::OrderProcessing), Acme(AcmeEvent::OrderProcessing),
Id = provider.id.to_string(), Id = provider.id.to_string(),
Hostname = provider.domains.as_slice(), Hostname = provider.domains.as_slice(),
Total = i, Total = i,
@ -128,20 +126,20 @@ impl Server {
} }
} }
if order.status == OrderStatus::Processing { if order.status == OrderStatus::Processing {
return Err(trc::EventType::Acme(trc::AcmeEvent::Error) return Err(EventType::Acme(AcmeEvent::Error)
.caused_by(trc::location!()) .caused_by(trc::location!())
.details("Order processing timed out")); .details("Order processing timed out"));
} }
} }
OrderStatus::Ready => { OrderStatus::Ready => {
trc::event!( trc::event!(
Acme(trc::AcmeEvent::OrderReady), Acme(AcmeEvent::OrderReady),
Id = provider.id.to_string(), Id = provider.id.to_string(),
Hostname = provider.domains.as_slice(), Hostname = provider.domains.as_slice(),
); );
let csr = cert.serialize_request_der().map_err(|err| { let csr = cert.serialize_request_der().map_err(|err| {
trc::EventType::Acme(trc::AcmeEvent::Error) EventType::Acme(AcmeEvent::Error)
.caused_by(trc::location!()) .caused_by(trc::location!())
.reason(err) .reason(err)
})?; })?;
@ -149,7 +147,7 @@ impl Server {
} }
OrderStatus::Valid { certificate } => { OrderStatus::Valid { certificate } => {
trc::event!( trc::event!(
Acme(trc::AcmeEvent::OrderValid), Acme(AcmeEvent::OrderValid),
Id = provider.id.to_string(), Id = provider.id.to_string(),
Hostname = provider.domains.as_slice(), Hostname = provider.domains.as_slice(),
); );
@ -163,15 +161,7 @@ impl Server {
return Ok(pem.into_bytes()); return Ok(pem.into_bytes());
} }
OrderStatus::Invalid => { OrderStatus::Invalid => {
trc::event!( return Err(EventType::Acme(AcmeEvent::OrderInvalid).into_err());
Acme(trc::AcmeEvent::OrderInvalid),
Id = provider.id.to_string(),
Hostname = provider.domains.as_slice(),
);
return Err(trc::EventType::Acme(trc::AcmeEvent::Error)
.into_err()
.details("Invalid ACME order"));
} }
} }
} }
@ -190,7 +180,7 @@ impl Server {
let challenge_type = provider.challenge.challenge_type(); let challenge_type = provider.challenge.challenge_type();
trc::event!( trc::event!(
Acme(trc::AcmeEvent::AuthStart), Acme(AcmeEvent::AuthStart),
Hostname = domain.to_string(), Hostname = domain.to_string(),
Type = challenge_type.as_str(), Type = challenge_type.as_str(),
Id = provider.id.to_string(), Id = provider.id.to_string(),
@ -201,11 +191,18 @@ impl Server {
.iter() .iter()
.find(|c| c.typ == challenge_type) .find(|c| c.typ == challenge_type)
.ok_or( .ok_or(
trc::EventType::Acme(trc::AcmeEvent::Error) EventType::Acme(AcmeEvent::OrderInvalid)
.into_err() .into_err()
.details("Missing Parameter") .details("Challenge not supported by ACME provider")
.ctx(trc::Key::Id, provider.id.to_string()) .ctx(trc::Key::Id, provider.id.to_string())
.ctx(trc::Key::Type, challenge_type.as_str()), .ctx(trc::Key::Type, challenge_type.as_str())
.ctx(
trc::Key::Contents,
auth.challenges
.iter()
.map(|c| trc::Value::Static(c.typ.as_str()))
.collect::<Vec<_>>(),
),
)?; )?;
match &provider.challenge { match &provider.challenge {
@ -247,7 +244,7 @@ impl Server {
if let Err(err) = updater.delete(&name, &origin).await { if let Err(err) = updater.delete(&name, &origin).await {
// Errors are expected if the record does not exist // Errors are expected if the record does not exist
trc::event!( trc::event!(
Acme(trc::AcmeEvent::DnsRecordDeletionFailed), Acme(AcmeEvent::DnsRecordDeletionFailed),
Hostname = name.to_string(), Hostname = name.to_string(),
Reason = err.to_string(), Reason = err.to_string(),
Details = origin.to_string(), Details = origin.to_string(),
@ -267,9 +264,7 @@ impl Server {
) )
.await .await
{ {
return Err(trc::EventType::Acme( return Err(EventType::Acme(AcmeEvent::DnsRecordCreationFailed)
trc::AcmeEvent::DnsRecordCreationFailed,
)
.ctx(trc::Key::Id, provider.id.to_string()) .ctx(trc::Key::Id, provider.id.to_string())
.ctx(trc::Key::Hostname, name) .ctx(trc::Key::Hostname, name)
.ctx(trc::Key::Details, origin) .ctx(trc::Key::Details, origin)
@ -277,7 +272,7 @@ impl Server {
} }
trc::event!( trc::event!(
Acme(trc::AcmeEvent::DnsRecordCreated), Acme(AcmeEvent::DnsRecordCreated),
Hostname = name.to_string(), Hostname = name.to_string(),
Details = origin.to_string(), Details = origin.to_string(),
Id = provider.id.to_string(), Id = provider.id.to_string(),
@ -295,7 +290,7 @@ impl Server {
break; break;
} else { } else {
trc::event!( trc::event!(
Acme(trc::AcmeEvent::DnsRecordNotPropagated), Acme(AcmeEvent::DnsRecordNotPropagated),
Id = provider.id.to_string(), Id = provider.id.to_string(),
Hostname = name.to_string(), Hostname = name.to_string(),
Details = origin.to_string(), Details = origin.to_string(),
@ -306,7 +301,7 @@ impl Server {
} }
Err(err) => { Err(err) => {
trc::event!( trc::event!(
Acme(trc::AcmeEvent::DnsRecordLookupFailed), Acme(AcmeEvent::DnsRecordLookupFailed),
Id = provider.id.to_string(), Id = provider.id.to_string(),
Hostname = name.to_string(), Hostname = name.to_string(),
Details = origin.to_string(), Details = origin.to_string(),
@ -320,14 +315,14 @@ impl Server {
if did_propagate { if did_propagate {
trc::event!( trc::event!(
Acme(trc::AcmeEvent::DnsRecordPropagated), Acme(AcmeEvent::DnsRecordPropagated),
Id = provider.id.to_string(), Id = provider.id.to_string(),
Hostname = name.to_string(), Hostname = name.to_string(),
Details = origin.to_string(), Details = origin.to_string(),
); );
} else { } else {
trc::event!( trc::event!(
Acme(trc::AcmeEvent::DnsRecordPropagationTimeout), Acme(AcmeEvent::DnsRecordPropagationTimeout),
Id = provider.id.to_string(), Id = provider.id.to_string(),
Hostname = name.to_string(), Hostname = name.to_string(),
Details = origin.to_string(), Details = origin.to_string(),
@ -341,7 +336,7 @@ impl Server {
} }
AuthStatus::Valid => return Ok(()), AuthStatus::Valid => return Ok(()),
_ => { _ => {
return Err(trc::EventType::Acme(trc::AcmeEvent::AuthError) return Err(EventType::Acme(AcmeEvent::AuthError)
.into_err() .into_err()
.ctx(trc::Key::Id, provider.id.to_string()) .ctx(trc::Key::Id, provider.id.to_string())
.ctx(trc::Key::Details, auth.status.as_str())) .ctx(trc::Key::Details, auth.status.as_str()))
@ -354,7 +349,7 @@ impl Server {
match auth.status { match auth.status {
AuthStatus::Pending => { AuthStatus::Pending => {
trc::event!( trc::event!(
Acme(trc::AcmeEvent::AuthPending), Acme(AcmeEvent::AuthPending),
Hostname = domain.to_string(), Hostname = domain.to_string(),
Id = provider.id.to_string(), Id = provider.id.to_string(),
Total = i, Total = i,
@ -364,7 +359,7 @@ impl Server {
} }
AuthStatus::Valid => { AuthStatus::Valid => {
trc::event!( trc::event!(
Acme(trc::AcmeEvent::AuthValid), Acme(AcmeEvent::AuthValid),
Hostname = domain.to_string(), Hostname = domain.to_string(),
Id = provider.id.to_string(), Id = provider.id.to_string(),
); );
@ -372,14 +367,14 @@ impl Server {
return Ok(()); return Ok(());
} }
_ => { _ => {
return Err(trc::EventType::Acme(trc::AcmeEvent::AuthError) return Err(EventType::Acme(AcmeEvent::AuthError)
.into_err() .into_err()
.ctx(trc::Key::Id, provider.id.to_string()) .ctx(trc::Key::Id, provider.id.to_string())
.ctx(trc::Key::Details, auth.status.as_str())) .ctx(trc::Key::Details, auth.status.as_str()))
} }
} }
} }
Err(trc::EventType::Acme(trc::AcmeEvent::AuthTooManyAttempts) Err(EventType::Acme(AcmeEvent::AuthTooManyAttempts)
.into_err() .into_err()
.ctx(trc::Key::Id, provider.id.to_string()) .ctx(trc::Key::Id, provider.id.to_string())
.ctx(trc::Key::Hostname, domain)) .ctx(trc::Key::Hostname, domain))
@ -388,12 +383,12 @@ impl Server {
fn parse_cert(pem: &[u8]) -> trc::Result<(CertifiedKey, [DateTime<Utc>; 2])> { fn parse_cert(pem: &[u8]) -> trc::Result<(CertifiedKey, [DateTime<Utc>; 2])> {
let mut pems = pem::parse_many(pem).map_err(|err| { let mut pems = pem::parse_many(pem).map_err(|err| {
trc::EventType::Acme(trc::AcmeEvent::Error) EventType::Acme(AcmeEvent::Error)
.reason(err) .reason(err)
.caused_by(trc::location!()) .caused_by(trc::location!())
})?; })?;
if pems.len() < 2 { if pems.len() < 2 {
return Err(trc::EventType::Acme(trc::AcmeEvent::Error) return Err(EventType::Acme(AcmeEvent::Error)
.caused_by(trc::location!()) .caused_by(trc::location!())
.ctx(trc::Key::Size, pems.len()) .ctx(trc::Key::Size, pems.len())
.details("Too few PEMs")); .details("Too few PEMs"));
@ -403,7 +398,7 @@ fn parse_cert(pem: &[u8]) -> trc::Result<(CertifiedKey, [DateTime<Utc>; 2])> {
))) { ))) {
Ok(pk) => pk, Ok(pk) => pk,
Err(err) => { Err(err) => {
return Err(trc::EventType::Acme(trc::AcmeEvent::Error) return Err(EventType::Acme(AcmeEvent::Error)
.reason(err) .reason(err)
.caused_by(trc::location!())) .caused_by(trc::location!()))
} }
@ -422,7 +417,7 @@ fn parse_cert(pem: &[u8]) -> trc::Result<(CertifiedKey, [DateTime<Utc>; 2])> {
}) })
} }
Err(err) => { Err(err) => {
return Err(trc::EventType::Acme(trc::AcmeEvent::Error) return Err(EventType::Acme(AcmeEvent::Error)
.reason(err) .reason(err)
.caused_by(trc::location!())) .caused_by(trc::location!()))
} }