Browse Source

Add the `root_certificates` parameter

Being able to define root certificates in the command line is not enough
for two reasons:
 1. It is always global, you cannot define a root certificate for a
    specific endpoint.
 2. Daemon scripts and unit files are not meant to be changed every time
    you need to add a root certificate.

For those reasons, it is now to possible to define root certificates in
the configuration. Those defined in the `global` section will be used on
every endpoint, just like those added via the command line. Those
defined in an endpoint will be used in this endpoint only.
pull/39/head
Rodolphe Breard 4 years ago
parent
commit
9ec48e7e03
  1. 6
      CHANGELOG.md
  2. 22
      acmed/src/account.rs
  3. 26
      acmed/src/acme_proto.rs
  4. 25
      acmed/src/acme_proto/account.rs
  5. 41
      acmed/src/acme_proto/http.rs
  6. 34
      acmed/src/config.rs
  7. 3
      acmed/src/endpoint.rs
  8. 19
      acmed/src/http.rs
  9. 16
      acmed/src/main_event_loop.rs
  10. 4
      man/en/acmed.toml.5

6
CHANGELOG.md

@ -13,6 +13,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- In the configuration, `root_certificates` has been added to the `global` and `endpoint` sections as an array of strings representing the path to root certificate files.
## [0.12.0] - 2020-09-26 ## [0.12.0] - 2020-09-26
### Added ### Added

22
acmed/src/account.rs

@ -202,11 +202,7 @@ impl Account {
.or_insert_with(AccountEndpoint::new); .or_insert_with(AccountEndpoint::new);
} }
pub fn synchronize(
&mut self,
endpoint: &mut Endpoint,
root_certs: &[String],
) -> Result<(), Error> {
pub fn synchronize(&mut self, endpoint: &mut Endpoint) -> Result<(), Error> {
let acc_ep = self.get_endpoint(&endpoint.name)?; let acc_ep = self.get_endpoint(&endpoint.name)?;
if !acc_ep.account_url.is_empty() { if !acc_ep.account_url.is_empty() {
if let Some(ec) = &self.external_account { if let Some(ec) = &self.external_account {
@ -217,7 +213,7 @@ impl Account {
&endpoint.name &endpoint.name
); );
self.info(&msg); self.info(&msg);
register_account(endpoint, root_certs, self)?;
register_account(endpoint, self)?;
return Ok(()); return Ok(());
} }
} }
@ -226,23 +222,19 @@ impl Account {
let contacts_changed = ct_hash != acc_ep.contacts_hash; let contacts_changed = ct_hash != acc_ep.contacts_hash;
let key_changed = key_hash != acc_ep.key_hash; let key_changed = key_hash != acc_ep.key_hash;
if contacts_changed { if contacts_changed {
update_account_contacts(endpoint, root_certs, self)?;
update_account_contacts(endpoint, self)?;
} }
if key_changed { if key_changed {
update_account_key(endpoint, root_certs, self)?;
update_account_key(endpoint, self)?;
} }
} else { } else {
register_account(endpoint, root_certs, self)?;
register_account(endpoint, self)?;
} }
Ok(()) Ok(())
} }
pub fn register(
&mut self,
endpoint: &mut Endpoint,
root_certs: &[String],
) -> Result<(), Error> {
register_account(endpoint, root_certs, self)
pub fn register(&mut self, endpoint: &mut Endpoint) -> Result<(), Error> {
register_account(endpoint, self)
} }
pub fn save(&self) -> Result<(), Error> { pub fn save(&self) -> Result<(), Error> {

26
acmed/src/acme_proto.rs

@ -84,7 +84,6 @@ macro_rules! set_empty_data_builder {
pub fn request_certificate( pub fn request_certificate(
cert: &Certificate, cert: &Certificate,
root_certs: &[String],
endpoint: &mut Endpoint, endpoint: &mut Endpoint,
account: &mut Account, account: &mut Account,
) -> Result<(), Error> { ) -> Result<(), Error> {
@ -92,10 +91,10 @@ 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).map_err(HttpError::in_err)?;
http::refresh_directory(endpoint).map_err(HttpError::in_err)?;
// Synchronize the account // Synchronize the account
account.synchronize(endpoint, root_certs)?;
account.synchronize(endpoint)?;
// Create a new order // Create a new order
let mut new_reg = false; let mut new_reg = false;
@ -103,7 +102,7 @@ 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());
match http::new_order(endpoint, root_certs, &data_builder) {
match http::new_order(endpoint, &data_builder) {
Ok((order, order_url)) => { Ok((order, order_url)) => {
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);
@ -112,7 +111,7 @@ pub fn request_certificate(
} }
Err(e) => { Err(e) => {
if !new_reg && e.is_acme_err(AcmeError::AccountDoesNotExist) { if !new_reg && e.is_acme_err(AcmeError::AccountDoesNotExist) {
account.register(endpoint, root_certs)?;
account.register(endpoint)?;
new_reg = true; new_reg = true;
} else { } else {
return Err(HttpError::in_err(e)); return Err(HttpError::in_err(e));
@ -125,7 +124,7 @@ 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, &data_builder, &auth_url)
.map_err(HttpError::in_err)?; .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);
@ -158,8 +157,7 @@ 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_jose_no_response(endpoint, root_certs, &data_builder, &chall_url)
let _ = http::post_jose_no_response(endpoint, &data_builder, &chall_url)
.map_err(HttpError::in_err)?; .map_err(HttpError::in_err)?;
} }
} }
@ -167,7 +165,7 @@ pub fn request_certificate(
// 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, &data_builder, &break_fn, &auth_url)
.map_err(HttpError::in_err)?; .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())?;
@ -179,7 +177,7 @@ 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, &data_builder, &break_fn, &order_url)
.map_err(HttpError::in_err)?; .map_err(HttpError::in_err)?;
// Finalize the order by sending the CSR // Finalize the order by sending the CSR
@ -209,7 +207,7 @@ 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, &data_builder, &order.finalize)
.map_err(HttpError::in_err)?; .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);
@ -218,7 +216,7 @@ 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, &data_builder, &break_fn, &order_url)
.map_err(HttpError::in_err)?; .map_err(HttpError::in_err)?;
// Download the certificate // Download the certificate
@ -226,8 +224,8 @@ pub fn request_certificate(
.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)
.map_err(HttpError::in_err)?;
let crt =
http::get_certificate(endpoint, &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!(

25
acmed/src/acme_proto/account.rs

@ -9,7 +9,7 @@ use crate::set_data_builder;
use acme_common::error::Error; use acme_common::error::Error;
macro_rules! create_account_if_does_not_exist { macro_rules! create_account_if_does_not_exist {
($e: expr, $endpoint: ident, $root_certs: ident, $account: ident) => {
($e: expr, $endpoint: ident, $account: ident) => {
match $e { match $e {
Ok(r) => Ok(r), Ok(r) => Ok(r),
Err(he) => match he { Err(he) => match he {
@ -20,7 +20,7 @@ macro_rules! create_account_if_does_not_exist {
$endpoint.name $endpoint.name
); );
$account.debug(&msg); $account.debug(&msg);
return register_account($endpoint, $root_certs, $account);
return register_account($endpoint, $account);
} }
_ => Err(HttpError::in_err(he.to_owned())), _ => Err(HttpError::in_err(he.to_owned())),
}, },
@ -30,11 +30,7 @@ macro_rules! create_account_if_does_not_exist {
}; };
} }
pub fn register_account(
endpoint: &mut Endpoint,
root_certs: &[String],
account: &mut BaseAccount,
) -> Result<(), Error> {
pub fn register_account(endpoint: &mut Endpoint, account: &mut BaseAccount) -> Result<(), Error> {
account.debug(&format!( account.debug(&format!(
"creating account on endpoint \"{}\"...", "creating account on endpoint \"{}\"...",
&endpoint.name &endpoint.name
@ -47,7 +43,7 @@ pub fn register_account(
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) = let (acc_rep, account_url) =
http::new_account(endpoint, root_certs, &data_builder).map_err(HttpError::in_err)?;
http::new_account(endpoint, &data_builder).map_err(HttpError::in_err)?;
account.set_account_url(&endpoint.name, &account_url)?; account.set_account_url(&endpoint.name, &account_url)?;
let orders_url = match acc_rep.orders { let orders_url = match acc_rep.orders {
Some(url) => url, Some(url) => url,
@ -75,7 +71,6 @@ pub fn register_account(
pub fn update_account_contacts( pub fn update_account_contacts(
endpoint: &mut Endpoint, endpoint: &mut Endpoint,
root_certs: &[String],
account: &mut BaseAccount, account: &mut BaseAccount,
) -> Result<(), Error> { ) -> Result<(), Error> {
let endpoint_name = endpoint.name.clone(); let endpoint_name = endpoint.name.clone();
@ -89,9 +84,8 @@ pub fn update_account_contacts(
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();
create_account_if_does_not_exist!( create_account_if_does_not_exist!(
http::post_jose_no_response(endpoint, root_certs, &data_builder, &url),
http::post_jose_no_response(endpoint, &data_builder, &url),
endpoint, endpoint,
root_certs,
account account
)?; )?;
account.update_contacts_hash(&endpoint_name)?; account.update_contacts_hash(&endpoint_name)?;
@ -103,11 +97,7 @@ pub fn update_account_contacts(
Ok(()) Ok(())
} }
pub fn update_account_key(
endpoint: &mut Endpoint,
root_certs: &[String],
account: &mut BaseAccount,
) -> Result<(), Error> {
pub fn update_account_key(endpoint: &mut Endpoint, account: &mut BaseAccount) -> Result<(), Error> {
let endpoint_name = endpoint.name.clone(); let endpoint_name = endpoint.name.clone();
account.debug(&format!( account.debug(&format!(
"updating account key on endpoint \"{}\"...", "updating account key on endpoint \"{}\"...",
@ -137,9 +127,8 @@ pub fn update_account_key(
) )
}; };
create_account_if_does_not_exist!( create_account_if_does_not_exist!(
http::post_jose_no_response(endpoint, root_certs, &data_builder, &url),
http::post_jose_no_response(endpoint, &data_builder, &url),
endpoint, endpoint,
root_certs,
account account
)?; )?;
account.update_key_hash(&endpoint_name)?; account.update_key_hash(&endpoint_name)?;

41
acmed/src/acme_proto/http.rs

@ -5,10 +5,10 @@ use acme_common::error::Error;
use std::{thread, time}; use std::{thread, time};
macro_rules! pool_object { macro_rules! pool_object {
($obj_type: ty, $obj_name: expr, $endpoint: expr, $root_certs: expr, $url: expr, $data_builder: expr, $break: expr) => {{
($obj_type: ty, $obj_name: expr, $endpoint: expr, $url: expr, $data_builder: expr, $break: expr) => {{
for _ in 0..crate::DEFAULT_POOL_NB_TRIES { for _ in 0..crate::DEFAULT_POOL_NB_TRIES {
thread::sleep(time::Duration::from_secs(crate::DEFAULT_POOL_WAIT_SEC)); thread::sleep(time::Duration::from_secs(crate::DEFAULT_POOL_WAIT_SEC));
let response = http::post_jose($endpoint, $root_certs, $url, $data_builder)?;
let response = http::post_jose($endpoint, $url, $data_builder)?;
let obj = response.json::<$obj_type>()?; let obj = response.json::<$obj_type>()?;
if $break(&obj) { if $break(&obj) {
return Ok(obj); return Ok(obj);
@ -19,39 +19,34 @@ macro_rules! pool_object {
}}; }};
} }
pub fn refresh_directory(
endpoint: &mut Endpoint,
root_certs: &[String],
) -> Result<(), http::HttpError> {
pub fn refresh_directory(endpoint: &mut Endpoint) -> 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, &url)?;
endpoint.dir = response.json::<Directory>()?; endpoint.dir = response.json::<Directory>()?;
Ok(()) Ok(())
} }
pub fn post_jose_no_response<F>( pub fn post_jose_no_response<F>(
endpoint: &mut Endpoint, endpoint: &mut Endpoint,
root_certs: &[String],
data_builder: &F, data_builder: &F,
url: &str, url: &str,
) -> Result<(), http::HttpError> ) -> Result<(), http::HttpError>
where where
F: Fn(&str, &str) -> Result<String, Error>, F: Fn(&str, &str) -> Result<String, Error>,
{ {
let _ = http::post_jose(endpoint, root_certs, &url, data_builder)?;
let _ = http::post_jose(endpoint, &url, data_builder)?;
Ok(()) Ok(())
} }
pub fn new_account<F>( pub fn new_account<F>(
endpoint: &mut Endpoint, endpoint: &mut Endpoint,
root_certs: &[String],
data_builder: &F, data_builder: &F,
) -> Result<(AccountResponse, String), http::HttpError> ) -> Result<(AccountResponse, String), http::HttpError>
where where
F: Fn(&str, &str) -> Result<String, Error>, F: Fn(&str, &str) -> Result<String, Error>,
{ {
let url = endpoint.dir.new_account.clone(); let url = endpoint.dir.new_account.clone();
let response = http::post_jose(endpoint, root_certs, &url, data_builder)?;
let response = http::post_jose(endpoint, &url, data_builder)?;
let acc_uri = response let acc_uri = response
.get_header(http::HEADER_LOCATION) .get_header(http::HEADER_LOCATION)
.ok_or_else(|| Error::from("no account location found"))?; .ok_or_else(|| Error::from("no account location found"))?;
@ -61,14 +56,13 @@ where
pub fn new_order<F>( pub fn new_order<F>(
endpoint: &mut Endpoint, endpoint: &mut Endpoint,
root_certs: &[String],
data_builder: &F, data_builder: &F,
) -> Result<(Order, String), http::HttpError> ) -> Result<(Order, String), http::HttpError>
where where
F: Fn(&str, &str) -> Result<String, Error>, F: Fn(&str, &str) -> Result<String, Error>,
{ {
let url = endpoint.dir.new_order.clone(); let url = endpoint.dir.new_order.clone();
let response = http::post_jose(endpoint, root_certs, &url, data_builder)?;
let response = http::post_jose(endpoint, &url, data_builder)?;
let order_uri = response let order_uri = response
.get_header(http::HEADER_LOCATION) .get_header(http::HEADER_LOCATION)
.ok_or_else(|| Error::from("no account location found"))?; .ok_or_else(|| Error::from("no account location found"))?;
@ -78,21 +72,19 @@ where
pub fn get_authorization<F>( pub fn get_authorization<F>(
endpoint: &mut Endpoint, endpoint: &mut Endpoint,
root_certs: &[String],
data_builder: &F, data_builder: &F,
url: &str, url: &str,
) -> Result<Authorization, http::HttpError> ) -> Result<Authorization, http::HttpError>
where where
F: Fn(&str, &str) -> Result<String, Error>, F: Fn(&str, &str) -> Result<String, Error>,
{ {
let response = http::post_jose(endpoint, root_certs, &url, data_builder)?;
let response = http::post_jose(endpoint, &url, data_builder)?;
let auth = response.json::<Authorization>()?; let auth = response.json::<Authorization>()?;
Ok(auth) Ok(auth)
} }
pub fn pool_authorization<F, S>( pub fn pool_authorization<F, S>(
endpoint: &mut Endpoint, endpoint: &mut Endpoint,
root_certs: &[String],
data_builder: &F, data_builder: &F,
break_fn: &S, break_fn: &S,
url: &str, url: &str,
@ -105,7 +97,6 @@ where
Authorization, Authorization,
"authorization", "authorization",
endpoint, endpoint,
root_certs,
url, url,
data_builder, data_builder,
break_fn break_fn
@ -114,7 +105,6 @@ where
pub fn pool_order<F, S>( pub fn pool_order<F, S>(
endpoint: &mut Endpoint, endpoint: &mut Endpoint,
root_certs: &[String],
data_builder: &F, data_builder: &F,
break_fn: &S, break_fn: &S,
url: &str, url: &str,
@ -123,34 +113,24 @@ where
F: Fn(&str, &str) -> Result<String, Error>, F: Fn(&str, &str) -> Result<String, Error>,
S: Fn(&Order) -> bool, S: Fn(&Order) -> bool,
{ {
pool_object!(
Order,
"order",
endpoint,
root_certs,
url,
data_builder,
break_fn
)
pool_object!(Order, "order", endpoint, url, data_builder, break_fn)
} }
pub fn finalize_order<F>( pub fn finalize_order<F>(
endpoint: &mut Endpoint, endpoint: &mut Endpoint,
root_certs: &[String],
data_builder: &F, data_builder: &F,
url: &str, url: &str,
) -> Result<Order, http::HttpError> ) -> Result<Order, http::HttpError>
where where
F: Fn(&str, &str) -> Result<String, Error>, F: Fn(&str, &str) -> Result<String, Error>,
{ {
let response = http::post_jose(endpoint, root_certs, &url, data_builder)?;
let response = http::post_jose(endpoint, &url, data_builder)?;
let order = response.json::<Order>()?; let order = response.json::<Order>()?;
Ok(order) Ok(order)
} }
pub fn get_certificate<F>( pub fn get_certificate<F>(
endpoint: &mut Endpoint, endpoint: &mut Endpoint,
root_certs: &[String],
data_builder: &F, data_builder: &F,
url: &str, url: &str,
) -> Result<String, http::HttpError> ) -> Result<String, http::HttpError>
@ -159,7 +139,6 @@ where
{ {
let response = http::post( let response = http::post(
endpoint, endpoint,
root_certs,
&url, &url,
data_builder, data_builder,
http::CONTENT_TYPE_JOSE, http::CONTENT_TYPE_JOSE,

34
acmed/src/config.rs

@ -186,6 +186,7 @@ pub struct GlobalOptions {
#[serde(default)] #[serde(default)]
pub env: HashMap<String, String>, pub env: HashMap<String, String>,
pub renew_delay: Option<String>, pub renew_delay: Option<String>,
pub root_certificates: Option<Vec<String>>,
} }
impl GlobalOptions { impl GlobalOptions {
@ -206,6 +207,7 @@ pub struct Endpoint {
#[serde(default)] #[serde(default)]
pub rate_limits: Vec<String>, pub rate_limits: Vec<String>,
pub renew_delay: Option<String>, pub renew_delay: Option<String>,
pub root_certificates: Option<Vec<String>>,
} }
impl Endpoint { impl Endpoint {
@ -219,13 +221,33 @@ impl Endpoint {
} }
} }
fn to_generic(&self, cnf: &Config) -> Result<crate::endpoint::Endpoint, Error> {
fn to_generic(
&self,
cnf: &Config,
root_certs: &[&str],
) -> Result<crate::endpoint::Endpoint, Error> {
let mut limits = vec![]; let mut limits = vec![];
for rl_name in self.rate_limits.iter() { for rl_name in self.rate_limits.iter() {
let (nb, timeframe) = cnf.get_rate_limit(&rl_name)?; let (nb, timeframe) = cnf.get_rate_limit(&rl_name)?;
limits.push((nb, timeframe)); limits.push((nb, timeframe));
} }
crate::endpoint::Endpoint::new(&self.name, &self.url, self.tos_agreed, &limits)
let mut root_lst: Vec<String> = vec![];
root_lst.extend(root_certs.iter().map(|v| v.to_string()));
if let Some(crt_lst) = &self.root_certificates {
root_lst.extend(crt_lst.iter().map(|v| v.to_owned()));
}
if let Some(glob) = &cnf.global {
if let Some(crt_lst) = &glob.root_certificates {
root_lst.extend(crt_lst.iter().map(|v| v.to_owned()));
}
}
crate::endpoint::Endpoint::new(
&self.name,
&self.url,
self.tos_agreed,
&limits,
root_lst.as_slice(),
)
} }
} }
@ -477,9 +499,13 @@ impl Certificate {
Err(format!("{}: unknown endpoint", self.endpoint).into()) Err(format!("{}: unknown endpoint", self.endpoint).into())
} }
pub fn get_endpoint(&self, cnf: &Config) -> Result<crate::endpoint::Endpoint, Error> {
pub fn get_endpoint(
&self,
cnf: &Config,
root_certs: &[&str],
) -> Result<crate::endpoint::Endpoint, Error> {
let endpoint = self.do_get_endpoint(cnf)?; let endpoint = self.do_get_endpoint(cnf)?;
endpoint.to_generic(cnf)
endpoint.to_generic(cnf, root_certs)
} }
pub fn get_hooks(&self, cnf: &Config) -> Result<Vec<hooks::Hook>, Error> { pub fn get_hooks(&self, cnf: &Config) -> Result<Vec<hooks::Hook>, Error> {

3
acmed/src/endpoint.rs

@ -13,6 +13,7 @@ pub struct Endpoint {
pub nonce: Option<String>, pub nonce: Option<String>,
pub rl: RateLimit, pub rl: RateLimit,
pub dir: Directory, pub dir: Directory,
pub root_certificates: Vec<String>,
} }
impl Endpoint { impl Endpoint {
@ -21,6 +22,7 @@ impl Endpoint {
url: &str, url: &str,
tos_agreed: bool, tos_agreed: bool,
limits: &[(usize, String)], limits: &[(usize, String)],
root_certs: &[String],
) -> Result<Self, Error> { ) -> Result<Self, Error> {
Ok(Self { Ok(Self {
name: name.to_string(), name: name.to_string(),
@ -37,6 +39,7 @@ impl Endpoint {
revoke_cert: String::new(), revoke_cert: String::new(),
key_change: String::new(), key_change: String::new(),
}, },
root_certificates: root_certs.to_vec(),
}) })
} }
} }

19
acmed/src/http.rs

@ -106,10 +106,10 @@ 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<(), HttpError> {
fn new_nonce(endpoint: &mut Endpoint) -> 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, &url)?;
Ok(()) Ok(())
} }
@ -170,12 +170,8 @@ 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<ValidHttpResponse, HttpError> {
let mut session = get_session(root_certs)?;
pub fn get(endpoint: &mut Endpoint, url: &str) -> Result<ValidHttpResponse, HttpError> {
let mut session = get_session(&endpoint.root_certificates)?;
session.try_header(header::ACCEPT, CONTENT_TYPE_JSON)?; session.try_header(header::ACCEPT, CONTENT_TYPE_JSON)?;
rate_limit(endpoint); rate_limit(endpoint);
let response = session.get(url).send()?; let response = session.get(url).send()?;
@ -186,7 +182,6 @@ pub fn get(
pub fn post<F>( pub fn post<F>(
endpoint: &mut Endpoint, endpoint: &mut Endpoint,
root_certs: &[String],
url: &str, url: &str,
data_builder: &F, data_builder: &F,
content_type: &str, content_type: &str,
@ -195,11 +190,11 @@ pub fn post<F>(
where where
F: Fn(&str, &str) -> Result<String, Error>, F: Fn(&str, &str) -> Result<String, Error>,
{ {
let mut session = get_session(root_certs)?;
let mut session = get_session(&endpoint.root_certificates)?;
session.try_header(header::ACCEPT, accept)?; session.try_header(header::ACCEPT, accept)?;
session.try_header(header::CONTENT_TYPE, content_type)?; session.try_header(header::CONTENT_TYPE, content_type)?;
if endpoint.nonce.is_none() { if endpoint.nonce.is_none() {
let _ = new_nonce(endpoint, root_certs);
let _ = new_nonce(endpoint);
} }
for _ in 0..crate::DEFAULT_HTTP_FAIL_NB_RETRY { for _ in 0..crate::DEFAULT_HTTP_FAIL_NB_RETRY {
let nonce = &endpoint.nonce.clone().unwrap_or_default(); let nonce = &endpoint.nonce.clone().unwrap_or_default();
@ -228,7 +223,6 @@ where
pub fn post_jose<F>( pub fn post_jose<F>(
endpoint: &mut Endpoint, endpoint: &mut Endpoint,
root_certs: &[String],
url: &str, url: &str,
data_builder: &F, data_builder: &F,
) -> Result<ValidHttpResponse, HttpError> ) -> Result<ValidHttpResponse, HttpError>
@ -237,7 +231,6 @@ where
{ {
post( post(
endpoint, endpoint,
root_certs,
url, url,
data_builder, data_builder,
CONTENT_TYPE_JOSE, CONTENT_TYPE_JOSE,

16
acmed/src/main_event_loop.rs

@ -15,13 +15,8 @@ use std::time::Duration;
type AccountSync = Arc<RwLock<Account>>; type AccountSync = Arc<RwLock<Account>>;
type EndpointSync = Arc<RwLock<Endpoint>>; type EndpointSync = Arc<RwLock<Endpoint>>;
fn renew_certificate(
crt: &Certificate,
root_certs: &[String],
endpoint: &mut Endpoint,
account: &mut Account,
) {
let (status, is_success) = match request_certificate(crt, root_certs, endpoint, account) {
fn renew_certificate(crt: &Certificate, endpoint: &mut Endpoint, account: &mut Account) {
let (status, is_success) = match request_certificate(crt, endpoint, account) {
Ok(_) => ("success".to_string(), true), Ok(_) => ("success".to_string(), true),
Err(e) => { Err(e) => {
let e = e.prefix("unable to renew the certificate"); let e = e.prefix("unable to renew the certificate");
@ -40,7 +35,6 @@ fn renew_certificate(
pub struct MainEventLoop { pub struct MainEventLoop {
certs: Vec<Certificate>, certs: Vec<Certificate>,
root_certs: Vec<String>,
accounts: HashMap<String, AccountSync>, accounts: HashMap<String, AccountSync>,
endpoints: HashMap<String, EndpointSync>, endpoints: HashMap<String, EndpointSync>,
} }
@ -98,7 +92,7 @@ impl MainEventLoop {
let mut certs = Vec::new(); let mut certs = Vec::new();
let mut endpoints = HashMap::new(); let mut endpoints = HashMap::new();
for (i, crt) in cnf.certificate.iter().enumerate() { for (i, crt) in cnf.certificate.iter().enumerate() {
let endpoint = crt.get_endpoint(&cnf)?;
let endpoint = crt.get_endpoint(&cnf, root_certs)?;
let endpoint_name = endpoint.name.clone(); let endpoint_name = endpoint.name.clone();
let crt_name = crt.get_crt_name()?; let crt_name = crt.get_crt_name()?;
let key_type = crt.get_key_type()?; let key_type = crt.get_key_type()?;
@ -155,7 +149,6 @@ impl MainEventLoop {
Ok(MainEventLoop { Ok(MainEventLoop {
certs, certs,
root_certs: root_certs.iter().map(|v| (*v).to_string()).collect(),
accounts: accounts accounts: accounts
.iter() .iter()
.map(|(k, v)| (k.to_owned(), Arc::new(RwLock::new(v.to_owned())))) .map(|(k, v)| (k.to_owned(), Arc::new(RwLock::new(v.to_owned()))))
@ -194,13 +187,12 @@ impl MainEventLoop {
} }
let mut accounts_lock = self.accounts.clone(); let mut accounts_lock = self.accounts.clone();
let ep_lock = endpoint_lock.clone(); let ep_lock = endpoint_lock.clone();
let rc = self.root_certs.clone();
let handle = thread::spawn(move || { let handle = thread::spawn(move || {
let mut endpoint = ep_lock.write().unwrap(); let mut endpoint = ep_lock.write().unwrap();
for crt in certs_to_renew { for crt in certs_to_renew {
if let Some(acc_lock) = accounts_lock.get_mut(&crt.account_name) { if let Some(acc_lock) = accounts_lock.get_mut(&crt.account_name) {
let mut account = acc_lock.write().unwrap(); let mut account = acc_lock.write().unwrap();
renew_certificate(&crt, &rc, &mut endpoint, &mut account);
renew_certificate(&crt, &mut endpoint, &mut account);
}; };
} }
}); });

4
man/en/acmed.toml.5

@ -68,6 +68,8 @@ for more details.
Period of time between the certificate renewal and its expiration date. The format is described in the Period of time between the certificate renewal and its expiration date. The format is described in the
.Sx TIME PERIODS .Sx TIME PERIODS
section. Default is 3w. section. Default is 3w.
.It Cm root_certificates Ar array
Array containing the path to root certificates that should be added to the trust store.
.El .El
.It Ic rate-limit .It Ic rate-limit
Array of table where each element defines a HTTPS rate limit. Array of table where each element defines a HTTPS rate limit.
@ -99,6 +101,8 @@ The endpoint's directory URL.
Period of time between the certificate renewal and its expiration date. The format is described in the Period of time between the certificate renewal and its expiration date. The format is described in the
.Sx TIME PERIODS .Sx TIME PERIODS
section. Default is the value defined in the global section. section. Default is the value defined in the global section.
.It Cm root_certificates Ar array
Array containing the path to root certificates that should be added to the trust store.
.El .El
.It Ic hook .It Ic hook
Array of table where each element defines a command that will be launched at a defined point. See section Array of table where each element defines a command that will be launched at a defined point. See section

Loading…
Cancel
Save