AcmeFactory
This commit is contained in:
parent
602c7f7ebe
commit
3c2434eb66
5 changed files with 165 additions and 72 deletions
|
|
@ -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())?;
|
||||
|
|
|
|||
|
|
@ -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)?;
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
))
|
||||
}
|
||||
|
|
@ -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…
Add table
Add a link
Reference in a new issue