diff --git a/src/symbols/acme/cert.rs b/src/symbols/acme/cert.rs index d3081a8..cb6f22e 100644 --- a/src/symbols/acme/cert.rs +++ b/src/symbols/acme/cert.rs @@ -2,22 +2,43 @@ use std::error::Error; use std::fmt; use std::fs::File as FsFile; use std::io::Write; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use crate::command_runner::CommandRunner; use crate::resources::Resource; use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; -pub struct AcmeCert<'a, D: AsRef, C: CommandRunner> { +pub struct AcmeCert< + 'a, + D: AsRef, + R: AsRef, + C: CommandRunner, + K: AsRef, + CH: AsRef, +> { domain: D, command_runner: &'a C, + root_cert_path: R, + account_key_path: K, + challenges_path: CH, } -impl<'a, D: AsRef, C: CommandRunner> AcmeCert<'a, D, C> { - pub fn new(domain: D, command_runner: &'a C) -> Self { +impl<'a, D: AsRef, R: AsRef, C: CommandRunner, K: AsRef, CH: AsRef> + AcmeCert<'a, D, R, C, K, CH> +{ + pub fn new( + domain: D, + command_runner: &'a C, + root_cert_path: R, + account_key_path: K, + challenges_path: CH, + ) -> Self { AcmeCert { domain, command_runner, + root_cert_path, + account_key_path, + challenges_path, } } @@ -30,7 +51,9 @@ impl<'a, D: AsRef, C: CommandRunner> AcmeCert<'a, D, C> { } } -impl<'a, D: AsRef, C: CommandRunner> fmt::Display for AcmeCert<'a, D, C> { +impl<'a, D: AsRef, R: AsRef, C: CommandRunner, K: AsRef, CH: AsRef> + fmt::Display for AcmeCert<'a, D, R, C, K, CH> +{ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "AcmeCert {}", self.domain.as_ref()) } @@ -38,7 +61,9 @@ impl<'a, D: AsRef, C: CommandRunner> fmt::Display for AcmeCert<'a, D, C> { const DAYS_IN_SECONDS: u32 = 24 * 60 * 60; -impl<'a, D: AsRef, C: CommandRunner> Symbol for AcmeCert<'a, D, C> { +impl<'a, D: AsRef, R: AsRef, C: CommandRunner, K: AsRef, CH: AsRef> Symbol + for AcmeCert<'a, D, R, C, K, CH> +{ fn target_reached(&self) -> Result> { if !self.get_cert_path().exists() { return Ok(false); @@ -72,7 +97,7 @@ impl<'a, D: AsRef, C: CommandRunner> Symbol for AcmeCert<'a, D, C> { args![ "verify", "--untrusted", - "/home/acme/lets_encrypt_x3_cross_signed.pem", + self.root_cert_path.as_ref(), self.get_cert_path(), ], ) @@ -97,11 +122,11 @@ impl<'a, D: AsRef, C: CommandRunner> Symbol for AcmeCert<'a, D, C> { "acme-tiny", args![ "--account-key", - "/home/acme/account.key", + self.account_key_path.as_ref(), "--csr", self.get_csr_path(), "--acme-dir", - "/home/acme/challenges/", + self.challenges_path.as_ref(), ], )?; let mut file = FsFile::create(self.get_cert_path())?; diff --git a/src/symbols/acme/chain.rs b/src/symbols/acme/chain.rs index ef0585f..e9a2344 100644 --- a/src/symbols/acme/chain.rs +++ b/src/symbols/acme/chain.rs @@ -2,22 +2,24 @@ use std::error::Error; use std::fmt; use std::fs::File as FsFile; use std::io::Write; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use crate::command_runner::CommandRunner; use crate::resources::Resource; use crate::symbols::{Action, OwnedSymbolAction, Symbol, SymbolAction, SymbolRunner}; -pub struct AcmeCertChain<'a, D: AsRef, C: CommandRunner> { +pub struct AcmeCertChain<'a, D: AsRef, R: AsRef, C: CommandRunner> { domain: D, command_runner: &'a C, + root_cert: R, } -impl<'a, D: AsRef, C: CommandRunner> AcmeCertChain<'a, D, C> { - pub fn new(domain: D, command_runner: &'a C) -> Self { +impl<'a, D: AsRef, R: AsRef, C: CommandRunner> AcmeCertChain<'a, D, R, C> { + pub fn new(domain: D, command_runner: &'a C, root_cert: R) -> Self { AcmeCertChain { domain, command_runner, + root_cert, } } @@ -30,7 +32,9 @@ impl<'a, D: AsRef, C: CommandRunner> AcmeCertChain<'a, D, C> { } } -impl<'a, D: AsRef, C: CommandRunner> fmt::Display for AcmeCertChain<'a, D, C> { +impl<'a, D: AsRef, R: AsRef, C: CommandRunner> fmt::Display + for AcmeCertChain<'a, D, R, C> +{ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "AcmeCertChain {}", self.domain.as_ref()) } @@ -38,7 +42,7 @@ impl<'a, D: AsRef, C: CommandRunner> fmt::Display for AcmeCertChain<'a, D, const DAYS_IN_SECONDS: u32 = 24 * 60 * 60; -impl<'a, D: AsRef, C: CommandRunner> Symbol for AcmeCertChain<'a, D, C> { +impl<'a, D: AsRef, R: AsRef, C: CommandRunner> Symbol for AcmeCertChain<'a, D, R, C> { fn target_reached(&self) -> Result> { if !self.get_cert_chain_path().exists() { return Ok(false); @@ -74,7 +78,7 @@ impl<'a, D: AsRef, C: CommandRunner> Symbol for AcmeCertChain<'a, D, C> { args![ "verify", "-untrusted", - "/home/acme/lets_encrypt_x3_cross_signed.pem", + self.root_cert.as_ref(), self.get_cert_chain_path(), ], ) @@ -85,10 +89,7 @@ impl<'a, D: AsRef, C: CommandRunner> Symbol for AcmeCertChain<'a, D, C> { fn execute(&self) -> Result<(), Box> { let output = self.command_runner.get_output( "cat", - args![ - self.get_single_cert_path(), - "/home/acme/lets_encrypt_x3_cross_signed.pem", - ], + args![self.get_single_cert_path(), self.root_cert.as_ref(),], )?; let mut file = FsFile::create(self.get_cert_chain_path())?; file.write_all(&output)?; diff --git a/src/symbols/acme/mod.rs b/src/symbols/acme/mod.rs index c30f54d..477de9c 100644 --- a/src/symbols/acme/mod.rs +++ b/src/symbols/acme/mod.rs @@ -1,9 +1,97 @@ +use std::borrow::Cow; +use std::path::{Path, PathBuf}; + +use crate::command_runner::CommandRunner; +use crate::command_runner::SetuidCommandRunner; +use crate::symbols::dir::Dir; +use crate::symbols::file::File; +use crate::symbols::list::List; +use crate::symbols::owner::Owner; +use crate::symbols::Symbol; + mod account_key; mod cert; mod chain; -mod user; pub use self::account_key::AcmeAccountKey; pub use self::cert::AcmeCert; pub use self::chain::AcmeCertChain; -pub use self::user::new as newAcmeUser; + +const ROOT_CERT_FILE_NAME: &str = "lets_encrypt_x3_cross_signed.pem"; +const ACCOUNT_KEY_FILE_NAME: &str = "account.key"; + +pub struct Factory<'a, U: AsRef, H: AsRef, C: AsRef, R: CommandRunner> { + user_name: U, + home_dir: H, + cert: C, + command_runner: &'a R, + acme_command_runner: SetuidCommandRunner<'a, U, R>, +} + +impl<'a, U: Clone + AsRef, H: AsRef, C: AsRef, R: CommandRunner> + Factory<'a, U, H, C, R> +{ + pub fn new(user_name: U, home_dir: H, cert: C, command_runner: &'a R) -> Self { + let acme_command_runner = SetuidCommandRunner::new(user_name.clone(), command_runner); + Self { + user_name, + home_dir, + cert, + command_runner, + acme_command_runner, + } + } + pub fn get_challenges_dir(&'a self) -> Cow { + [self.home_dir.as_ref(), "challenges".as_ref()] + .iter() + .collect::() + .into() + } + pub fn get_init(&'a self) -> impl Symbol + 'a { + let root_cert_path: PathBuf = [self.home_dir.as_ref(), ROOT_CERT_FILE_NAME.as_ref()] + .iter() + .collect(); + let account_key_file: PathBuf = [self.home_dir.as_ref(), ACCOUNT_KEY_FILE_NAME.as_ref()] + .iter() + .collect(); + List::from(( + AcmeAccountKey::new(account_key_file.clone(), self.command_runner), + Owner::new( + account_key_file, + self.user_name.clone(), + self.command_runner, + ), + Dir::new(self.get_challenges_dir()), + Owner::new( + self.get_challenges_dir(), + self.user_name.clone(), + self.command_runner, + ), + Dir::new("/etc/ssl/local_certs"), + Owner::new( + "/etc/ssl/local_certs", + self.user_name.clone(), + self.command_runner, + ), + File::new(root_cert_path, self.cert.as_ref()), + )) + } + pub fn get_cert>(&'a self, host: HOST) -> impl Symbol + 'a { + let root_cert_path: PathBuf = [self.home_dir.as_ref(), ROOT_CERT_FILE_NAME.as_ref()] + .iter() + .collect(); + let account_key_path: PathBuf = [self.home_dir.as_ref(), ACCOUNT_KEY_FILE_NAME.as_ref()] + .iter() + .collect(); + List::from(( + AcmeCert::new( + host.clone(), + &self.acme_command_runner, + root_cert_path.clone(), + account_key_path, + self.get_challenges_dir(), + ), + AcmeCertChain::new(host, &self.acme_command_runner, root_cert_path), + )) + } +} diff --git a/src/symbols/acme/user.rs b/src/symbols/acme/user.rs deleted file mode 100644 index 6a74780..0000000 --- a/src/symbols/acme/user.rs +++ /dev/null @@ -1,28 +0,0 @@ -use std::path::{Path, PathBuf}; - -use crate::command_runner::CommandRunner; -use crate::symbols::acme::AcmeAccountKey; -use crate::symbols::dir::Dir; -use crate::symbols::file::File; -use crate::symbols::list::List; -use crate::symbols::owner::Owner; -use crate::symbols::Symbol; - -pub fn new<'a, R: CommandRunner, C: 'a + AsRef, U: 'a + AsRef + Clone, H: AsRef>( - command_runner: &'a R, - cert: C, - user_name: U, - home: H, -) -> impl Symbol + 'a { - let path = |rel: &str| [home.as_ref(), rel.as_ref()].iter().collect::(); - let account_key_file = path("account.key"); - List::from(( - AcmeAccountKey::new(account_key_file.clone(), command_runner), - Owner::new(account_key_file, user_name.clone(), command_runner), - Dir::new(path("challenges")), - Owner::new(path("challenges"), user_name.clone(), command_runner), - Dir::new("/etc/ssl/local_certs"), - Owner::new("/etc/ssl/local_certs", user_name, command_runner), - File::new(path("lets_encrypt_x3_cross_signed.pem"), cert), - )) -} diff --git a/src/symbols/factory.rs b/src/symbols/factory.rs index f654b5f..b7051a7 100644 --- a/src/symbols/factory.rs +++ b/src/symbols/factory.rs @@ -2,9 +2,9 @@ use std::borrow::Cow; use std::path::Path; use std::path::PathBuf; -use crate::command_runner::{CommandRunner, SetuidCommandRunner}; +use crate::command_runner::CommandRunner; use crate::storage::{SimpleStorage, Storage}; -use crate::symbols::acme::{newAcmeUser, AcmeCert, AcmeCertChain}; +use crate::symbols::acme::Factory as AcmeFactory; use crate::symbols::cron::Cron; use crate::symbols::file::File; use crate::symbols::git::checkout::GitCheckout; @@ -29,27 +29,37 @@ pub trait Policy { fn socket_path(&self, user_name: &str, service_name: &str) -> PathBuf { format!("/var/tmp/{}-{}.socket", user_name, service_name).into() } + fn acme_user(&self) -> Cow { + "acme".into() + } } pub struct DefaultPolicy; impl Policy for DefaultPolicy {} -pub struct SymbolFactory<'a, C: 'a + CommandRunner, R: 'a + SymbolRunner, P: 'a + Policy> { +pub struct SymbolFactory< + 'a, + C: 'a + CommandRunner, + R: 'a + SymbolRunner, + P: 'a + Policy, +> { command_runner: &'a C, - acme_command_runner: SetuidCommandRunner<'a, Cow<'a, str>, C>, + acme_factory: AcmeFactory<'a, Cow<'a, str>, PathBuf, &'a str, C>, symbol_runner: &'a R, policy: &'a P, } -impl<'b, C: 'b + CommandRunner, R: 'b + SymbolRunner, P: 'b + Policy> SymbolFactory<'b, C, R, P> { - pub fn new(command_runner: &'b C, symbol_runner: &'b R, policy: &'b P) -> Self { - let acme_user = "acme"; // FIXME: CONFIG - - let acme_command_runner = SetuidCommandRunner::new(acme_user.into(), command_runner); +impl<'b, C: 'b + CommandRunner, R: 'b + SymbolRunner, P: 'b + Policy> + SymbolFactory<'b, C, R, P> +{ + pub fn new(command_runner: &'b C, symbol_runner: &'b R, policy: &'b P, cert: &'b str) -> Self { + let acme_user = policy.acme_user(); + let acme_home = policy.home_for_user(&acme_user); + let acme_factory = AcmeFactory::new(acme_user, acme_home, cert, command_runner); SymbolFactory { command_runner, - acme_command_runner, + acme_factory, symbol_runner, policy, } @@ -66,9 +76,8 @@ impl<'b, C: 'b + CommandRunner, R: 'b + SymbolRunner, P: 'b + Policy> SymbolFact nginx_server_symbol, ReloadService::new("nginx", self.command_runner), ), - AcmeCert::new(host, &self.acme_command_runner), Hook::new( - AcmeCertChain::new(host, &self.acme_command_runner), + self.acme_factory.get_cert(host), ReloadService::new("nginx", self.command_runner), ), )) @@ -76,10 +85,13 @@ impl<'b, C: 'b + CommandRunner, R: 'b + SymbolRunner, P: 'b + Policy> SymbolFact pub fn get_nginx_acme_challenge_config<'a>(&'a self) -> impl Symbol + 'a { File::new( "/etc/nginx/snippets/acme-challenge.conf", - "location ^~ /.well-known/acme-challenge/ { - alias /home/acme/challenges/; + format!( + "location ^~ /.well-known/acme-challenge/ {{ + alias {}/challenges/; try_files $uri =404; -}", +}}", + self.acme_factory.get_challenges_dir().to_str().unwrap() + ), ) } @@ -340,13 +352,8 @@ env[PATH] = /usr/local/bin:/usr/bin:/bin Cron::new(user, content, self.command_runner) } - pub fn get_acme_user<'a, S: 'a + AsRef + Clone, D: 'a + AsRef>( - &'a self, - cert: D, - user_name: S, - ) -> impl Symbol + 'a { - let home = self.policy.home_for_user(user_name.as_ref()); - newAcmeUser(self.command_runner, cert, user_name.clone(), home) + pub fn get_acme_user<'a>(&'a self) -> impl Symbol + 'a { + self.acme_factory.get_init() } pub fn get_systemd_user_service<'a>(