From c723ee6a15ffb1c3aac5246a19853ace8324b535 Mon Sep 17 00:00:00 2001 From: Keagan McClelland Date: Fri, 12 Nov 2021 11:58:06 -0700 Subject: [PATCH] Feature/always gen certs (#784) * move SslManager to NetController and out of NginxController * always generate the certs, move that functionality to the net controller before any nginx configs are written * add info log * refactor to make net controller not responsible for nginx stuff * diff minimization * diff minimization * diff minimization * diff minimization * use net controller cert path instead of nginx controller cert path * remove unused imports * move location of cert mounts --- appmgr/src/backup/backup_bulk.rs | 7 +--- appmgr/src/net/mod.rs | 54 ++++++++++++++++++++++++----- appmgr/src/net/nginx.rs | 59 +++++++------------------------- appmgr/src/net/ssl.rs | 21 +++++++++++- appmgr/src/volume.rs | 4 +-- system-images/compat/Cargo.lock | 7 ++++ 6 files changed, 87 insertions(+), 65 deletions(-) diff --git a/appmgr/src/backup/backup_bulk.rs b/appmgr/src/backup/backup_bulk.rs index ed3acc243..63db057c0 100644 --- a/appmgr/src/backup/backup_bulk.rs +++ b/appmgr/src/backup/backup_bulk.rs @@ -331,12 +331,7 @@ async fn perform_backup( tx.save().await?; } - let (root_ca_key, root_ca_cert) = ctx - .net_controller - .nginx - .ssl_manager - .export_root_ca() - .await?; + let (root_ca_key, root_ca_cert) = ctx.net_controller.ssl.export_root_ca().await?; let mut os_backup_file = AtomicFile::new(backup_guard.as_ref().join("os-backup.cbor")).await?; os_backup_file .write_all( diff --git a/appmgr/src/net/mod.rs b/appmgr/src/net/mod.rs index b6eb413b9..b6505f3a8 100644 --- a/appmgr/src/net/mod.rs +++ b/appmgr/src/net/mod.rs @@ -27,6 +27,8 @@ pub mod ssl; pub mod tor; pub mod wifi; +const PACKAGE_CERT_PATH: &str = "/var/lib/embassy/ssl"; + #[command(subcommands(tor::tor))] pub fn net() -> Result<(), Error> { Ok(()) @@ -37,6 +39,7 @@ pub struct NetController { #[cfg(feature = "avahi")] pub mdns: MdnsController, pub nginx: NginxController, + pub ssl: SslManager, } impl NetController { #[instrument(skip(db))] @@ -47,7 +50,7 @@ impl NetController { db: SqlitePool, import_root_ca: Option<(PKey, X509)>, ) -> Result { - let ssl_manager = match import_root_ca { + let ssl = match import_root_ca { None => SslManager::init(db).await, Some(a) => SslManager::import_root_ca(db, a.0, a.1).await, }?; @@ -55,20 +58,29 @@ impl NetController { tor: TorController::init(embassyd_addr, embassyd_tor_key, tor_control).await?, #[cfg(feature = "avahi")] mdns: MdnsController::init(), - nginx: NginxController::init(PathBuf::from("/etc/nginx"), ssl_manager).await?, + nginx: NginxController::init(PathBuf::from("/etc/nginx"), &ssl).await?, + ssl, }) } + pub fn ssl_directory_for(&self, pkg_id: &PackageId) -> PathBuf { + PathBuf::from(format!("{}/{}", PACKAGE_CERT_PATH, pkg_id)) + } + #[instrument(skip(self, interfaces))] - pub async fn add< - 'a, - I: IntoIterator + Clone, - >( + pub async fn add<'a, I>( &self, pkg_id: &PackageId, ip: Ipv4Addr, interfaces: I, - ) -> Result<(), Error> { + ) -> Result<(), Error> + where + I: IntoIterator + Clone, + for<'b> &'b I: IntoIterator, + { + self.generate_certificate_mountpoint(pkg_id, &interfaces) + .await?; + let interfaces_tor = interfaces .clone() .into_iter() @@ -107,7 +119,7 @@ impl NetController { }, )), }); - self.nginx.add(pkg_id.clone(), ip, interfaces) + self.nginx.add(&self.ssl, pkg_id.clone(), ip, interfaces) } ); tor_res?; @@ -138,7 +150,31 @@ impl NetController { Ok(()) } + async fn generate_certificate_mountpoint<'a, I>( + &self, + pkg_id: &PackageId, + interfaces: &I, + ) -> Result<(), Error> + where + I: IntoIterator + Clone, + for<'b> &'b I: IntoIterator, + { + tracing::info!("Generating SSL Certificate mountpoints for {}", pkg_id); + let package_path = PathBuf::from(PACKAGE_CERT_PATH).join(pkg_id); + tokio::fs::create_dir_all(&package_path).await?; + Ok(for (id, _, key) in interfaces { + let dns_base = OnionAddressV3::from(&key.public()).get_address_without_dot_onion(); + let ssl_path_key = package_path.join(format!("{}.key.pem", id)); + let ssl_path_cert = package_path.join(format!("{}.cert.pem", id)); + let (key, chain) = self.ssl.certificate_for(&dns_base).await?; + tokio::try_join!( + crate::net::ssl::export_key(&key, &ssl_path_key), + crate::net::ssl::export_cert(&chain, &ssl_path_cert) + )?; + }) + } + pub async fn export_root_ca(&self) -> Result<(PKey, X509), Error> { - self.nginx.ssl_manager.export_root_ca().await + self.ssl.export_root_ca().await } } diff --git a/appmgr/src/net/nginx.rs b/appmgr/src/net/nginx.rs index 4ebe1abfc..cf199e8d1 100644 --- a/appmgr/src/net/nginx.rs +++ b/appmgr/src/net/nginx.rs @@ -15,23 +15,19 @@ use crate::util::{Invoke, Port}; use crate::{Error, ErrorKind, ResultExt}; pub struct NginxController { - nginx_root: PathBuf, - pub ssl_manager: SslManager, + pub nginx_root: PathBuf, inner: Mutex, } impl NginxController { - pub async fn init(nginx_root: PathBuf, ssl_manager: SslManager) -> Result { + pub async fn init(nginx_root: PathBuf, ssl_manager: &SslManager) -> Result { Ok(NginxController { - inner: Mutex::new(NginxControllerInner::init(&nginx_root, &ssl_manager).await?), - ssl_manager, + inner: Mutex::new(NginxControllerInner::init(&nginx_root, ssl_manager).await?), nginx_root, }) } - pub fn ssl_directory_for(&self, package: &PackageId) -> PathBuf { - self.nginx_root.join("ssl").join(package) - } pub async fn add>( &self, + ssl_manager: &SslManager, package: PackageId, ipv4: Ipv4Addr, interfaces: I, @@ -39,13 +35,7 @@ impl NginxController { self.inner .lock() .await - .add( - &self.nginx_root, - &self.ssl_manager, - package, - ipv4, - interfaces, - ) + .add(&self.nginx_root, ssl_manager, package, ipv4, interfaces) .await } pub async fn remove(&self, package: &PackageId) -> Result<(), Error> { @@ -66,17 +56,13 @@ impl NginxControllerInner { let inner = NginxControllerInner { interfaces: BTreeMap::new(), }; + // write main ssl key/cert to fs location let (key, cert) = ssl_manager.certificate_for(&get_hostname().await?).await?; let ssl_path_key = nginx_root.join(format!("ssl/embassy_main.key.pem")); let ssl_path_cert = nginx_root.join(format!("ssl/embassy_main.cert.pem")); tokio::try_join!( - tokio::fs::write(&ssl_path_key, key.private_key_to_pem_pkcs8()?), - tokio::fs::write( - &ssl_path_cert, - cert.into_iter() - .flat_map(|c| c.to_pem().unwrap()) - .collect::>() - ) + crate::net::ssl::export_key(&key, &ssl_path_key), + crate::net::ssl::export_cert(&cert, &ssl_path_cert), )?; Ok(inner) } @@ -104,34 +90,15 @@ impl NginxControllerInner { // get ssl certificate chain let (listen_args, ssl_certificate_line, ssl_certificate_key_line) = if lan_port_config.ssl { + // these have already been written by the net controller let package_path = nginx_root.join(format!("ssl/{}", package)); - tokio::fs::create_dir_all(package_path).await?; - let ssl_path_key = - nginx_root.join(format!("ssl/{}/{}.key.pem", package, id)); - let ssl_path_cert = - nginx_root.join(format!("ssl/{}/{}.cert.pem", package, id)); + let ssl_path_key = package_path.join(format!("{}.key.pem", id)); + let ssl_path_cert = package_path.join(format!("{}.cert.pem", id)); let (key, chain) = ssl_manager.certificate_for(&meta.dns_base).await?; - // write nginx ssl certs tokio::try_join!( - tokio::fs::write(&ssl_path_key, key.private_key_to_pem_pkcs8()?).map( - |res| res.with_ctx(|_| ( - ErrorKind::Filesystem, - ssl_path_key.display().to_string() - )) - ), - tokio::fs::write( - &ssl_path_cert, - chain - .into_iter() - .flat_map(|c| c.to_pem().unwrap()) - .collect::>() - ) - .map(|res| res.with_ctx(|_| ( - ErrorKind::Filesystem, - ssl_path_cert.display().to_string() - ))), + crate::net::ssl::export_key(&key, &ssl_path_key), + crate::net::ssl::export_cert(&chain, &ssl_path_cert) )?; - ( format!("{} ssl", lan_port_config.mapping), format!("ssl_certificate {};", ssl_path_cert.to_str().unwrap()), diff --git a/appmgr/src/net/ssl.rs b/appmgr/src/net/ssl.rs index 353efcce3..eff30c64d 100644 --- a/appmgr/src/net/ssl.rs +++ b/appmgr/src/net/ssl.rs @@ -1,6 +1,8 @@ use std::cmp::Ordering; +use std::path::Path; use color_eyre::eyre::eyre; +use futures::FutureExt; use openssl::asn1::{Asn1Integer, Asn1Time}; use openssl::bn::{BigNum, MsbOption}; use openssl::ec::{EcGroup, EcKey}; @@ -13,7 +15,7 @@ use sqlx::SqlitePool; use tokio::sync::Mutex; use tracing::instrument; -use crate::{Error, ErrorKind}; +use crate::{Error, ErrorKind, ResultExt}; static CERTIFICATE_VERSION: i32 = 2; // X509 version 3 is actually encoded as '2' in the cert because fuck you. @@ -256,6 +258,23 @@ impl SslManager { } } +pub async fn export_key(key: &PKey, target: &Path) -> Result<(), Error> { + tokio::fs::write(target, key.private_key_to_pem_pkcs8()?) + .map(|res| res.with_ctx(|_| (ErrorKind::Filesystem, target.display().to_string()))) + .await?; + Ok(()) +} +pub async fn export_cert(chain: &Vec, target: &Path) -> Result<(), Error> { + tokio::fs::write( + target, + chain + .into_iter() + .flat_map(|c| c.to_pem().unwrap()) + .collect::>(), + ) + .await?; + Ok(()) +} #[instrument] fn rand_serial() -> Result { let mut bn = BigNum::new()?; diff --git a/appmgr/src/volume.rs b/appmgr/src/volume.rs index 88ffec093..08cf7b311 100644 --- a/appmgr/src/volume.rs +++ b/appmgr/src/volume.rs @@ -215,9 +215,7 @@ impl Volume { } else { path.as_ref() }), - Volume::Certificate { interface_id: _ } => { - ctx.net_controller.nginx.ssl_directory_for(pkg_id) - } + Volume::Certificate { interface_id: _ } => ctx.net_controller.ssl_directory_for(pkg_id), Volume::Backup { .. } => backup_dir(pkg_id), } } diff --git a/system-images/compat/Cargo.lock b/system-images/compat/Cargo.lock index 303ee8933..d2bdd0b3a 100644 --- a/system-images/compat/Cargo.lock +++ b/system-images/compat/Cargo.lock @@ -889,6 +889,7 @@ dependencies = [ "patch-db", "pbkdf2", "pin-project", + "platforms", "prettytable-rs", "proptest", "proptest-derive", @@ -2267,6 +2268,12 @@ version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" +[[package]] +name = "platforms" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989d43012e2ca1c4a02507c67282691a0a3207f9dc67cec596b43fe925b3d325" + [[package]] name = "ppv-lite86" version = "0.2.10"