From b288f2b32ce2ac423c01f6b82cf77a932d4b3b01 Mon Sep 17 00:00:00 2001 From: Rodolphe Breard Date: Sun, 31 May 2020 14:00:26 +0200 Subject: [PATCH] Add an independent endpoint structure Having the data relative to the endpoint in the certificate structure makes no sense. There is no way to share it across certificates which is sometimes needed. Also, having the other part of the endpoint data (mostly nonce and rate limit) in different places makes it difficult to maintain. Hence, the endpoint structure has been created. For now it is quite simple and does not handle every aspects of the endpoint, but this will be implemented in the future. --- acmed/Cargo.toml | 1 + acmed/src/acme_proto.rs | 7 ++--- acmed/src/acme_proto/account.rs | 4 ++- acmed/src/acme_proto/structs/account.rs | 5 ++-- acmed/src/certificate.rs | 3 +-- acmed/src/config.rs | 35 ++++++++----------------- acmed/src/endpoint.rs | 6 +++++ acmed/src/main.rs | 3 ++- acmed/src/main_event_loop.rs | 31 +++++++++++++++------- 9 files changed, 53 insertions(+), 42 deletions(-) create mode 100644 acmed/src/endpoint.rs diff --git a/acmed/Cargo.toml b/acmed/Cargo.toml index a13461d..092268f 100644 --- a/acmed/Cargo.toml +++ b/acmed/Cargo.toml @@ -20,6 +20,7 @@ http_req = "0.5" log = "0.4" nom = "5.0" openssl-sys = "0.9" +reqwest = { version = "0.10", features = ["blocking"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" toml = "0.5" diff --git a/acmed/src/acme_proto.rs b/acmed/src/acme_proto.rs index 6e98341..992ac66 100644 --- a/acmed/src/acme_proto.rs +++ b/acmed/src/acme_proto.rs @@ -3,6 +3,7 @@ use crate::acme_proto::structs::{ ApiError, Authorization, AuthorizationStatus, NewOrder, Order, OrderStatus, }; use crate::certificate::Certificate; +use crate::endpoint::Endpoint; use crate::jws::encode_kid; use crate::storage; use acme_common::crypto::Csr; @@ -66,7 +67,7 @@ macro_rules! set_empty_data_builder { }; } -pub fn request_certificate(cert: &Certificate, root_certs: &[String]) -> Result<(), Error> { +pub fn request_certificate(cert: &Certificate, root_certs: &[String], endpoint: &mut Endpoint) -> Result<(), Error> { let domains = cert .domains .iter() @@ -75,13 +76,13 @@ pub fn request_certificate(cert: &Certificate, root_certs: &[String]) -> Result< let mut hook_datas = vec![]; // 1. Get the directory - let directory = http::get_directory(cert, root_certs, &cert.remote_url)?; + let directory = http::get_directory(cert, root_certs, &endpoint.url)?; // 2. Get a first nonce let nonce = http::get_nonce(cert, root_certs, &directory.new_nonce)?; // 3. Get or create the account - let (account, nonce) = AccountManager::new(cert, &directory, &nonce, root_certs)?; + let (account, nonce) = AccountManager::new(cert, &directory, endpoint, &nonce, root_certs)?; // 4. Create a new order let new_order = NewOrder::new(&domains); diff --git a/acmed/src/acme_proto/account.rs b/acmed/src/acme_proto/account.rs index 4068264..0c9abbf 100644 --- a/acmed/src/acme_proto/account.rs +++ b/acmed/src/acme_proto/account.rs @@ -1,6 +1,7 @@ use crate::acme_proto::http; use crate::acme_proto::structs::{Account, AccountResponse, Directory}; use crate::certificate::Certificate; +use crate::endpoint::Endpoint; use crate::jws::algorithms::SignatureAlgorithm; use crate::jws::encode_jwk; use crate::storage; @@ -18,12 +19,13 @@ impl AccountManager { pub fn new( cert: &Certificate, directory: &Directory, + endpoint: &Endpoint, nonce: &str, root_certs: &[String], ) -> Result<(Self, String), Error> { // TODO: store the key id (account url) let key_pair = storage::get_account_keypair(cert)?; - let account = Account::new(cert); + let account = Account::new(cert, endpoint); let account = serde_json::to_string(&account)?; let data_builder = |n: &str| encode_jwk(&key_pair, account.as_bytes(), &directory.new_account, n); diff --git a/acmed/src/acme_proto/structs/account.rs b/acmed/src/acme_proto/structs/account.rs index 841e7a9..a869177 100644 --- a/acmed/src/acme_proto/structs/account.rs +++ b/acmed/src/acme_proto/structs/account.rs @@ -1,4 +1,5 @@ use crate::certificate::Certificate; +use crate::endpoint::Endpoint; use acme_common::error::Error; use serde::{Deserialize, Serialize}; use std::str::FromStr; @@ -12,10 +13,10 @@ pub struct Account { } impl Account { - pub fn new(cert: &Certificate) -> Self { + pub fn new(cert: &Certificate, endpoint: &Endpoint) -> Self { Account { contact: vec![format!("mailto:{}", cert.account.email)], - terms_of_service_agreed: cert.tos_agreed, + terms_of_service_agreed: endpoint.tos_agreed, only_return_existing: false, } } diff --git a/acmed/src/certificate.rs b/acmed/src/certificate.rs index 09e78c7..de3d1e9 100644 --- a/acmed/src/certificate.rs +++ b/acmed/src/certificate.rs @@ -47,8 +47,7 @@ pub struct Certificate { pub domains: Vec, pub algo: Algorithm, pub kp_reuse: bool, - pub remote_url: String, - pub tos_agreed: bool, + pub endpoint_name: String, pub hooks: Vec, pub account_directory: String, pub crt_directory: String, diff --git a/acmed/src/config.rs b/acmed/src/config.rs index e76273c..f436c53 100644 --- a/acmed/src/config.rs +++ b/acmed/src/config.rs @@ -184,13 +184,14 @@ pub struct Endpoint { } impl Endpoint { - fn is_used(&self, config: &Config) -> bool { - for crt in config.certificate.iter() { - if crt.endpoint == self.name { - return true; - } - } - false + fn to_generic(&self, _cnf: &Config) -> Result { + // TODO: include rate limits using `cnf.get_rate_limit()` + let ep = crate::endpoint::Endpoint { + name: self.name.to_owned(), + url: self.url.to_owned(), + tos_agreed: self.tos_agreed, + }; + Ok(ep) } } @@ -334,30 +335,16 @@ impl Certificate { crt_directory.to_string() } - fn get_endpoint(&self, cnf: &Config) -> Result { + pub fn get_endpoint(&self, cnf: &Config) -> Result { for endpoint in cnf.endpoint.iter() { if endpoint.name == self.endpoint { - return Ok(endpoint.clone()); + let ep = endpoint.to_generic(cnf)?; + return Ok(ep); } } Err(format!("{}: unknown endpoint.", self.endpoint).into()) } - pub fn get_remote_url(&self, cnf: &Config) -> Result { - let ep = self.get_endpoint(cnf)?; - Ok(ep.url) - } - - pub fn get_endpoint_name(&self, cnf: &Config) -> Result { - let ep = self.get_endpoint(cnf)?; - Ok(ep.name) - } - - pub fn get_tos_agreement(&self, cnf: &Config) -> Result { - let ep = self.get_endpoint(cnf)?; - Ok(ep.tos_agreed) - } - pub fn get_hooks(&self, cnf: &Config) -> Result, Error> { let mut res = vec![]; for name in self.hooks.iter() { diff --git a/acmed/src/endpoint.rs b/acmed/src/endpoint.rs new file mode 100644 index 0000000..9d8ef4a --- /dev/null +++ b/acmed/src/endpoint.rs @@ -0,0 +1,6 @@ +pub struct Endpoint { + pub name: String, + pub url: String, + pub tos_agreed: bool, + // TODO: rate limits +} diff --git a/acmed/src/main.rs b/acmed/src/main.rs index 3ea922c..9f7e4d1 100644 --- a/acmed/src/main.rs +++ b/acmed/src/main.rs @@ -6,6 +6,7 @@ use log::error; mod acme_proto; mod certificate; mod config; +mod endpoint; mod hooks; mod jws; mod main_event_loop; @@ -120,7 +121,7 @@ fn main() { ); let config_file = matches.value_of("config").unwrap_or(DEFAULT_CONFIG_FILE); - let srv = match MainEventLoop::new(&config_file, &root_certs) { + let mut srv = match MainEventLoop::new(&config_file, &root_certs) { Ok(s) => s, Err(e) => { error!("{}", e); diff --git a/acmed/src/main_event_loop.rs b/acmed/src/main_event_loop.rs index a9a312f..b8f1bc4 100644 --- a/acmed/src/main_event_loop.rs +++ b/acmed/src/main_event_loop.rs @@ -2,12 +2,14 @@ use crate::acme_proto::account::init_account; use crate::acme_proto::request_certificate; use crate::certificate::Certificate; use crate::config; +use crate::endpoint::Endpoint; use acme_common::error::Error; +use std::collections::HashMap; use std::thread; use std::time::Duration; -fn renew_certificate(crt: &Certificate, root_certs: &[String]) { - let (status, is_success) = match request_certificate(crt, root_certs) { +fn renew_certificate(crt: &Certificate, root_certs: &[String], endpoint: &mut Endpoint) { + let (status, is_success) = match request_certificate(crt, root_certs, endpoint) { Ok(_) => ("Success.".to_string(), true), Err(e) => { let e = e.prefix("Unable to renew the certificate"); @@ -27,6 +29,7 @@ fn renew_certificate(crt: &Certificate, root_certs: &[String]) { pub struct MainEventLoop { certs: Vec, root_certs: Vec, + endpoints: HashMap, } impl MainEventLoop { @@ -34,15 +37,15 @@ impl MainEventLoop { let cnf = config::from_file(config_file)?; let mut certs = Vec::new(); + let mut endpoints = HashMap::new(); for (i, crt) in cnf.certificate.iter().enumerate() { - let ep_name = crt.get_endpoint_name(&cnf)?; + let endpoint = crt.get_endpoint(&cnf)?; let cert = Certificate { account: crt.get_account(&cnf)?, domains: crt.get_domains()?, algo: crt.get_algorithm()?, kp_reuse: crt.get_kp_reuse(), - remote_url: crt.get_remote_url(&cnf)?, - tos_agreed: crt.get_tos_agreement(&cnf)?, + endpoint_name: endpoint.name.clone(), hooks: crt.get_hooks(&cnf)?, account_directory: cnf.get_account_dir(), crt_directory: crt.get_crt_dir(&cnf), @@ -57,6 +60,9 @@ impl MainEventLoop { env: crt.env.to_owned(), id: i + 1, }; + if ! endpoints.contains_key(&endpoint.name) { + endpoints.insert(endpoint.name.clone(), endpoint); + } init_account(&cert)?; certs.push(cert); } @@ -64,23 +70,30 @@ impl MainEventLoop { Ok(MainEventLoop { certs, root_certs: root_certs.iter().map(|v| (*v).to_string()).collect(), + endpoints, }) } - pub fn run(&self) { + pub fn run(&mut self) { loop { self.renew_certificates(); thread::sleep(Duration::from_secs(crate::DEFAULT_SLEEP_TIME)); } } - fn renew_certificates(&self) { + fn renew_certificates(&mut self) { for crt in self.certs.iter() { match crt.should_renew() { Ok(true) => { let root_certs = self.root_certs.clone(); - let cert = (*crt).clone(); - renew_certificate(&cert, &root_certs); + match self.endpoints.get_mut(&crt.endpoint_name) { + Some(mut endpoint) => { + renew_certificate(&crt, &root_certs, &mut endpoint); + }, + None => { + crt.warn(&format!("{}: Endpoint not found", &crt.endpoint_name)); + } + }; } Ok(false) => {} Err(e) => {