From 7d629e38d82055884fe657c79920ae57ef52ac12 Mon Sep 17 00:00:00 2001 From: Adrian Heine Date: Sun, 9 Apr 2017 16:07:33 +0200 Subject: [PATCH] Update --- src/symbols/acme/account_key.rs | 60 +++++++++++++++++++++ src/symbols/acme/cert.rs | 81 +++++++++++++++++++++++++++++ src/symbols/acme/mod.rs | 5 ++ src/symbols/dir_for.rs | 49 ----------------- src/symbols/mod.rs | 13 +++-- src/symbols/nginx/mod.rs | 1 + src/symbols/nginx/reload.rs | 40 ++++++++++++++ src/symbols/nginx/server.rs | 13 +++-- src/symbols/not_a_symlink.rs | 47 +++++++++++++++++ src/symbols/owner.rs | 41 +++++++++++++++ src/symbols/tls/csr.rs | 57 ++++++++++++++++++++ src/symbols/tls/key.rs | 60 +++++++++++++++++++++ src/symbols/tls/mod.rs | 7 +++ src/symbols/tls/self_signed_cert.rs | 62 ++++++++++++++++++++++ 14 files changed, 478 insertions(+), 58 deletions(-) create mode 100644 src/symbols/acme/account_key.rs create mode 100644 src/symbols/acme/cert.rs create mode 100644 src/symbols/acme/mod.rs delete mode 100644 src/symbols/dir_for.rs create mode 100644 src/symbols/nginx/reload.rs create mode 100644 src/symbols/not_a_symlink.rs create mode 100644 src/symbols/owner.rs create mode 100644 src/symbols/tls/csr.rs create mode 100644 src/symbols/tls/key.rs create mode 100644 src/symbols/tls/mod.rs create mode 100644 src/symbols/tls/self_signed_cert.rs diff --git a/src/symbols/acme/account_key.rs b/src/symbols/acme/account_key.rs new file mode 100644 index 0000000..0ff0a90 --- /dev/null +++ b/src/symbols/acme/account_key.rs @@ -0,0 +1,60 @@ +use std::borrow::Cow; +use std::error::Error; +use std::fmt; + +use command_runner::CommandRunner; +use symbols::Symbol; + +pub struct AcmeAccountKey<'a> { + path: Cow<'a, str>, + command_runner: &'a CommandRunner +} + +impl<'a> AcmeAccountKey<'a> { + pub fn new(path: Cow<'a, str>, command_runner: &'a CommandRunner) -> AcmeAccountKey<'a> { + AcmeAccountKey { + path: path, + command_runner: command_runner + } + } + + fn get_path(&self) -> String { + self.path.clone().into_owned() + } + + fn get_bytes(&self) -> u32 { + 4096 + } +} + +impl<'a> fmt::Display for AcmeAccountKey<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "AcmeAccountKey {}", self.path) + } +} + +impl<'a> Symbol for AcmeAccountKey<'a> { + fn target_reached(&self) -> Result> { + let result = self.command_runner.run_with_args("openssl", &["rsa", "-in", &self.get_path(), "-noout", "-check", "-text"]); + match result { + Err(e) => Err(Box::new(e)), + Ok(output) => match output.status.code() { + Some(0) => Ok(output.stdout.starts_with(format!("Private-Key: ({} bit)\n", self.get_bytes()).as_bytes())), + Some(_) => Ok(false), + _ => Err("Didn't work".to_string().into()) + } + } + } + + fn execute(&self) -> Result<(), Box> { + let output = self.command_runner.run_with_args("openssl", &["genrsa", "-out", &self.get_path(), &self.get_bytes().to_string()]); + match output { + Err(e) => Err(Box::new(e)), + Ok(_) => Ok(()) + } + } +} + +#[cfg(test)] +mod test { +} diff --git a/src/symbols/acme/cert.rs b/src/symbols/acme/cert.rs new file mode 100644 index 0000000..fd05c24 --- /dev/null +++ b/src/symbols/acme/cert.rs @@ -0,0 +1,81 @@ +use std::borrow::Cow; +use std::error::Error; +use std::fmt; +use std::fs::File as FsFile; +use std::io::{self, Write}; + +use command_runner::CommandRunner; +use symbols::Symbol; + +pub struct AcmeCert<'a> { + domain: Cow<'a, str>, + command_runner: &'a CommandRunner +} + +impl<'a> AcmeCert<'a> { + pub fn new(domain: Cow<'a, str>, command_runner: &'a CommandRunner) -> AcmeCert<'a> { + AcmeCert { + domain: domain, + command_runner: command_runner + } + } + + fn get_key_path(&self) -> String { + format!("/etc/ssl/private/{}.key", self.domain) + } + + fn get_csr_path(&self) -> String { + format!("/etc/ssl/local_certs/{}.csr", self.domain) + } + + fn get_cert_path(&self) -> String { + format!("/etc/ssl/local_certs/{}.crt", self.domain) + } +} + +impl<'a> fmt::Display for AcmeCert<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "AcmeCert {}", self.domain) + } +} + +const DAYS_IN_SECONDS: u32 = 24*60*60; + +impl<'a> Symbol for AcmeCert<'a> { + fn target_reached(&self) -> Result> { + let file = FsFile::open(self.get_cert_path()); + // Check first if file exists to support dry-run mode where the acme user is not even created + if let Err(e) = file { + return if e.kind() == io::ErrorKind::NotFound { + Ok(false) + } else { + Err(Box::new(e)) + }; + } + + // FIXME: check who signed it + let result = self.command_runner.run_with_args("openssl", &["x509", "-in", &self.get_cert_path(), "-noout", "-subject", "-checkend", &(30*DAYS_IN_SECONDS).to_string()]); + match result { + Err(e) => Err(Box::new(e)), + Ok(output) => match output.status.code() { + Some(0) => if output.stdout == format!("subject=CN = {}\nCertificate will not expire\n", self.domain).as_bytes() { + let result = try!(self.command_runner.run_with_args("openssl", &["verify", "--untrusted", "/home/acme/lets_encrypt_x3_cross_signed.pem", &self.get_cert_path()]).map_err(|e| Box::new(e))); + Ok(result.status.code() == Some(0)) + } else { Ok(false) }, + Some(_) => Ok(false), + _ => Err("Didn't work".to_string().into()) + } + } + } + + fn execute(&self) -> Result<(), Box> { + let output = try!(self.command_runner.run_with_args("acme-tiny", &["--account-key", "/home/acme/account.key", "--csr", &self.get_csr_path(), "--acme-dir", "/home/acme/challenges/"]).map_err(|e| Box::new(e))); + let mut file = try!(FsFile::create(self.get_cert_path())); + try!(file.write_all(&output.stdout)); + Ok(()) + } +} + +#[cfg(test)] +mod test { +} diff --git a/src/symbols/acme/mod.rs b/src/symbols/acme/mod.rs new file mode 100644 index 0000000..280a536 --- /dev/null +++ b/src/symbols/acme/mod.rs @@ -0,0 +1,5 @@ +mod account_key; +mod cert; + +pub use self::account_key::AcmeAccountKey; +pub use self::cert::AcmeCert; diff --git a/src/symbols/dir_for.rs b/src/symbols/dir_for.rs deleted file mode 100644 index 40d3ccf..0000000 --- a/src/symbols/dir_for.rs +++ /dev/null @@ -1,49 +0,0 @@ -use std::error::Error; -use std::fmt; -use std::fs; -use std::os::unix::fs::MetadataExt; - -use users::get_user_by_name; - -use symbols::Symbol; -use symbols::dir::Dir; -use command_runner::CommandRunner; - - -pub struct DirFor<'a, D> where D: AsRef + fmt::Display { - dir: Dir, - path: D, - user_name: &'a str, - command_runner: &'a CommandRunner -} - -impl<'a, D> DirFor<'a, D> where D: AsRef + fmt::Display + Clone { - pub fn new(path: D, user_name: &'a str, command_runner: &'a CommandRunner) -> Self { - DirFor { dir: Dir::new(path.clone()), path: path, user_name: user_name, command_runner: command_runner } - } -} - -impl<'a, D> Symbol for DirFor<'a, D> where D: AsRef + fmt::Display { - fn target_reached(&self) -> Result> { - match self.dir.target_reached() { - Ok(true) => { - let actual_uid = fs::metadata(self.path.as_ref()).unwrap().uid(); - let target_uid = get_user_by_name(self.user_name).unwrap().uid(); - Ok(actual_uid == target_uid) - }, - res => res - } - } - - fn execute(&self) -> Result<(), Box> { - try!(self.dir.execute()); - try!(self.command_runner.run_with_args("chown", &[self.user_name, self.path.as_ref()])); - Ok(()) - } -} - -impl<'a, D> fmt::Display for DirFor<'a, D> where D: AsRef + fmt::Display { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>{ - write!(f, "Dir {} for {}", self.path, self.user_name) - } -} diff --git a/src/symbols/mod.rs b/src/symbols/mod.rs index 11115d7..5bf6520 100644 --- a/src/symbols/mod.rs +++ b/src/symbols/mod.rs @@ -10,13 +10,16 @@ pub trait Symbol: Display { } } +pub mod acme; pub mod dir; -pub mod dir_for; pub mod file; pub mod git; -pub mod npm; -pub mod user; -pub mod systemd; -pub mod nginx; pub mod hook; pub mod list; +pub mod nginx; +pub mod not_a_symlink; +pub mod npm; +pub mod owner; +pub mod systemd; +pub mod tls; +pub mod user; diff --git a/src/symbols/nginx/mod.rs b/src/symbols/nginx/mod.rs index 74f47ad..75c030c 100644 --- a/src/symbols/nginx/mod.rs +++ b/src/symbols/nginx/mod.rs @@ -1 +1,2 @@ +pub mod reload; pub mod server; diff --git a/src/symbols/nginx/reload.rs b/src/symbols/nginx/reload.rs new file mode 100644 index 0000000..ac6daa1 --- /dev/null +++ b/src/symbols/nginx/reload.rs @@ -0,0 +1,40 @@ +use std::error::Error; +use std::fmt; +use std::io; +use std::ops::Deref; + +use command_runner::CommandRunner; +use symbols::Symbol; +use symbols::file::File as FileSymbol; +use resources::Resource; + +pub struct NginxReload<'a> { + command_runner: &'a CommandRunner, +} + +use std::borrow::Cow; + +impl<'a> NginxReload<'a> { + pub fn new(command_runner: &'a CommandRunner) -> Self { + NginxReload { + command_runner: command_runner + } + } +} + +impl<'a> Symbol for NginxReload<'a> { + fn target_reached(&self) -> Result> { + Ok(true) + } + + fn execute(&self) -> Result<(), Box> { + try!(self.command_runner.run_with_args("systemctl", &["reload-or-restart", "nginx"])); + Ok(()) + } +} + +impl<'a> fmt::Display for NginxReload<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(),fmt::Error>{ + write!(f, "Reload nginx server") + } +} diff --git a/src/symbols/nginx/server.rs b/src/symbols/nginx/server.rs index d6fb8c8..b678b5d 100644 --- a/src/symbols/nginx/server.rs +++ b/src/symbols/nginx/server.rs @@ -75,18 +75,23 @@ location @proxy {{ let content = String::from(redir_content) + &format!("server {{ listen 80; - # listen 443 ssl; - # ssl_certificate /etc/ssl/.crt; - # ssl_certificate_key /etc/ssl/.key; + listen 443 ssl; + ssl_certificate /etc/ssl/local_certs/{0}.crt; + ssl_certificate_key /etc/ssl/private/{0}.key; server_name {}; root {}; + include \"snippets/acme-challenge.conf\"; {} }} ", domain, static_path, proxy_content); + NginxServer::new_generic(FileSymbol::new(file_path, content), command_runner) + } + + pub fn new_generic(file: FileSymbol>, command_runner: &'a CommandRunner) -> Self { NginxServer { command_runner: command_runner, - file: FileSymbol::new(file_path, content) + file: file } } } diff --git a/src/symbols/not_a_symlink.rs b/src/symbols/not_a_symlink.rs new file mode 100644 index 0000000..081b2aa --- /dev/null +++ b/src/symbols/not_a_symlink.rs @@ -0,0 +1,47 @@ +use std::error::Error; +use std::fmt; +use std::fs; +use std::io; +use std::ops::Deref; +use std::path::Path; + +use symbols::Symbol; +use resources::{DirResource, Resource}; + +pub struct NotASymlink where D: AsRef + fmt::Display { + path: D +} + +impl NotASymlink where D: AsRef + fmt::Display { + pub fn new(path: D) -> Self { + NotASymlink { + path: path + } + } +} + +impl Symbol for NotASymlink where D: AsRef + fmt::Display { + fn target_reached(&self) -> Result> { + let metadata = fs::symlink_metadata(self.path.as_ref()); + // Check if file exists + if let Err(e) = metadata { + return if e.kind() == io::ErrorKind::NotFound { + Ok(true) + } else { + Err(Box::new(e)) + }; + } + Ok(!metadata.unwrap().file_type().is_symlink()) + } + + fn execute(&self) -> Result<(), Box> { + try!(fs::remove_file(self.path.as_ref())); + Ok(()) + } +} + +impl fmt::Display for NotASymlink where D: AsRef + fmt::Display { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>{ + write!(f, "NotASymlink {}", self.path) + } +} diff --git a/src/symbols/owner.rs b/src/symbols/owner.rs new file mode 100644 index 0000000..5b45508 --- /dev/null +++ b/src/symbols/owner.rs @@ -0,0 +1,41 @@ +use std::error::Error; +use std::fmt; +use std::fs; +use std::os::unix::fs::MetadataExt; + +use users::get_user_by_name; + +use symbols::Symbol; +use command_runner::CommandRunner; + + +pub struct Owner<'a, D> where D: AsRef + fmt::Display { + path: D, + user_name: &'a str, + command_runner: &'a CommandRunner +} + +impl<'a, D> Owner<'a, D> where D: AsRef + fmt::Display { + pub fn new(path: D, user_name: &'a str, command_runner: &'a CommandRunner) -> Self { + Owner { path: path, user_name: user_name, command_runner: command_runner } + } +} + +impl<'a, D> Symbol for Owner<'a, D> where D: AsRef + fmt::Display { + fn target_reached(&self) -> Result> { + let actual_uid = fs::metadata(self.path.as_ref()).unwrap().uid(); + let target_uid = get_user_by_name(self.user_name).unwrap().uid(); + Ok(actual_uid == target_uid) + } + + fn execute(&self) -> Result<(), Box> { + try!(self.command_runner.run_with_args("chown", &[self.user_name, self.path.as_ref()])); + Ok(()) + } +} + +impl<'a, D> fmt::Display for Owner<'a, D> where D: AsRef + fmt::Display { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>{ + write!(f, "Owner {} for {}", self.user_name, self.path) + } +} diff --git a/src/symbols/tls/csr.rs b/src/symbols/tls/csr.rs new file mode 100644 index 0000000..935d4c6 --- /dev/null +++ b/src/symbols/tls/csr.rs @@ -0,0 +1,57 @@ +use std::borrow::Cow; +use std::error::Error; +use std::fmt; + +use command_runner::CommandRunner; +use symbols::Symbol; + +pub struct TlsCsr<'a> { + domain: Cow<'a, str>, + command_runner: &'a CommandRunner +} + +impl<'a> TlsCsr<'a> { + pub fn new(domain: Cow<'a, str>, command_runner: &'a CommandRunner) -> TlsCsr<'a> { + TlsCsr { + domain: domain, + command_runner: command_runner + } + } + + fn get_key_path(&self) -> String { + format!("/etc/ssl/private/{}.key", self.domain) + } + + fn get_csr_path(&self) -> String { + format!("/etc/ssl/local_certs/{}.csr", self.domain) + } +} + +impl<'a> fmt::Display for TlsCsr<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "TlsCsr {}", self.domain) + } +} + +impl<'a> Symbol for TlsCsr<'a> { + fn target_reached(&self) -> Result> { + let result = self.command_runner.run_with_args("openssl", &["req", "-in", &self.get_csr_path(), "-noout", "-verify"]); + match result { + Err(e) => Err(Box::new(e)), + Ok(output) => match output.status.code() { + Some(0) => Ok(output.stderr == "verify OK\n".as_bytes()), + Some(_) => Ok(false), + _ => Err("Didn't work".to_string().into()) + } + } + } + + fn execute(&self) -> Result<(), Box> { + let output = try!(self.command_runner.run_with_args("openssl", &["req", "-new", "-sha256", "-key", &self.get_key_path(), "-out", &self.get_csr_path(), "-subj", &format!("/CN={}", self.domain)]).map_err(|e| Box::new(e))); + Ok(()) + } +} + +#[cfg(test)] +mod test { +} diff --git a/src/symbols/tls/key.rs b/src/symbols/tls/key.rs new file mode 100644 index 0000000..534c317 --- /dev/null +++ b/src/symbols/tls/key.rs @@ -0,0 +1,60 @@ +use std::borrow::Cow; +use std::error::Error; +use std::fmt; + +use command_runner::CommandRunner; +use symbols::Symbol; + +pub struct TlsKey<'a> { + domain: Cow<'a, str>, + command_runner: &'a CommandRunner +} + +impl<'a> TlsKey<'a> { + pub fn new(domain: Cow<'a, str>, command_runner: &'a CommandRunner) -> TlsKey<'a> { + TlsKey { + domain: domain, + command_runner: command_runner + } + } + + fn get_path(&self) -> String { + format!("/etc/ssl/private/{}.key", self.domain) + } + + fn get_bytes(&self) -> u32 { + 4096 + } +} + +impl<'a> fmt::Display for TlsKey<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "TlsKey {}", self.domain) + } +} + +impl<'a> Symbol for TlsKey<'a> { + fn target_reached(&self) -> Result> { + let result = self.command_runner.run_with_args("openssl", &["rsa", "-in", &self.get_path(), "-noout", "-check", "-text"]); + match result { + Err(e) => Err(Box::new(e)), + Ok(output) => match output.status.code() { + Some(0) => Ok(output.stdout.starts_with(format!("Private-Key: ({} bit)\n", self.get_bytes()).as_bytes())), + Some(_) => Ok(false), + _ => Err("Didn't work".to_string().into()) + } + } + } + + fn execute(&self) -> Result<(), Box> { + let output = self.command_runner.run_with_args("openssl", &["genrsa", "-out", &self.get_path(), &self.get_bytes().to_string()]); + match output { + Err(e) => Err(Box::new(e)), + Ok(_) => Ok(()) + } + } +} + +#[cfg(test)] +mod test { +} diff --git a/src/symbols/tls/mod.rs b/src/symbols/tls/mod.rs new file mode 100644 index 0000000..d7b4c79 --- /dev/null +++ b/src/symbols/tls/mod.rs @@ -0,0 +1,7 @@ +mod csr; +mod key; +mod self_signed_cert; + +pub use self::csr::TlsCsr; +pub use self::key::TlsKey; +pub use self::self_signed_cert::SelfSignedTlsCert; diff --git a/src/symbols/tls/self_signed_cert.rs b/src/symbols/tls/self_signed_cert.rs new file mode 100644 index 0000000..438ea8f --- /dev/null +++ b/src/symbols/tls/self_signed_cert.rs @@ -0,0 +1,62 @@ +use std::borrow::Cow; +use std::error::Error; +use std::fmt; + +use command_runner::CommandRunner; +use symbols::Symbol; + +pub struct SelfSignedTlsCert<'a> { + domain: Cow<'a, str>, + command_runner: &'a CommandRunner +} + +impl<'a> SelfSignedTlsCert<'a> { + pub fn new(domain: Cow<'a, str>, command_runner: &'a CommandRunner) -> SelfSignedTlsCert<'a> { + SelfSignedTlsCert { + domain: domain, + command_runner: command_runner + } + } + + fn get_key_path(&self) -> String { + format!("/etc/ssl/private/{}.key", self.domain) + } + + fn get_cert_path(&self) -> String { + format!("/etc/ssl/local_certs/{}.crt", self.domain) + } +} + +impl<'a> fmt::Display for SelfSignedTlsCert<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "SelfSignedTlsCert {}", self.domain) + } +} + +const DAYS_IN_SECONDS: u32 = 24*60*60; + +impl<'a> Symbol for SelfSignedTlsCert<'a> { + fn target_reached(&self) -> Result> { + let result = self.command_runner.run_with_args("openssl", &["x509", "-in", &self.get_cert_path(), "-noout", "-subject", "-checkend", &(30*DAYS_IN_SECONDS).to_string()]); + match result { + Err(e) => Err(Box::new(e)), + Ok(output) => match output.status.code() { + Some(0) => Ok(output.stdout == format!("subject=CN = {}\nCertificate will not expire\n", self.domain).as_bytes()), + Some(_) => Ok(false), + _ => Err("Didn't work".to_string().into()) + } + } + } + + fn execute(&self) -> Result<(), Box> { + let output = self.command_runner.run_with_args("openssl", &["req", "-x509", "-sha256", "-days", "90", "-key", &self.get_key_path(), "-out", &self.get_cert_path(), "-subj", &format!("/CN={}", self.domain)]); + match output { + Err(e) => Err(Box::new(e)), + Ok(_) => Ok(()) + } + } +} + +#[cfg(test)] +mod test { +}