Browse Source

AcmeFactory

master
Adrian Heine 3 years ago
parent
commit
3c2434eb66
  1. 43
      src/symbols/acme/cert.rs
  2. 23
      src/symbols/acme/chain.rs
  3. 92
      src/symbols/acme/mod.rs
  4. 28
      src/symbols/acme/user.rs
  5. 51
      src/symbols/factory.rs

43
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<str>, C: CommandRunner> {
pub struct AcmeCert<
'a,
D: AsRef<str>,
R: AsRef<Path>,
C: CommandRunner,
K: AsRef<Path>,
CH: AsRef<Path>,
> {
domain: D,
command_runner: &'a C,
root_cert_path: R,
account_key_path: K,
challenges_path: CH,
}
impl<'a, D: AsRef<str>, C: CommandRunner> AcmeCert<'a, D, C> {
pub fn new(domain: D, command_runner: &'a C) -> Self {
impl<'a, D: AsRef<str>, R: AsRef<Path>, C: CommandRunner, K: AsRef<Path>, CH: AsRef<Path>>
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<str>, C: CommandRunner> AcmeCert<'a, D, C> {
}
}
impl<'a, D: AsRef<str>, C: CommandRunner> fmt::Display for AcmeCert<'a, D, C> {
impl<'a, D: AsRef<str>, R: AsRef<Path>, C: CommandRunner, K: AsRef<Path>, CH: AsRef<Path>>
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<str>, C: CommandRunner> fmt::Display for AcmeCert<'a, D, C> {
const DAYS_IN_SECONDS: u32 = 24 * 60 * 60;
impl<'a, D: AsRef<str>, C: CommandRunner> Symbol for AcmeCert<'a, D, C> {
impl<'a, D: AsRef<str>, R: AsRef<Path>, C: CommandRunner, K: AsRef<Path>, CH: AsRef<Path>> Symbol
for AcmeCert<'a, D, R, C, K, CH>
{
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
if !self.get_cert_path().exists() {
return Ok(false);
@ -72,7 +97,7 @@ impl<'a, D: AsRef<str>, 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<str>, 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())?;

23
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<str>, C: CommandRunner> {
pub struct AcmeCertChain<'a, D: AsRef<str>, R: AsRef<Path>, C: CommandRunner> {
domain: D,
command_runner: &'a C,
root_cert: R,
}
impl<'a, D: AsRef<str>, C: CommandRunner> AcmeCertChain<'a, D, C> {
pub fn new(domain: D, command_runner: &'a C) -> Self {
impl<'a, D: AsRef<str>, R: AsRef<Path>, 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<str>, C: CommandRunner> AcmeCertChain<'a, D, C> {
}
}
impl<'a, D: AsRef<str>, C: CommandRunner> fmt::Display for AcmeCertChain<'a, D, C> {
impl<'a, D: AsRef<str>, R: AsRef<Path>, 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<str>, C: CommandRunner> fmt::Display for AcmeCertChain<'a, D,
const DAYS_IN_SECONDS: u32 = 24 * 60 * 60;
impl<'a, D: AsRef<str>, C: CommandRunner> Symbol for AcmeCertChain<'a, D, C> {
impl<'a, D: AsRef<str>, R: AsRef<Path>, C: CommandRunner> Symbol for AcmeCertChain<'a, D, R, C> {
fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
if !self.get_cert_chain_path().exists() {
return Ok(false);
@ -74,7 +78,7 @@ impl<'a, D: AsRef<str>, 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<str>, C: CommandRunner> Symbol for AcmeCertChain<'a, D, C> {
fn execute(&self) -> Result<(), Box<dyn Error>> {
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)?;

92
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<str>, H: AsRef<Path>, C: AsRef<str>, 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<str>, H: AsRef<Path>, C: AsRef<str>, 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<Path> {
[self.home_dir.as_ref(), "challenges".as_ref()]
.iter()
.collect::<PathBuf>()
.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<HOST: 'a + Clone + AsRef<str>>(&'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),
))
}
}

28
src/symbols/acme/user.rs

@ -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<str>, U: 'a + AsRef<str> + Clone, H: AsRef<Path>>(
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::<PathBuf>();
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),
))
}

51
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<str> {
"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<str> + Clone, D: 'a + AsRef<str>>(
&'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>(

Loading…
Cancel
Save