Browse Source

Create a dedicated FileManager struct

The certificate struct was bloated with file management data which
therefore required the certificate to be passed in every storage
function. In order to clean this, a new FileManager struct has been
created.
pull/39/head
Rodolphe Breard 4 years ago
parent
commit
69739d4703
  1. 2
      acmed/src/account.rs
  2. 3
      acmed/src/acme_proto.rs
  3. 11
      acmed/src/acme_proto/account.rs
  4. 4
      acmed/src/acme_proto/certificate.rs
  5. 35
      acmed/src/certificate.rs
  6. 16
      acmed/src/config.rs
  7. 42
      acmed/src/hooks.rs
  8. 6
      acmed/src/logs.rs
  9. 1
      acmed/src/main.rs
  10. 61
      acmed/src/main_event_loop.rs
  11. 151
      acmed/src/storage.rs

2
acmed/src/account.rs

@ -1,3 +1,4 @@
use crate::storage::FileManager;
use acme_common::crypto::{JwsSignatureAlgorithm, KeyType}; use acme_common::crypto::{JwsSignatureAlgorithm, KeyType};
use acme_common::error::Error; use acme_common::error::Error;
use std::str::FromStr; use std::str::FromStr;
@ -12,6 +13,7 @@ pub struct Account {
impl Account { impl Account {
pub fn new( pub fn new(
_file_manager: &FileManager,
name: &str, name: &str,
email: &str, email: &str,
key_type: &Option<String>, key_type: &Option<String>,

3
acmed/src/acme_proto.rs

@ -6,6 +6,7 @@ use crate::certificate::Certificate;
use crate::endpoint::Endpoint; use crate::endpoint::Endpoint;
use crate::identifier::IdentifierType; use crate::identifier::IdentifierType;
use crate::jws::encode_kid; use crate::jws::encode_kid;
use crate::logs::HasLogger;
use crate::storage; use crate::storage;
use acme_common::crypto::Csr; use acme_common::crypto::Csr;
use acme_common::error::Error; use acme_common::error::Error;
@ -199,7 +200,7 @@ pub fn request_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); let data_builder = set_empty_data_builder!(account);
let crt = http::get_certificate(endpoint, root_certs, &data_builder, &crt_url)?; let crt = http::get_certificate(endpoint, root_certs, &data_builder, &crt_url)?;
storage::write_certificate(cert, &crt.as_bytes())?;
storage::write_certificate(&cert.file_manager, &crt.as_bytes())?;
cert.info(&format!( cert.info(&format!(
"Certificate renewed (identifiers: {})", "Certificate renewed (identifiers: {})",

11
acmed/src/acme_proto/account.rs

@ -3,6 +3,7 @@ use crate::acme_proto::structs::Account;
use crate::certificate::Certificate; use crate::certificate::Certificate;
use crate::endpoint::Endpoint; use crate::endpoint::Endpoint;
use crate::jws::encode_jwk; use crate::jws::encode_jwk;
use crate::logs::HasLogger;
use crate::storage; use crate::storage;
use acme_common::crypto::{gen_keypair, JwsSignatureAlgorithm, KeyPair}; use acme_common::crypto::{gen_keypair, JwsSignatureAlgorithm, KeyPair};
use acme_common::error::Error; use acme_common::error::Error;
@ -21,7 +22,7 @@ impl AccountManager {
cert: &Certificate, cert: &Certificate,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
// TODO: store the key id (account url) // TODO: store the key id (account url)
let key_pair = storage::get_account_keypair(cert)?;
let key_pair = storage::get_account_keypair(&cert.file_manager)?;
let signature_algorithm = cert.account.signature_algorithm; let signature_algorithm = cert.account.signature_algorithm;
let kp_ref = &key_pair; let kp_ref = &key_pair;
let account = Account::new(cert, endpoint); let account = Account::new(cert, endpoint);
@ -43,21 +44,21 @@ impl AccountManager {
} }
pub fn init_account(cert: &Certificate) -> Result<(), Error> { pub fn init_account(cert: &Certificate) -> Result<(), Error> {
if !storage::account_files_exists(cert) {
if !storage::account_files_exists(&cert.file_manager) {
cert.info(&format!( cert.info(&format!(
"Account {} does not exists. Creating it.", "Account {} does not exists. Creating it.",
&cert.account.name &cert.account.name
)); ));
let key_pair = gen_keypair(cert.account.key_type)?; let key_pair = gen_keypair(cert.account.key_type)?;
storage::set_account_keypair(cert, &key_pair)?;
storage::set_account_keypair(&cert.file_manager, &key_pair)?;
cert.debug(&format!("Account {} created.", &cert.account.name)); cert.debug(&format!("Account {} created.", &cert.account.name));
} else { } else {
let key_pair = storage::get_account_keypair(cert)?;
let key_pair = storage::get_account_keypair(&cert.file_manager)?;
if key_pair.key_type != cert.account.key_type { if key_pair.key_type != cert.account.key_type {
cert.info(&format!("Account {name} has a key pair of type {kt_has} while {kt_want} was expected. Creating a new {kt_want} key pair.", name=&cert.account.name, kt_has=key_pair.key_type, kt_want=cert.account.key_type)); cert.info(&format!("Account {name} has a key pair of type {kt_has} while {kt_want} was expected. Creating a new {kt_want} key pair.", name=&cert.account.name, kt_has=key_pair.key_type, kt_want=cert.account.key_type));
// TODO: Do a propper key rollover // TODO: Do a propper key rollover
let key_pair = gen_keypair(cert.account.key_type)?; let key_pair = gen_keypair(cert.account.key_type)?;
storage::set_account_keypair(cert, &key_pair)?;
storage::set_account_keypair(&cert.file_manager, &key_pair)?;
cert.debug(&format!( cert.debug(&format!(
"Account {} updated with a new {} key pair.", "Account {} updated with a new {} key pair.",
&cert.account.name, cert.account.key_type &cert.account.name, cert.account.key_type

4
acmed/src/acme_proto/certificate.rs

@ -5,12 +5,12 @@ use acme_common::error::Error;
fn gen_key_pair(cert: &Certificate) -> Result<KeyPair, Error> { fn gen_key_pair(cert: &Certificate) -> Result<KeyPair, Error> {
let key_pair = gen_keypair(cert.key_type)?; let key_pair = gen_keypair(cert.key_type)?;
storage::set_keypair(cert, &key_pair)?;
storage::set_keypair(&cert.file_manager, &key_pair)?;
Ok(key_pair) Ok(key_pair)
} }
fn read_key_pair(cert: &Certificate) -> Result<KeyPair, Error> { fn read_key_pair(cert: &Certificate) -> Result<KeyPair, Error> {
storage::get_keypair(cert)
storage::get_keypair(&cert.file_manager)
} }
pub fn get_key_pair(cert: &Certificate) -> Result<KeyPair, Error> { pub fn get_key_pair(cert: &Certificate) -> Result<KeyPair, Error> {

35
acmed/src/certificate.rs

@ -2,7 +2,8 @@ use crate::account::Account;
use crate::acme_proto::Challenge; use crate::acme_proto::Challenge;
use crate::hooks::{self, ChallengeHookData, Hook, HookEnvData, HookType, PostOperationHookData}; use crate::hooks::{self, ChallengeHookData, Hook, HookEnvData, HookType, PostOperationHookData};
use crate::identifier::{Identifier, IdentifierType}; use crate::identifier::{Identifier, IdentifierType};
use crate::storage::{certificate_files_exists, get_certificate};
use crate::logs::HasLogger;
use crate::storage::{certificate_files_exists, get_certificate, FileManager};
use acme_common::crypto::{HashFunction, KeyType, X509Certificate}; use acme_common::crypto::{HashFunction, KeyType, X509Certificate};
use acme_common::error::Error; use acme_common::error::Error;
use log::{debug, info, trace, warn}; use log::{debug, info, trace, warn};
@ -19,19 +20,11 @@ pub struct Certificate {
pub kp_reuse: bool, pub kp_reuse: bool,
pub endpoint_name: String, pub endpoint_name: String,
pub hooks: Vec<Hook>, pub hooks: Vec<Hook>,
pub account_directory: String,
pub crt_directory: String,
pub crt_name: String, pub crt_name: String,
pub crt_name_format: String,
pub cert_file_mode: u32,
pub cert_file_owner: Option<String>,
pub cert_file_group: Option<String>,
pub pk_file_mode: u32,
pub pk_file_owner: Option<String>,
pub pk_file_group: Option<String>,
pub env: HashMap<String, String>, pub env: HashMap<String, String>,
pub id: usize, pub id: usize,
pub renew_delay: Duration, pub renew_delay: Duration,
pub file_manager: FileManager,
} }
impl fmt::Display for Certificate { impl fmt::Display for Certificate {
@ -41,23 +34,25 @@ impl fmt::Display for Certificate {
} }
} }
impl Certificate {
pub fn warn(&self, msg: &str) {
impl HasLogger for Certificate {
fn warn(&self, msg: &str) {
warn!("{}: {}", &self, msg); warn!("{}: {}", &self, msg);
} }
pub fn info(&self, msg: &str) {
fn info(&self, msg: &str) {
info!("{}: {}", &self, msg); info!("{}: {}", &self, msg);
} }
pub fn debug(&self, msg: &str) {
fn debug(&self, msg: &str) {
debug!("{}: {}", &self, msg); debug!("{}: {}", &self, msg);
} }
pub fn trace(&self, msg: &str) {
fn trace(&self, msg: &str) {
trace!("{}: {}", &self, msg); trace!("{}: {}", &self, msg);
} }
}
impl Certificate {
pub fn get_identifier_from_str(&self, identifier: &str) -> Result<Identifier, Error> { pub fn get_identifier_from_str(&self, identifier: &str) -> Result<Identifier, Error> {
let identifier = identifier.to_string(); let identifier = identifier.to_string();
for d in self.identifiers.iter() { for d in self.identifiers.iter() {
@ -119,11 +114,11 @@ impl Certificate {
"Checking for renewal (identifiers: {})", "Checking for renewal (identifiers: {})",
self.identifier_list() self.identifier_list()
)); ));
if !certificate_files_exists(&self) {
if !certificate_files_exists(&self.file_manager) {
self.debug("certificate does not exist: requesting one"); self.debug("certificate does not exist: requesting one");
return Ok(true); return Ok(true);
} }
let cert = get_certificate(&self)?;
let cert = get_certificate(&self.file_manager)?;
let renew_ident = self.has_missing_identifiers(&cert); let renew_ident = self.has_missing_identifiers(&cert);
if renew_ident { if renew_ident {
@ -169,7 +164,7 @@ impl Certificate {
HookType::ChallengeTlsAlpn01Clean, HookType::ChallengeTlsAlpn01Clean,
), ),
}; };
hooks::call(self, &hook_data, hook_type.0)?;
hooks::call(self, &self.hooks, &hook_data, hook_type.0)?;
Ok((hook_data, hook_type.1)) Ok((hook_data, hook_type.1))
} }
@ -178,7 +173,7 @@ impl Certificate {
data: &ChallengeHookData, data: &ChallengeHookData,
hook_type: HookType, hook_type: HookType,
) -> Result<(), Error> { ) -> Result<(), Error> {
hooks::call(self, data, hook_type)
hooks::call(self, &self.hooks, data, hook_type)
} }
pub fn call_post_operation_hooks(&self, status: &str, is_success: bool) -> Result<(), Error> { pub fn call_post_operation_hooks(&self, status: &str, is_success: bool) -> Result<(), Error> {
@ -195,7 +190,7 @@ impl Certificate {
env: HashMap::new(), env: HashMap::new(),
}; };
hook_data.set_env(&self.env); hook_data.set_env(&self.env);
hooks::call(self, &hook_data, HookType::PostOperation)?;
hooks::call(self, &self.hooks, &hook_data, HookType::PostOperation)?;
Ok(()) Ok(())
} }
} }

16
acmed/src/config.rs

@ -1,6 +1,7 @@
use crate::duration::parse_duration; use crate::duration::parse_duration;
use crate::hooks; use crate::hooks;
use crate::identifier::IdentifierType; use crate::identifier::IdentifierType;
use crate::storage::FileManager;
use acme_common::crypto::{HashFunction, KeyType}; use acme_common::crypto::{HashFunction, KeyType};
use acme_common::error::Error; use acme_common::error::Error;
use glob::glob; use glob::glob;
@ -87,7 +88,7 @@ impl Config {
if name == hook.name { if name == hook.name {
let h = hooks::Hook { let h = hooks::Hook {
name: hook.name.to_owned(), name: hook.name.to_owned(),
hook_type: hook.hook_type.to_owned(),
hook_type: hook.hook_type.iter().map(|e| e.to_owned()).collect(),
cmd: hook.cmd.to_owned(), cmd: hook.cmd.to_owned(),
args: hook.args.to_owned(), args: hook.args.to_owned(),
stdin: get_stdin(&hook)?, stdin: get_stdin(&hook)?,
@ -242,7 +243,7 @@ pub struct Hook {
pub allow_failure: Option<bool>, pub allow_failure: Option<bool>,
} }
#[derive(Clone, Debug, Eq, PartialEq, Deserialize)]
#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub enum HookType { pub enum HookType {
FilePreCreate, FilePreCreate,
@ -281,8 +282,9 @@ pub struct Account {
} }
impl Account { impl Account {
pub fn to_generic(&self) -> Result<crate::account::Account, Error> {
pub fn to_generic(&self, file_manager: &FileManager) -> Result<crate::account::Account, Error> {
crate::account::Account::new( crate::account::Account::new(
file_manager,
&self.name, &self.name,
&self.email, &self.email,
&self.key_type, &self.key_type,
@ -311,10 +313,14 @@ pub struct Certificate {
} }
impl Certificate { impl Certificate {
pub fn get_account(&self, cnf: &Config) -> Result<crate::account::Account, Error> {
pub fn get_account(
&self,
cnf: &Config,
file_manager: &FileManager,
) -> Result<crate::account::Account, Error> {
for account in cnf.account.iter() { for account in cnf.account.iter() {
if account.name == self.account { if account.name == self.account {
let acc = account.to_generic()?;
let acc = account.to_generic(file_manager)?;
return Ok(acc); return Ok(acc);
} }
} }

42
acmed/src/hooks.rs

@ -1,10 +1,10 @@
use crate::certificate::Certificate;
pub use crate::config::HookType; pub use crate::config::HookType;
use crate::logs::HasLogger;
use acme_common::error::Error; use acme_common::error::Error;
use handlebars::Handlebars; use handlebars::Handlebars;
use serde::Serialize; use serde::Serialize;
use std::collections::hash_map::Iter; use std::collections::hash_map::Iter;
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::fs::File; use std::fs::File;
use std::io::prelude::*; use std::io::prelude::*;
use std::io::BufReader; use std::io::BufReader;
@ -86,7 +86,7 @@ pub enum HookStdin {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Hook { pub struct Hook {
pub name: String, pub name: String,
pub hook_type: Vec<HookType>,
pub hook_type: HashSet<HookType>,
pub cmd: String, pub cmd: String,
pub args: Option<Vec<String>>, pub args: Option<Vec<String>>,
pub stdin: HookStdin, pub stdin: HookStdin,
@ -102,11 +102,11 @@ impl fmt::Display for Hook {
} }
macro_rules! get_hook_output { macro_rules! get_hook_output {
($cert: expr, $out: expr, $reg: ident, $data: expr, $hook_name: expr, $out_name: expr) => {{
($logger: expr, $out: expr, $reg: ident, $data: expr, $hook_name: expr, $out_name: expr) => {{
match $out { match $out {
Some(path) => { Some(path) => {
let path = $reg.render_template(path, $data)?; let path = $reg.render_template(path, $data)?;
$cert.trace(&format!("Hook {}: {}: {}", $hook_name, $out_name, &path));
$logger.trace(&format!("Hook {}: {}: {}", $hook_name, $out_name, &path));
let file = File::create(&path)?; let file = File::create(&path)?;
Stdio::from(file) Stdio::from(file)
} }
@ -115,11 +115,12 @@ macro_rules! get_hook_output {
}}; }};
} }
fn call_single<T>(cert: &Certificate, data: &T, hook: &Hook) -> Result<(), Error>
fn call_single<L, T>(logger: &L, data: &T, hook: &Hook) -> Result<(), Error>
where where
L: HasLogger,
T: Clone + HookEnvData + Serialize, T: Clone + HookEnvData + Serialize,
{ {
cert.debug(&format!("Calling hook: {}", hook.name));
logger.debug(&format!("Calling hook: {}", hook.name));
let reg = Handlebars::new(); let reg = Handlebars::new();
let mut v = vec![]; let mut v = vec![];
let args = match &hook.args { let args = match &hook.args {
@ -132,13 +133,13 @@ where
} }
None => &[], None => &[],
}; };
cert.trace(&format!("Hook {}: cmd: {}", hook.name, hook.cmd));
cert.trace(&format!("Hook {}: args: {:?}", hook.name, args));
logger.trace(&format!("Hook {}: cmd: {}", hook.name, hook.cmd));
logger.trace(&format!("Hook {}: args: {:?}", hook.name, args));
let mut cmd = Command::new(&hook.cmd) let mut cmd = Command::new(&hook.cmd)
.envs(data.get_env()) .envs(data.get_env())
.args(args) .args(args)
.stdout(get_hook_output!( .stdout(get_hook_output!(
cert,
logger,
&hook.stdout, &hook.stdout,
reg, reg,
&data, &data,
@ -146,7 +147,7 @@ where
"stdout" "stdout"
)) ))
.stderr(get_hook_output!( .stderr(get_hook_output!(
cert,
logger,
&hook.stderr, &hook.stderr,
reg, reg,
&data, &data,
@ -161,13 +162,13 @@ where
match &hook.stdin { match &hook.stdin {
HookStdin::Str(s) => { HookStdin::Str(s) => {
let data_in = reg.render_template(&s, &data)?; let data_in = reg.render_template(&s, &data)?;
cert.trace(&format!("Hook {}: string stdin: {}", hook.name, &data_in));
logger.trace(&format!("Hook {}: string stdin: {}", hook.name, &data_in));
let stdin = cmd.stdin.as_mut().ok_or("stdin not found")?; let stdin = cmd.stdin.as_mut().ok_or("stdin not found")?;
stdin.write_all(data_in.as_bytes())?; stdin.write_all(data_in.as_bytes())?;
} }
HookStdin::File(f) => { HookStdin::File(f) => {
let file_name = reg.render_template(&f, &data)?; let file_name = reg.render_template(&f, &data)?;
cert.trace(&format!("Hook {}: file stdin: {}", hook.name, &file_name));
logger.trace(&format!("Hook {}: file stdin: {}", hook.name, &file_name));
let stdin = cmd.stdin.as_mut().ok_or("stdin not found")?; let stdin = cmd.stdin.as_mut().ok_or("stdin not found")?;
let file = File::open(&file_name)?; let file = File::open(&file_name)?;
let buf_reader = BufReader::new(file); let buf_reader = BufReader::new(file);
@ -188,22 +189,19 @@ where
return Err(msg); return Err(msg);
} }
match status.code() { match status.code() {
Some(code) => cert.debug(&format!("Hook {}: exited: code {}", hook.name, code)),
None => cert.debug(&format!("Hook {}: exited", hook.name)),
Some(code) => logger.debug(&format!("Hook {}: exited: code {}", hook.name, code)),
None => logger.debug(&format!("Hook {}: exited", hook.name)),
}; };
Ok(()) Ok(())
} }
pub fn call<T>(cert: &Certificate, data: &T, hook_type: HookType) -> Result<(), Error>
pub fn call<L, T>(logger: &L, hooks: &[Hook], data: &T, hook_type: HookType) -> Result<(), Error>
where where
L: HasLogger,
T: Clone + HookEnvData + Serialize, T: Clone + HookEnvData + Serialize,
{ {
for hook in cert
.hooks
.iter()
.filter(|h| h.hook_type.contains(&hook_type))
{
call_single(cert, data, &hook).map_err(|e| e.prefix(&hook.name))?;
for hook in hooks.iter().filter(|h| h.hook_type.contains(&hook_type)) {
call_single(logger, data, &hook).map_err(|e| e.prefix(&hook.name))?;
} }
Ok(()) Ok(())
} }

6
acmed/src/logs.rs

@ -0,0 +1,6 @@
pub trait HasLogger {
fn warn(&self, msg: &str);
fn info(&self, msg: &str);
fn debug(&self, msg: &str);
fn trace(&self, msg: &str);
}

1
acmed/src/main.rs

@ -15,6 +15,7 @@ mod hooks;
mod http; mod http;
mod identifier; mod identifier;
mod jws; mod jws;
mod logs;
mod main_event_loop; mod main_event_loop;
mod storage; mod storage;

61
acmed/src/main_event_loop.rs

@ -3,6 +3,9 @@ use crate::acme_proto::request_certificate;
use crate::certificate::Certificate; use crate::certificate::Certificate;
use crate::config; use crate::config;
use crate::endpoint::Endpoint; use crate::endpoint::Endpoint;
use crate::hooks::HookType;
use crate::logs::HasLogger;
use crate::storage::FileManager;
use acme_common::error::Error; use acme_common::error::Error;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
@ -38,33 +41,71 @@ pub struct MainEventLoop {
impl MainEventLoop { impl MainEventLoop {
pub fn new(config_file: &str, root_certs: &[&str]) -> Result<Self, Error> { pub fn new(config_file: &str, root_certs: &[&str]) -> Result<Self, Error> {
let cnf = config::from_file(config_file)?; let cnf = config::from_file(config_file)?;
let file_hooks = vec![
HookType::FilePreCreate,
HookType::FilePostCreate,
HookType::FilePreEdit,
HookType::FilePostEdit,
]
.into_iter()
.collect();
let cert_hooks = vec![
HookType::ChallengeHttp01,
HookType::ChallengeHttp01Clean,
HookType::ChallengeDns01,
HookType::ChallengeDns01Clean,
HookType::ChallengeTlsAlpn01,
HookType::ChallengeTlsAlpn01Clean,
HookType::PostOperation,
]
.into_iter()
.collect();
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)?;
let endpoint_name = endpoint.name.clone(); let endpoint_name = endpoint.name.clone();
let cert = Certificate {
account: crt.get_account(&cnf)?,
identifiers: crt.get_identifiers()?,
key_type: crt.get_key_type()?,
csr_digest: crt.get_csr_digest()?,
kp_reuse: crt.get_kp_reuse(),
endpoint_name: endpoint_name.clone(),
hooks: crt.get_hooks(&cnf)?,
let crt_name = crt.get_crt_name()?;
let key_type = crt.get_key_type()?;
let hooks = crt.get_hooks(&cnf)?;
let fm = FileManager {
account_directory: cnf.get_account_dir(), account_directory: cnf.get_account_dir(),
crt_directory: crt.get_crt_dir(&cnf),
crt_name: crt.get_crt_name()?,
account_name: crt.account.to_owned(),
crt_name: crt_name.clone(),
crt_name_format: crt.get_crt_name_format(), crt_name_format: crt.get_crt_name_format(),
crt_directory: crt.get_crt_dir(&cnf),
crt_key_type: key_type.to_string(),
cert_file_mode: cnf.get_cert_file_mode(), cert_file_mode: cnf.get_cert_file_mode(),
cert_file_owner: cnf.get_cert_file_user(), cert_file_owner: cnf.get_cert_file_user(),
cert_file_group: cnf.get_cert_file_group(), cert_file_group: cnf.get_cert_file_group(),
pk_file_mode: cnf.get_pk_file_mode(), pk_file_mode: cnf.get_pk_file_mode(),
pk_file_owner: cnf.get_pk_file_user(), pk_file_owner: cnf.get_pk_file_user(),
pk_file_group: cnf.get_pk_file_group(), pk_file_group: cnf.get_pk_file_group(),
hooks: hooks
.iter()
.filter(|h| !h.hook_type.is_disjoint(&file_hooks))
.map(|e| e.to_owned())
.collect(),
env: crt.env.clone(),
};
let cert = Certificate {
account: crt.get_account(&cnf, &fm)?,
identifiers: crt.get_identifiers()?,
key_type,
csr_digest: crt.get_csr_digest()?,
kp_reuse: crt.get_kp_reuse(),
endpoint_name: endpoint_name.clone(),
hooks: hooks
.iter()
.filter(|h| !h.hook_type.is_disjoint(&cert_hooks))
.map(|e| e.to_owned())
.collect(),
crt_name,
env: crt.env.to_owned(), env: crt.env.to_owned(),
id: i + 1, id: i + 1,
renew_delay: crt.get_renew_delay(&cnf)?, renew_delay: crt.get_renew_delay(&cnf)?,
file_manager: fm,
}; };
endpoints endpoints
.entry(endpoint_name) .entry(endpoint_name)

151
acmed/src/storage.rs

@ -1,5 +1,5 @@
use crate::certificate::Certificate;
use crate::hooks::{self, FileStorageHookData, HookEnvData, HookType};
use crate::hooks::{self, FileStorageHookData, Hook, HookEnvData, HookType};
use crate::logs::HasLogger;
use acme_common::b64_encode; use acme_common::b64_encode;
use acme_common::crypto::{KeyPair, X509Certificate}; use acme_common::crypto::{KeyPair, X509Certificate};
use acme_common::error::Error; use acme_common::error::Error;
@ -12,6 +12,42 @@ use std::path::PathBuf;
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
use std::os::unix::fs::OpenOptionsExt; use std::os::unix::fs::OpenOptionsExt;
#[derive(Clone, Debug)]
pub struct FileManager {
pub account_name: String,
pub account_directory: String,
pub crt_name: String,
pub crt_name_format: String,
pub crt_directory: String,
pub crt_key_type: String,
pub cert_file_mode: u32,
pub cert_file_owner: Option<String>,
pub cert_file_group: Option<String>,
pub pk_file_mode: u32,
pub pk_file_owner: Option<String>,
pub pk_file_group: Option<String>,
pub hooks: Vec<Hook>,
pub env: HashMap<String, String>,
}
impl HasLogger for FileManager {
fn warn(&self, msg: &str) {
log::warn!("{}: {}", &self.crt_name, msg);
}
fn info(&self, msg: &str) {
log::info!("{}: {}", &self.crt_name, msg);
}
fn debug(&self, msg: &str) {
log::debug!("{}: {}", &self.crt_name, msg);
}
fn trace(&self, msg: &str) {
log::trace!("{}: {}", &self.crt_name, msg);
}
}
#[derive(Clone)] #[derive(Clone)]
enum FileType { enum FileType {
AccountPrivateKey, AccountPrivateKey,
@ -33,27 +69,27 @@ impl fmt::Display for FileType {
} }
fn get_file_full_path( fn get_file_full_path(
cert: &Certificate,
fm: &FileManager,
file_type: FileType, file_type: FileType,
) -> Result<(String, String, PathBuf), Error> { ) -> Result<(String, String, PathBuf), Error> {
let base_path = match file_type { let base_path = match file_type {
FileType::AccountPrivateKey | FileType::AccountPublicKey => &cert.account_directory,
FileType::PrivateKey => &cert.crt_directory,
FileType::Certificate => &cert.crt_directory,
FileType::AccountPrivateKey | FileType::AccountPublicKey => &fm.account_directory,
FileType::PrivateKey => &fm.crt_directory,
FileType::Certificate => &fm.crt_directory,
}; };
let file_name = match file_type { let file_name = match file_type {
FileType::AccountPrivateKey | FileType::AccountPublicKey => format!( FileType::AccountPrivateKey | FileType::AccountPublicKey => format!(
"{account}.{file_type}.{ext}", "{account}.{file_type}.{ext}",
account = b64_encode(&cert.account.name),
account = b64_encode(&fm.account_name),
file_type = file_type.to_string(), file_type = file_type.to_string(),
ext = "pem" ext = "pem"
), ),
FileType::PrivateKey | FileType::Certificate => { FileType::PrivateKey | FileType::Certificate => {
// TODO: use cert.crt_name_format instead of a string literal
// TODO: use fm.crt_name_format instead of a string literal
format!( format!(
"{name}_{algo}.{file_type}.{ext}", "{name}_{algo}.{file_type}.{ext}",
name = cert.crt_name,
algo = cert.key_type.to_string(),
name = fm.crt_name,
algo = fm.crt_key_type,
file_type = file_type.to_string(), file_type = file_type.to_string(),
ext = "pem" ext = "pem"
) )
@ -64,13 +100,13 @@ fn get_file_full_path(
Ok((base_path.to_string(), file_name, path)) Ok((base_path.to_string(), file_name, path))
} }
fn get_file_path(cert: &Certificate, file_type: FileType) -> Result<PathBuf, Error> {
let (_, _, path) = get_file_full_path(cert, file_type)?;
fn get_file_path(fm: &FileManager, file_type: FileType) -> Result<PathBuf, Error> {
let (_, _, path) = get_file_full_path(fm, file_type)?;
Ok(path) Ok(path)
} }
fn read_file(cert: &Certificate, path: &PathBuf) -> Result<Vec<u8>, Error> {
cert.trace(&format!("Reading file {:?}", path));
fn read_file(fm: &FileManager, path: &PathBuf) -> Result<Vec<u8>, Error> {
fm.trace(&format!("Reading file {:?}", path));
let mut file = File::open(path)?; let mut file = File::open(path)?;
let mut contents = vec![]; let mut contents = vec![];
file.read_to_end(&mut contents)?; file.read_to_end(&mut contents)?;
@ -78,15 +114,12 @@ fn read_file(cert: &Certificate, path: &PathBuf) -> Result<Vec<u8>, Error> {
} }
#[cfg(unix)] #[cfg(unix)]
fn set_owner(cert: &Certificate, path: &PathBuf, file_type: FileType) -> Result<(), Error> {
fn set_owner(fm: &FileManager, path: &PathBuf, file_type: FileType) -> Result<(), Error> {
let (uid, gid) = match file_type { let (uid, gid) = match file_type {
FileType::Certificate => (
cert.cert_file_owner.to_owned(),
cert.cert_file_group.to_owned(),
),
FileType::PrivateKey => (cert.pk_file_owner.to_owned(), cert.pk_file_group.to_owned()),
FileType::Certificate => (fm.cert_file_owner.to_owned(), fm.cert_file_group.to_owned()),
FileType::PrivateKey => (fm.pk_file_owner.to_owned(), fm.pk_file_group.to_owned()),
FileType::AccountPrivateKey | FileType::AccountPublicKey => { FileType::AccountPrivateKey | FileType::AccountPublicKey => {
// The account private and public keys does not need to be accessible to users other different from the current one.
// The account file does not need to be accessible to users other different from the current one.
return Ok(()); return Ok(());
} }
}; };
@ -121,12 +154,12 @@ fn set_owner(cert: &Certificate, path: &PathBuf, file_type: FileType) -> Result<
None => None, None => None,
}; };
match uid { match uid {
Some(u) => cert.trace(&format!("{:?}: setting the uid to {}", path, u.as_raw())),
None => cert.trace(&format!("{:?}: uid unchanged", path)),
Some(u) => fm.trace(&format!("{:?}: setting the uid to {}", path, u.as_raw())),
None => fm.trace(&format!("{:?}: uid unchanged", path)),
}; };
match gid { match gid {
Some(g) => cert.trace(&format!("{:?}: setting the gid to {}", path, g.as_raw())),
None => cert.trace(&format!("{:?}: gid unchanged", path)),
Some(g) => fm.trace(&format!("{:?}: setting the gid to {}", path, g.as_raw())),
None => fm.trace(&format!("{:?}: gid unchanged", path)),
}; };
match nix::unistd::chown(path, uid, gid) { match nix::unistd::chown(path, uid, gid) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
@ -134,29 +167,29 @@ fn set_owner(cert: &Certificate, path: &PathBuf, file_type: FileType) -> Result<
} }
} }
fn write_file(cert: &Certificate, file_type: FileType, data: &[u8]) -> Result<(), Error> {
let (file_directory, file_name, path) = get_file_full_path(cert, file_type.clone())?;
fn write_file(fm: &FileManager, file_type: FileType, data: &[u8]) -> Result<(), Error> {
let (file_directory, file_name, path) = get_file_full_path(fm, file_type.clone())?;
let mut hook_data = FileStorageHookData { let mut hook_data = FileStorageHookData {
file_name, file_name,
file_directory, file_directory,
file_path: path.to_owned(), file_path: path.to_owned(),
env: HashMap::new(), env: HashMap::new(),
}; };
hook_data.set_env(&cert.env);
hook_data.set_env(&fm.env);
let is_new = !path.is_file(); let is_new = !path.is_file();
if is_new { if is_new {
hooks::call(cert, &hook_data, HookType::FilePreCreate)?;
hooks::call(fm, &fm.hooks, &hook_data, HookType::FilePreCreate)?;
} else { } else {
hooks::call(cert, &hook_data, HookType::FilePreEdit)?;
hooks::call(fm, &fm.hooks, &hook_data, HookType::FilePreEdit)?;
} }
cert.trace(&format!("Writing file {:?}", path));
fm.trace(&format!("Writing file {:?}", path));
let mut file = if cfg!(unix) { let mut file = if cfg!(unix) {
let mut options = OpenOptions::new(); let mut options = OpenOptions::new();
options.mode(match &file_type { options.mode(match &file_type {
FileType::Certificate => cert.cert_file_mode,
FileType::PrivateKey => cert.pk_file_mode,
FileType::Certificate => fm.cert_file_mode,
FileType::PrivateKey => fm.pk_file_mode,
FileType::AccountPublicKey => crate::DEFAULT_ACCOUNT_FILE_MODE, FileType::AccountPublicKey => crate::DEFAULT_ACCOUNT_FILE_MODE,
FileType::AccountPrivateKey => crate::DEFAULT_ACCOUNT_FILE_MODE, FileType::AccountPrivateKey => crate::DEFAULT_ACCOUNT_FILE_MODE,
}); });
@ -166,64 +199,64 @@ fn write_file(cert: &Certificate, file_type: FileType, data: &[u8]) -> Result<()
}; };
file.write_all(data)?; file.write_all(data)?;
if cfg!(unix) { if cfg!(unix) {
set_owner(cert, &path, file_type)?;
set_owner(fm, &path, file_type)?;
} }
if is_new { if is_new {
hooks::call(cert, &hook_data, HookType::FilePostCreate)?;
hooks::call(fm, &fm.hooks, &hook_data, HookType::FilePostCreate)?;
} else { } else {
hooks::call(cert, &hook_data, HookType::FilePostEdit)?;
hooks::call(fm, &fm.hooks, &hook_data, HookType::FilePostEdit)?;
} }
Ok(()) Ok(())
} }
pub fn get_account_keypair(cert: &Certificate) -> Result<KeyPair, Error> {
let path = get_file_path(cert, FileType::AccountPrivateKey)?;
let raw_key = read_file(cert, &path)?;
pub fn get_account_keypair(fm: &FileManager) -> Result<KeyPair, Error> {
let path = get_file_path(fm, FileType::AccountPrivateKey)?;
let raw_key = read_file(fm, &path)?;
let key = KeyPair::from_pem(&raw_key)?; let key = KeyPair::from_pem(&raw_key)?;
Ok(key) Ok(key)
} }
pub fn set_account_keypair(cert: &Certificate, key_pair: &KeyPair) -> Result<(), Error> {
pub fn set_account_keypair(fm: &FileManager, key_pair: &KeyPair) -> Result<(), Error> {
let pem_pub_key = key_pair.private_key_to_pem()?; let pem_pub_key = key_pair.private_key_to_pem()?;
let pem_priv_key = key_pair.public_key_to_pem()?; let pem_priv_key = key_pair.public_key_to_pem()?;
write_file(cert, FileType::AccountPublicKey, &pem_priv_key)?;
write_file(cert, FileType::AccountPrivateKey, &pem_pub_key)?;
write_file(fm, FileType::AccountPublicKey, &pem_priv_key)?;
write_file(fm, FileType::AccountPrivateKey, &pem_pub_key)?;
Ok(()) Ok(())
} }
pub fn get_keypair(cert: &Certificate) -> Result<KeyPair, Error> {
let path = get_file_path(cert, FileType::PrivateKey)?;
let raw_key = read_file(cert, &path)?;
pub fn get_keypair(fm: &FileManager) -> Result<KeyPair, Error> {
let path = get_file_path(fm, FileType::PrivateKey)?;
let raw_key = read_file(fm, &path)?;
let key = KeyPair::from_pem(&raw_key)?; let key = KeyPair::from_pem(&raw_key)?;
Ok(key) Ok(key)
} }
pub fn set_keypair(cert: &Certificate, key_pair: &KeyPair) -> Result<(), Error> {
pub fn set_keypair(fm: &FileManager, key_pair: &KeyPair) -> Result<(), Error> {
let data = key_pair.private_key_to_pem()?; let data = key_pair.private_key_to_pem()?;
write_file(cert, FileType::PrivateKey, &data)
write_file(fm, FileType::PrivateKey, &data)
} }
pub fn get_certificate(cert: &Certificate) -> Result<X509Certificate, Error> {
let path = get_file_path(cert, FileType::Certificate)?;
let raw_crt = read_file(cert, &path)?;
pub fn get_certificate(fm: &FileManager) -> Result<X509Certificate, Error> {
let path = get_file_path(fm, FileType::Certificate)?;
let raw_crt = read_file(fm, &path)?;
let crt = X509Certificate::from_pem(&raw_crt)?; let crt = X509Certificate::from_pem(&raw_crt)?;
Ok(crt) Ok(crt)
} }
pub fn write_certificate(cert: &Certificate, data: &[u8]) -> Result<(), Error> {
write_file(cert, FileType::Certificate, data)
pub fn write_certificate(fm: &FileManager, data: &[u8]) -> Result<(), Error> {
write_file(fm, FileType::Certificate, data)
} }
fn check_files(cert: &Certificate, file_types: &[FileType]) -> bool {
fn check_files(fm: &FileManager, file_types: &[FileType]) -> bool {
for t in file_types.to_vec() { for t in file_types.to_vec() {
let path = match get_file_path(cert, t) {
let path = match get_file_path(fm, t) {
Ok(p) => p, Ok(p) => p,
Err(_) => { Err(_) => {
return false; return false;
} }
}; };
cert.trace(&format!(
fm.trace(&format!(
"Testing file path: {}", "Testing file path: {}",
path.to_str().unwrap_or_default() path.to_str().unwrap_or_default()
)); ));
@ -234,12 +267,12 @@ fn check_files(cert: &Certificate, file_types: &[FileType]) -> bool {
true true
} }
pub fn account_files_exists(cert: &Certificate) -> bool {
pub fn account_files_exists(fm: &FileManager) -> bool {
let file_types = vec![FileType::AccountPrivateKey, FileType::AccountPublicKey]; let file_types = vec![FileType::AccountPrivateKey, FileType::AccountPublicKey];
check_files(cert, &file_types)
check_files(fm, &file_types)
} }
pub fn certificate_files_exists(cert: &Certificate) -> bool {
pub fn certificate_files_exists(fm: &FileManager) -> bool {
let file_types = vec![FileType::PrivateKey, FileType::Certificate]; let file_types = vec![FileType::PrivateKey, FileType::Certificate];
check_files(cert, &file_types)
check_files(fm, &file_types)
} }
Loading…
Cancel
Save