diff --git a/acmed/config/default_hooks.toml b/acmed/config/default_hooks.toml index f6d4353..804e50d 100644 --- a/acmed/config/default_hooks.toml +++ b/acmed/config/default_hooks.toml @@ -136,6 +136,7 @@ args = [ "-C", "{{file_directory}}", "add", "{{file_name}}" ] +allow_failure = true [[hook]] name = "git-commit" @@ -149,6 +150,7 @@ args = [ "-m", "{{file_name}}", "--only", "{{file_name}}" ] +allow_failure = true [[group]] name = "git" diff --git a/acmed/src/account.rs b/acmed/src/account.rs index d0995b7..cab6ca2 100644 --- a/acmed/src/account.rs +++ b/acmed/src/account.rs @@ -1,4 +1,6 @@ -use crate::acme_proto::account::{register_account, update_account_contacts, update_account_key}; +use crate::acme_proto::account::{ + check_account_exists, register_account, update_account_contacts, update_account_key, +}; use crate::endpoint::Endpoint; use crate::logs::HasLogger; use crate::storage::FileManager; @@ -206,6 +208,9 @@ impl Account { if key_changed { update_account_key(endpoint, root_certs, self)?; } + if !contacts_changed && !key_changed { + check_account_exists(endpoint, root_certs, self)?; + } } else { register_account(endpoint, root_certs, self)?; } diff --git a/acmed/src/acme_proto.rs b/acmed/src/acme_proto.rs index b258a9d..b4d2c23 100644 --- a/acmed/src/acme_proto.rs +++ b/acmed/src/acme_proto.rs @@ -4,6 +4,7 @@ use crate::acme_proto::structs::{ }; use crate::certificate::Certificate; use crate::endpoint::Endpoint; +use crate::http::HttpError; use crate::identifier::IdentifierType; use crate::jws::encode_kid; use crate::logs::HasLogger; @@ -73,6 +74,8 @@ macro_rules! set_data_builder { } }; } + +#[macro_export] macro_rules! set_empty_data_builder { ($account: ident, $endpoint_name: ident) => { set_data_builder!($account, $endpoint_name, b"") @@ -89,7 +92,7 @@ pub fn request_certificate( let endpoint_name = endpoint.name.clone(); // Refresh the directory - http::refresh_directory(endpoint, root_certs)?; + http::refresh_directory(endpoint, root_certs).map_err(HttpError::in_err)?; // Synchronize the account account.synchronize(endpoint, root_certs)?; @@ -98,7 +101,8 @@ pub fn request_certificate( let new_order = NewOrder::new(&cert.identifiers); let new_order = serde_json::to_string(&new_order)?; let data_builder = set_data_builder!(account, endpoint_name, new_order.as_bytes()); - let (order, order_url) = http::new_order(endpoint, root_certs, &data_builder)?; + let (order, order_url) = + http::new_order(endpoint, root_certs, &data_builder).map_err(HttpError::in_err)?; if let Some(e) = order.get_error() { cert.warn(&e.prefix("Error").message); } @@ -107,7 +111,8 @@ pub fn request_certificate( for auth_url in order.authorizations.iter() { // Fetch the authorization let data_builder = set_empty_data_builder!(account, endpoint_name); - let auth = http::get_authorization(endpoint, root_certs, &data_builder, &auth_url)?; + let auth = http::get_authorization(endpoint, root_certs, &data_builder, &auth_url) + .map_err(HttpError::in_err)?; if let Some(e) = auth.get_error() { cert.warn(&e.prefix("Error").message); } @@ -139,15 +144,17 @@ pub fn request_certificate( // Tell the server the challenge has been completed let chall_url = challenge.get_url(); let data_builder = set_data_builder!(account, endpoint_name, b"{}"); - let _ = http::post_no_response(endpoint, root_certs, &data_builder, &chall_url)?; + let _ = + http::post_jose_no_response(endpoint, root_certs, &data_builder, &chall_url) + .map_err(HttpError::in_err)?; } } // Pool the authorization in order to see whether or not it is valid let data_builder = set_empty_data_builder!(account, endpoint_name); let break_fn = |a: &Authorization| a.status == AuthorizationStatus::Valid; - let _ = - http::pool_authorization(endpoint, root_certs, &data_builder, &break_fn, &auth_url)?; + let _ = http::pool_authorization(endpoint, root_certs, &data_builder, &break_fn, &auth_url) + .map_err(HttpError::in_err)?; for (data, hook_type) in hook_datas.iter() { cert.call_challenge_hooks_clean(&data, (*hook_type).to_owned())?; } @@ -158,7 +165,8 @@ pub fn request_certificate( // Pool the order in order to see whether or not it is ready let data_builder = set_empty_data_builder!(account, endpoint_name); let break_fn = |o: &Order| o.status == OrderStatus::Ready; - let order = http::pool_order(endpoint, root_certs, &data_builder, &break_fn, &order_url)?; + let order = http::pool_order(endpoint, root_certs, &data_builder, &break_fn, &order_url) + .map_err(HttpError::in_err)?; // Finalize the order by sending the CSR let key_pair = certificate::get_key_pair(cert)?; @@ -186,7 +194,8 @@ pub fn request_certificate( }); let csr = csr.to_string(); let data_builder = set_data_builder!(account, endpoint_name, csr.as_bytes()); - let order = http::finalize_order(endpoint, root_certs, &data_builder, &order.finalize)?; + let order = http::finalize_order(endpoint, root_certs, &data_builder, &order.finalize) + .map_err(HttpError::in_err)?; if let Some(e) = order.get_error() { cert.warn(&e.prefix("Error").message); } @@ -194,14 +203,16 @@ pub fn request_certificate( // Pool the order in order to see whether or not it is valid let data_builder = set_empty_data_builder!(account, endpoint_name); let break_fn = |o: &Order| o.status == OrderStatus::Valid; - let order = http::pool_order(endpoint, root_certs, &data_builder, &break_fn, &order_url)?; + let order = http::pool_order(endpoint, root_certs, &data_builder, &break_fn, &order_url) + .map_err(HttpError::in_err)?; // Download the certificate let crt_url = order .certificate .ok_or_else(|| Error::from("No certificate available for download."))?; let data_builder = set_empty_data_builder!(account, endpoint_name); - let crt = http::get_certificate(endpoint, root_certs, &data_builder, &crt_url)?; + let crt = http::get_certificate(endpoint, root_certs, &data_builder, &crt_url) + .map_err(HttpError::in_err)?; storage::write_certificate(&cert.file_manager, &crt.as_bytes())?; cert.info(&format!( diff --git a/acmed/src/acme_proto/account.rs b/acmed/src/acme_proto/account.rs index 6a4a785..5521dc9 100644 --- a/acmed/src/acme_proto/account.rs +++ b/acmed/src/acme_proto/account.rs @@ -1,12 +1,33 @@ use crate::account::Account as BaseAccount; use crate::acme_proto::http; -use crate::acme_proto::structs::{Account, AccountKeyRollover, AccountUpdate}; +use crate::acme_proto::structs::{Account, AccountKeyRollover, AccountUpdate, AcmeError}; use crate::endpoint::Endpoint; +use crate::http::HttpError; use crate::jws::{encode_jwk, encode_jwk_no_nonce, encode_kid}; use crate::logs::HasLogger; -use crate::set_data_builder; +use crate::{set_data_builder, set_empty_data_builder}; use acme_common::error::Error; +macro_rules! create_account_if_does_not_exist { + ($e: expr, $endpoint: ident, $root_certs: ident, $account: ident) => { + match $e { + Ok(r) => Ok(r), + Err(he) => match he { + HttpError::ApiError(ref e) => match e.get_acme_type() { + AcmeError::AccountDoesNotExist => { + let msg = + format!("account has been dropped by endpoint {}", $endpoint.name); + $account.debug(&msg); + return register_account($endpoint, $root_certs, $account); + } + _ => Err(HttpError::in_err(he.to_owned())), + }, + HttpError::GenericError(e) => Err(e), + }, + } + }; +} + pub fn register_account( endpoint: &mut Endpoint, root_certs: &[String], @@ -23,7 +44,8 @@ pub fn register_account( let signature_algorithm = &account.current_key.signature_algorithm; let data_builder = |n: &str, url: &str| encode_jwk(kp_ref, signature_algorithm, acc_ref.as_bytes(), url, n); - let (acc_rep, account_url) = http::new_account(endpoint, root_certs, &data_builder)?; + let (acc_rep, account_url) = + http::new_account(endpoint, root_certs, &data_builder).map_err(HttpError::in_err)?; account.set_account_url(&endpoint.name, &account_url)?; let msg = format!( "endpoint {}: account {}: the server has not provided an order URL upon account creation", @@ -53,7 +75,12 @@ pub fn update_account_contacts( let acc_up_struct = serde_json::to_string(&acc_up_struct)?; let data_builder = set_data_builder!(account, endpoint_name, acc_up_struct.as_bytes()); let url = account.get_endpoint(&endpoint_name)?.account_url.clone(); - http::post_no_response(endpoint, root_certs, &data_builder, &url)?; + create_account_if_does_not_exist!( + http::post_jose_no_response(endpoint, root_certs, &data_builder, &url), + endpoint, + root_certs, + account + )?; account.update_contacts_hash(&endpoint_name)?; account.save()?; account.info(&format!( @@ -86,7 +113,12 @@ pub fn update_account_key( &url, )?; let data_builder = set_data_builder!(account, endpoint_name, rollover_payload.as_bytes()); - http::post_no_response(endpoint, root_certs, &data_builder, &url)?; + create_account_if_does_not_exist!( + http::post_jose_no_response(endpoint, root_certs, &data_builder, &url), + endpoint, + root_certs, + account + )?; account.update_key_hash(&endpoint_name)?; account.save()?; account.info(&format!( @@ -95,3 +127,20 @@ pub fn update_account_key( )); Ok(()) } + +pub fn check_account_exists( + endpoint: &mut Endpoint, + root_certs: &[String], + account: &mut BaseAccount, +) -> Result<(), Error> { + let endpoint_name = endpoint.name.clone(); + let url = account.get_endpoint(&endpoint_name)?.order_url.clone(); + let data_builder = set_empty_data_builder!(account, endpoint_name); + create_account_if_does_not_exist!( + http::post_jose_no_response(endpoint, root_certs, &data_builder, &url), + endpoint, + root_certs, + account + )?; + Ok(()) +} diff --git a/acmed/src/acme_proto/http.rs b/acmed/src/acme_proto/http.rs index 250c195..925de8c 100644 --- a/acmed/src/acme_proto/http.rs +++ b/acmed/src/acme_proto/http.rs @@ -19,19 +19,22 @@ macro_rules! pool_object { }}; } -pub fn refresh_directory(endpoint: &mut Endpoint, root_certs: &[String]) -> Result<(), Error> { +pub fn refresh_directory( + endpoint: &mut Endpoint, + root_certs: &[String], +) -> Result<(), http::HttpError> { let url = endpoint.url.clone(); let response = http::get(endpoint, root_certs, &url)?; endpoint.dir = response.json::()?; Ok(()) } -pub fn post_no_response( +pub fn post_jose_no_response( endpoint: &mut Endpoint, root_certs: &[String], data_builder: &F, url: &str, -) -> Result<(), Error> +) -> Result<(), http::HttpError> where F: Fn(&str, &str) -> Result, { @@ -43,7 +46,7 @@ pub fn new_account( endpoint: &mut Endpoint, root_certs: &[String], data_builder: &F, -) -> Result<(AccountResponse, String), Error> +) -> Result<(AccountResponse, String), http::HttpError> where F: Fn(&str, &str) -> Result, { @@ -62,7 +65,7 @@ pub fn new_order( endpoint: &mut Endpoint, root_certs: &[String], data_builder: &F, -) -> Result<(Order, String), Error> +) -> Result<(Order, String), http::HttpError> where F: Fn(&str, &str) -> Result, { @@ -82,7 +85,7 @@ pub fn get_authorization( root_certs: &[String], data_builder: &F, url: &str, -) -> Result +) -> Result where F: Fn(&str, &str) -> Result, { @@ -97,7 +100,7 @@ pub fn pool_authorization( data_builder: &F, break_fn: &S, url: &str, -) -> Result +) -> Result where F: Fn(&str, &str) -> Result, S: Fn(&Authorization) -> bool, @@ -119,7 +122,7 @@ pub fn pool_order( data_builder: &F, break_fn: &S, url: &str, -) -> Result +) -> Result where F: Fn(&str, &str) -> Result, S: Fn(&Order) -> bool, @@ -140,7 +143,7 @@ pub fn finalize_order( root_certs: &[String], data_builder: &F, url: &str, -) -> Result +) -> Result where F: Fn(&str, &str) -> Result, { @@ -154,7 +157,7 @@ pub fn get_certificate( root_certs: &[String], data_builder: &F, url: &str, -) -> Result +) -> Result where F: Fn(&str, &str) -> Result, { diff --git a/acmed/src/acme_proto/structs/error.rs b/acmed/src/acme_proto/structs/error.rs index 8d4a288..c385648 100644 --- a/acmed/src/acme_proto/structs/error.rs +++ b/acmed/src/acme_proto/structs/error.rs @@ -7,7 +7,7 @@ pub trait ApiError { fn get_error(&self) -> Option; } -#[derive(PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub enum AcmeError { AccountDoesNotExist, AlreadyRevoked, @@ -127,7 +127,7 @@ impl From for Error { } } -#[derive(Clone, PartialEq, Deserialize)] +#[derive(Clone, Debug, PartialEq, Deserialize)] pub struct HttpApiError { #[serde(rename = "type")] error_type: Option, diff --git a/acmed/src/http.rs b/acmed/src/http.rs index c3a5f47..18ace3e 100644 --- a/acmed/src/http.rs +++ b/acmed/src/http.rs @@ -13,6 +13,51 @@ pub const CONTENT_TYPE_PEM: &str = "application/pem-certificate-chain"; pub const HEADER_NONCE: &str = "Replay-Nonce"; pub const HEADER_LOCATION: &str = "Location"; +#[derive(Clone, Debug)] +pub enum HttpError { + ApiError(HttpApiError), + GenericError(Error), +} + +impl HttpError { + pub fn in_err(error: HttpError) -> Error { + match error { + HttpError::ApiError(e) => e.to_string().into(), + HttpError::GenericError(e) => e, + } + } +} + +impl From for HttpError { + fn from(error: Error) -> Self { + HttpError::GenericError(error) + } +} + +impl From for HttpError { + fn from(error: HttpApiError) -> Self { + HttpError::ApiError(error) + } +} + +impl From<&str> for HttpError { + fn from(error: &str) -> Self { + HttpError::GenericError(error.into()) + } +} + +impl From for HttpError { + fn from(error: String) -> Self { + HttpError::GenericError(error.into()) + } +} + +impl From for HttpError { + fn from(error: attohttpc::Error) -> Self { + HttpError::GenericError(error.into()) + } +} + fn is_nonce(data: &str) -> bool { !data.is_empty() && data @@ -20,7 +65,7 @@ fn is_nonce(data: &str) -> bool { .all(|c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_') } -fn new_nonce(endpoint: &mut Endpoint, root_certs: &[String]) -> Result<(), Error> { +fn new_nonce(endpoint: &mut Endpoint, root_certs: &[String]) -> Result<(), HttpError> { rate_limit(endpoint); let url = endpoint.dir.new_nonce.clone(); let _ = get(endpoint, root_certs, &url)?; @@ -81,7 +126,11 @@ fn get_session(root_certs: &[String]) -> Result { Ok(session) } -pub fn get(endpoint: &mut Endpoint, root_certs: &[String], url: &str) -> Result { +pub fn get( + endpoint: &mut Endpoint, + root_certs: &[String], + url: &str, +) -> Result { let mut session = get_session(root_certs)?; session.try_header(header::ACCEPT, CONTENT_TYPE_JSON)?; rate_limit(endpoint); @@ -98,7 +147,7 @@ pub fn post( data_builder: &F, content_type: &str, accept: &str, -) -> Result +) -> Result where F: Fn(&str, &str) -> Result, { @@ -118,11 +167,11 @@ where Ok(_) => { return Ok(response); } - Err(e) => { + Err(_) => { let api_err = response.json::()?; let acme_err = api_err.get_acme_type(); if !acme_err.is_recoverable() { - return Err(e); + return Err(api_err.into()); } } } @@ -136,7 +185,7 @@ pub fn post_jose( root_certs: &[String], url: &str, data_builder: &F, -) -> Result +) -> Result where F: Fn(&str, &str) -> Result, {