Browse Source

Re-create accounts dropped by the endpoint

The previous commit added a small regression: if an account has been dropped by the endpoint, it was not re-created. This commit fixes this.
pull/39/head
Rodolphe Breard 4 years ago
parent
commit
8b2a32d671
  1. 2
      acmed/config/default_hooks.toml
  2. 7
      acmed/src/account.rs
  3. 31
      acmed/src/acme_proto.rs
  4. 59
      acmed/src/acme_proto/account.rs
  5. 23
      acmed/src/acme_proto/http.rs
  6. 4
      acmed/src/acme_proto/structs/error.rs
  7. 61
      acmed/src/http.rs

2
acmed/config/default_hooks.toml

@ -136,6 +136,7 @@ args = [
"-C", "{{file_directory}}", "-C", "{{file_directory}}",
"add", "{{file_name}}" "add", "{{file_name}}"
] ]
allow_failure = true
[[hook]] [[hook]]
name = "git-commit" name = "git-commit"
@ -149,6 +150,7 @@ args = [
"-m", "{{file_name}}", "-m", "{{file_name}}",
"--only", "{{file_name}}" "--only", "{{file_name}}"
] ]
allow_failure = true
[[group]] [[group]]
name = "git" name = "git"

7
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::endpoint::Endpoint;
use crate::logs::HasLogger; use crate::logs::HasLogger;
use crate::storage::FileManager; use crate::storage::FileManager;
@ -206,6 +208,9 @@ impl Account {
if key_changed { if key_changed {
update_account_key(endpoint, root_certs, self)?; update_account_key(endpoint, root_certs, self)?;
} }
if !contacts_changed && !key_changed {
check_account_exists(endpoint, root_certs, self)?;
}
} else { } else {
register_account(endpoint, root_certs, self)?; register_account(endpoint, root_certs, self)?;
} }

31
acmed/src/acme_proto.rs

@ -4,6 +4,7 @@ use crate::acme_proto::structs::{
}; };
use crate::certificate::Certificate; use crate::certificate::Certificate;
use crate::endpoint::Endpoint; use crate::endpoint::Endpoint;
use crate::http::HttpError;
use crate::identifier::IdentifierType; use crate::identifier::IdentifierType;
use crate::jws::encode_kid; use crate::jws::encode_kid;
use crate::logs::HasLogger; use crate::logs::HasLogger;
@ -73,6 +74,8 @@ macro_rules! set_data_builder {
} }
}; };
} }
#[macro_export]
macro_rules! set_empty_data_builder { macro_rules! set_empty_data_builder {
($account: ident, $endpoint_name: ident) => { ($account: ident, $endpoint_name: ident) => {
set_data_builder!($account, $endpoint_name, b"") set_data_builder!($account, $endpoint_name, b"")
@ -89,7 +92,7 @@ pub fn request_certificate(
let endpoint_name = endpoint.name.clone(); let endpoint_name = endpoint.name.clone();
// Refresh the directory // Refresh the directory
http::refresh_directory(endpoint, root_certs)?;
http::refresh_directory(endpoint, root_certs).map_err(HttpError::in_err)?;
// Synchronize the account // Synchronize the account
account.synchronize(endpoint, root_certs)?; account.synchronize(endpoint, root_certs)?;
@ -98,7 +101,8 @@ pub fn request_certificate(
let new_order = NewOrder::new(&cert.identifiers); let new_order = NewOrder::new(&cert.identifiers);
let new_order = serde_json::to_string(&new_order)?; let new_order = serde_json::to_string(&new_order)?;
let data_builder = set_data_builder!(account, endpoint_name, new_order.as_bytes()); 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() { if let Some(e) = order.get_error() {
cert.warn(&e.prefix("Error").message); cert.warn(&e.prefix("Error").message);
} }
@ -107,7 +111,8 @@ pub fn request_certificate(
for auth_url in order.authorizations.iter() { for auth_url in order.authorizations.iter() {
// Fetch the authorization // Fetch the authorization
let data_builder = set_empty_data_builder!(account, endpoint_name); 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() { if let Some(e) = auth.get_error() {
cert.warn(&e.prefix("Error").message); cert.warn(&e.prefix("Error").message);
} }
@ -139,15 +144,17 @@ pub fn request_certificate(
// Tell the server the challenge has been completed // Tell the server the challenge has been completed
let chall_url = challenge.get_url(); let chall_url = challenge.get_url();
let data_builder = set_data_builder!(account, endpoint_name, b"{}"); 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 // Pool the authorization in order to see whether or not it is valid
let data_builder = set_empty_data_builder!(account, endpoint_name); let data_builder = set_empty_data_builder!(account, endpoint_name);
let break_fn = |a: &Authorization| a.status == AuthorizationStatus::Valid; 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() { for (data, hook_type) in hook_datas.iter() {
cert.call_challenge_hooks_clean(&data, (*hook_type).to_owned())?; 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 // Pool the order in order to see whether or not it is ready
let data_builder = set_empty_data_builder!(account, endpoint_name); let data_builder = set_empty_data_builder!(account, endpoint_name);
let break_fn = |o: &Order| o.status == OrderStatus::Ready; 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 // Finalize the order by sending the CSR
let key_pair = certificate::get_key_pair(cert)?; let key_pair = certificate::get_key_pair(cert)?;
@ -186,7 +194,8 @@ pub fn request_certificate(
}); });
let csr = csr.to_string(); let csr = csr.to_string();
let data_builder = set_data_builder!(account, endpoint_name, csr.as_bytes()); 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() { if let Some(e) = order.get_error() {
cert.warn(&e.prefix("Error").message); 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 // Pool the order in order to see whether or not it is valid
let data_builder = set_empty_data_builder!(account, endpoint_name); let data_builder = set_empty_data_builder!(account, endpoint_name);
let break_fn = |o: &Order| o.status == OrderStatus::Valid; 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 // Download the certificate
let crt_url = order let crt_url = order
.certificate .certificate
.ok_or_else(|| Error::from("No certificate available for download."))?; .ok_or_else(|| Error::from("No certificate available for download."))?;
let data_builder = set_empty_data_builder!(account, endpoint_name); 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())?; storage::write_certificate(&cert.file_manager, &crt.as_bytes())?;
cert.info(&format!( cert.info(&format!(

59
acmed/src/acme_proto/account.rs

@ -1,12 +1,33 @@
use crate::account::Account as BaseAccount; use crate::account::Account as BaseAccount;
use crate::acme_proto::http; 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::endpoint::Endpoint;
use crate::http::HttpError;
use crate::jws::{encode_jwk, encode_jwk_no_nonce, encode_kid}; use crate::jws::{encode_jwk, encode_jwk_no_nonce, encode_kid};
use crate::logs::HasLogger; use crate::logs::HasLogger;
use crate::set_data_builder;
use crate::{set_data_builder, set_empty_data_builder};
use acme_common::error::Error; 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( pub fn register_account(
endpoint: &mut Endpoint, endpoint: &mut Endpoint,
root_certs: &[String], root_certs: &[String],
@ -23,7 +44,8 @@ pub fn register_account(
let signature_algorithm = &account.current_key.signature_algorithm; let signature_algorithm = &account.current_key.signature_algorithm;
let data_builder = let data_builder =
|n: &str, url: &str| encode_jwk(kp_ref, signature_algorithm, acc_ref.as_bytes(), url, n); |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)?; account.set_account_url(&endpoint.name, &account_url)?;
let msg = format!( let msg = format!(
"endpoint {}: account {}: the server has not provided an order URL upon account creation", "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 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 data_builder = set_data_builder!(account, endpoint_name, acc_up_struct.as_bytes());
let url = account.get_endpoint(&endpoint_name)?.account_url.clone(); 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.update_contacts_hash(&endpoint_name)?;
account.save()?; account.save()?;
account.info(&format!( account.info(&format!(
@ -86,7 +113,12 @@ pub fn update_account_key(
&url, &url,
)?; )?;
let data_builder = set_data_builder!(account, endpoint_name, rollover_payload.as_bytes()); 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.update_key_hash(&endpoint_name)?;
account.save()?; account.save()?;
account.info(&format!( account.info(&format!(
@ -95,3 +127,20 @@ pub fn update_account_key(
)); ));
Ok(()) 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(())
}

23
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 url = endpoint.url.clone();
let response = http::get(endpoint, root_certs, &url)?; let response = http::get(endpoint, root_certs, &url)?;
endpoint.dir = response.json::<Directory>()?; endpoint.dir = response.json::<Directory>()?;
Ok(()) Ok(())
} }
pub fn post_no_response<F>(
pub fn post_jose_no_response<F>(
endpoint: &mut Endpoint, endpoint: &mut Endpoint,
root_certs: &[String], root_certs: &[String],
data_builder: &F, data_builder: &F,
url: &str, url: &str,
) -> Result<(), Error>
) -> Result<(), http::HttpError>
where where
F: Fn(&str, &str) -> Result<String, Error>, F: Fn(&str, &str) -> Result<String, Error>,
{ {
@ -43,7 +46,7 @@ pub fn new_account<F>(
endpoint: &mut Endpoint, endpoint: &mut Endpoint,
root_certs: &[String], root_certs: &[String],
data_builder: &F, data_builder: &F,
) -> Result<(AccountResponse, String), Error>
) -> Result<(AccountResponse, String), http::HttpError>
where where
F: Fn(&str, &str) -> Result<String, Error>, F: Fn(&str, &str) -> Result<String, Error>,
{ {
@ -62,7 +65,7 @@ pub fn new_order<F>(
endpoint: &mut Endpoint, endpoint: &mut Endpoint,
root_certs: &[String], root_certs: &[String],
data_builder: &F, data_builder: &F,
) -> Result<(Order, String), Error>
) -> Result<(Order, String), http::HttpError>
where where
F: Fn(&str, &str) -> Result<String, Error>, F: Fn(&str, &str) -> Result<String, Error>,
{ {
@ -82,7 +85,7 @@ pub fn get_authorization<F>(
root_certs: &[String], root_certs: &[String],
data_builder: &F, data_builder: &F,
url: &str, url: &str,
) -> Result<Authorization, Error>
) -> Result<Authorization, http::HttpError>
where where
F: Fn(&str, &str) -> Result<String, Error>, F: Fn(&str, &str) -> Result<String, Error>,
{ {
@ -97,7 +100,7 @@ pub fn pool_authorization<F, S>(
data_builder: &F, data_builder: &F,
break_fn: &S, break_fn: &S,
url: &str, url: &str,
) -> Result<Authorization, Error>
) -> Result<Authorization, http::HttpError>
where where
F: Fn(&str, &str) -> Result<String, Error>, F: Fn(&str, &str) -> Result<String, Error>,
S: Fn(&Authorization) -> bool, S: Fn(&Authorization) -> bool,
@ -119,7 +122,7 @@ pub fn pool_order<F, S>(
data_builder: &F, data_builder: &F,
break_fn: &S, break_fn: &S,
url: &str, url: &str,
) -> Result<Order, Error>
) -> Result<Order, http::HttpError>
where where
F: Fn(&str, &str) -> Result<String, Error>, F: Fn(&str, &str) -> Result<String, Error>,
S: Fn(&Order) -> bool, S: Fn(&Order) -> bool,
@ -140,7 +143,7 @@ pub fn finalize_order<F>(
root_certs: &[String], root_certs: &[String],
data_builder: &F, data_builder: &F,
url: &str, url: &str,
) -> Result<Order, Error>
) -> Result<Order, http::HttpError>
where where
F: Fn(&str, &str) -> Result<String, Error>, F: Fn(&str, &str) -> Result<String, Error>,
{ {
@ -154,7 +157,7 @@ pub fn get_certificate<F>(
root_certs: &[String], root_certs: &[String],
data_builder: &F, data_builder: &F,
url: &str, url: &str,
) -> Result<String, Error>
) -> Result<String, http::HttpError>
where where
F: Fn(&str, &str) -> Result<String, Error>, F: Fn(&str, &str) -> Result<String, Error>,
{ {

4
acmed/src/acme_proto/structs/error.rs

@ -7,7 +7,7 @@ pub trait ApiError {
fn get_error(&self) -> Option<Error>; fn get_error(&self) -> Option<Error>;
} }
#[derive(PartialEq)]
#[derive(Clone, Debug, PartialEq)]
pub enum AcmeError { pub enum AcmeError {
AccountDoesNotExist, AccountDoesNotExist,
AlreadyRevoked, AlreadyRevoked,
@ -127,7 +127,7 @@ impl From<AcmeError> for Error {
} }
} }
#[derive(Clone, PartialEq, Deserialize)]
#[derive(Clone, Debug, PartialEq, Deserialize)]
pub struct HttpApiError { pub struct HttpApiError {
#[serde(rename = "type")] #[serde(rename = "type")]
error_type: Option<String>, error_type: Option<String>,

61
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_NONCE: &str = "Replay-Nonce";
pub const HEADER_LOCATION: &str = "Location"; 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<Error> for HttpError {
fn from(error: Error) -> Self {
HttpError::GenericError(error)
}
}
impl From<HttpApiError> 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<String> for HttpError {
fn from(error: String) -> Self {
HttpError::GenericError(error.into())
}
}
impl From<attohttpc::Error> for HttpError {
fn from(error: attohttpc::Error) -> Self {
HttpError::GenericError(error.into())
}
}
fn is_nonce(data: &str) -> bool { fn is_nonce(data: &str) -> bool {
!data.is_empty() !data.is_empty()
&& data && data
@ -20,7 +65,7 @@ fn is_nonce(data: &str) -> bool {
.all(|c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_') .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); rate_limit(endpoint);
let url = endpoint.dir.new_nonce.clone(); let url = endpoint.dir.new_nonce.clone();
let _ = get(endpoint, root_certs, &url)?; let _ = get(endpoint, root_certs, &url)?;
@ -81,7 +126,11 @@ fn get_session(root_certs: &[String]) -> Result<Session, Error> {
Ok(session) Ok(session)
} }
pub fn get(endpoint: &mut Endpoint, root_certs: &[String], url: &str) -> Result<Response, Error> {
pub fn get(
endpoint: &mut Endpoint,
root_certs: &[String],
url: &str,
) -> Result<Response, HttpError> {
let mut session = get_session(root_certs)?; let mut session = get_session(root_certs)?;
session.try_header(header::ACCEPT, CONTENT_TYPE_JSON)?; session.try_header(header::ACCEPT, CONTENT_TYPE_JSON)?;
rate_limit(endpoint); rate_limit(endpoint);
@ -98,7 +147,7 @@ pub fn post<F>(
data_builder: &F, data_builder: &F,
content_type: &str, content_type: &str,
accept: &str, accept: &str,
) -> Result<Response, Error>
) -> Result<Response, HttpError>
where where
F: Fn(&str, &str) -> Result<String, Error>, F: Fn(&str, &str) -> Result<String, Error>,
{ {
@ -118,11 +167,11 @@ where
Ok(_) => { Ok(_) => {
return Ok(response); return Ok(response);
} }
Err(e) => {
Err(_) => {
let api_err = response.json::<HttpApiError>()?; let api_err = response.json::<HttpApiError>()?;
let acme_err = api_err.get_acme_type(); let acme_err = api_err.get_acme_type();
if !acme_err.is_recoverable() { if !acme_err.is_recoverable() {
return Err(e);
return Err(api_err.into());
} }
} }
} }
@ -136,7 +185,7 @@ pub fn post_jose<F>(
root_certs: &[String], root_certs: &[String],
url: &str, url: &str,
data_builder: &F, data_builder: &F,
) -> Result<Response, Error>
) -> Result<Response, HttpError>
where where
F: Fn(&str, &str) -> Result<String, Error>, F: Fn(&str, &str) -> Result<String, Error>,
{ {

Loading…
Cancel
Save