From 7c68702ab8ce214c35a075b5b339151f8c8707ca Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Sun, 9 Jul 2023 15:08:33 +0200 Subject: [PATCH] Introduce domain::Config --- src/domain.rs | 2 ++ src/domain/acme/manager.rs | 4 +-- src/domain/checker.rs | 29 ++++++--------- src/domain/config.rs | 26 ++++++++++++++ src/domain/manager.rs | 16 ++++----- src/domain/store.rs | 10 +++--- src/main.rs | 73 +++++++++++++++++++++----------------- 7 files changed, 94 insertions(+), 66 deletions(-) create mode 100644 src/domain/config.rs diff --git a/src/domain.rs b/src/domain.rs index 74359fc..76c64aa 100644 --- a/src/domain.rs +++ b/src/domain.rs @@ -1,9 +1,11 @@ pub mod acme; pub mod checker; +mod config; pub mod manager; mod name; pub mod store; +pub use config::*; pub use name::*; use crate::error::unexpected::{self, Mappable}; diff --git a/src/domain/acme/manager.rs b/src/domain/acme/manager.rs index d5b61cb..c76900e 100644 --- a/src/domain/acme/manager.rs +++ b/src/domain/acme/manager.rs @@ -33,7 +33,7 @@ pub struct ManagerImpl { impl ManagerImpl { pub async fn new( store: Store, - contact_email: &str, + config: &domain::ConfigACME, ) -> Result { let dir = acme2::DirectoryBuilder::new(LETS_ENCRYPT_URL.to_string()) .build() @@ -41,7 +41,7 @@ impl ManagerImpl { .or_unexpected_while("creating acme2 directory builder")?; let mut contact = String::from("mailto:"); - contact.push_str(contact_email); + contact.push_str(config.contact_email.as_str()); let mut builder = acme2::AccountBuilder::new(dir); builder.contact(vec![contact]); diff --git a/src/domain/checker.rs b/src/domain/checker.rs index 216a683..17acc2e 100644 --- a/src/domain/checker.rs +++ b/src/domain/checker.rs @@ -8,15 +8,6 @@ use trust_dns_client::client::{AsyncClient, ClientHandle}; use trust_dns_client::rr::{DNSClass, Name, RData, RecordType}; use trust_dns_client::udp; -#[derive(thiserror::Error, Debug)] -pub enum NewDNSCheckerError { - #[error("invalid resolver address")] - InvalidResolverAddress, - - #[error(transparent)] - Unexpected(#[from] unexpected::Error), -} - #[derive(thiserror::Error, Debug)] pub enum CheckDomainError { #[error("target A not set")] @@ -37,19 +28,19 @@ pub struct DNSChecker { } impl DNSChecker { - pub async fn new( - target_a: net::Ipv4Addr, - resolver_addr: &str, - ) -> Result { - let resolver_addr = resolver_addr - .parse() - .map_err(|_| NewDNSCheckerError::InvalidResolverAddress)?; - - let stream = udp::UdpClientStream::::new(resolver_addr); + pub async fn new(config: &domain::ConfigDNS) -> Result { + let target_a = match config + .target_records + .get(0) + .expect("at least one target record expected") + { + domain::ConfigDNSTargetRecord::A { addr } => addr.clone(), + }; + let stream = udp::UdpClientStream::::new(config.resolver_addr); let (client, bg) = AsyncClient::connect(stream).await.or_unexpected()?; - tokio::spawn(bg); + // TODO there should be a mechanism to clean this up Ok(Self { target_a, diff --git a/src/domain/config.rs b/src/domain/config.rs new file mode 100644 index 0000000..e8da538 --- /dev/null +++ b/src/domain/config.rs @@ -0,0 +1,26 @@ +use std::{net, path}; + +use serde::Deserialize; + +#[derive(Deserialize)] +pub enum ConfigDNSTargetRecord { + A { addr: net::Ipv4Addr }, +} + +#[derive(Deserialize)] +pub struct ConfigDNS { + pub resolver_addr: net::SocketAddr, + pub target_records: Vec, +} + +#[derive(Deserialize)] +pub struct ConfigACME { + pub contact_email: String, +} + +#[derive(Deserialize)] +pub struct Config { + pub store_dir_path: path::PathBuf, + pub dns: ConfigDNS, + pub acme: Option, +} diff --git a/src/domain/manager.rs b/src/domain/manager.rs index ea443a9..937cd4c 100644 --- a/src/domain/manager.rs +++ b/src/domain/manager.rs @@ -163,7 +163,7 @@ pub trait Manager: Sync + Send + rustls::server::ResolvesServerCert { pub struct ManagerImpl { origin_store: Box, - domain_config_store: Box, + domain_store: Box, domain_checker: checker::DNSChecker, acme_manager: Option>, } @@ -171,18 +171,18 @@ pub struct ManagerImpl { impl ManagerImpl { pub fn new< OriginStore: origin::Store + Send + Sync + 'static, - DomainConfigStore: store::Store + Send + Sync + 'static, + DomainStore: store::Store + Send + Sync + 'static, AcmeManager: acme::manager::Manager + Send + Sync + 'static, >( task_stack: &mut util::TaskStack, origin_store: OriginStore, - domain_config_store: DomainConfigStore, + domain_store: DomainStore, domain_checker: checker::DNSChecker, acme_manager: Option, ) -> sync::Arc { let manager = sync::Arc::new(ManagerImpl { origin_store: Box::from(origin_store), - domain_config_store: Box::from(domain_config_store), + domain_store: Box::from(domain_store), domain_checker, acme_manager: acme_manager .map(|m| Box::new(m) as Box), @@ -225,7 +225,7 @@ impl ManagerImpl { impl Manager for ManagerImpl { fn get_config(&self, domain: &domain::Name) -> Result { - Ok(self.domain_config_store.get(domain)?) + Ok(self.domain_store.get(domain)?) } fn get_file<'store>( @@ -233,7 +233,7 @@ impl Manager for ManagerImpl { domain: &domain::Name, path: &str, ) -> Result { - let config = self.domain_config_store.get(domain)?; + let config = self.domain_store.get(domain)?; let f = self.origin_store.get_file(&config.origin_descr, path)?; Ok(f) } @@ -267,7 +267,7 @@ impl Manager for ManagerImpl { self.origin_store.sync(&config.origin_descr)?; - self.domain_config_store.set(&domain, &config)?; + self.domain_store.set(&domain, &config)?; self.sync_cert(domain).await?; @@ -287,7 +287,7 @@ impl Manager for ManagerImpl { } fn all_domains(&self) -> Result, unexpected::Error> { - self.domain_config_store.all_domains() + self.domain_store.all_domains() } } diff --git a/src/domain/store.rs b/src/domain/store.rs index 4072b5f..33deb91 100644 --- a/src/domain/store.rs +++ b/src/domain/store.rs @@ -1,4 +1,4 @@ -use std::path::{Path, PathBuf}; +use std::path; use std::str::FromStr; use std::{fs, io}; @@ -28,22 +28,22 @@ pub trait Store { } pub struct FSStore { - dir_path: PathBuf, + dir_path: path::PathBuf, } impl FSStore { - pub fn new(dir_path: &Path) -> io::Result { + pub fn new(dir_path: &path::Path) -> io::Result { fs::create_dir_all(dir_path)?; Ok(Self { dir_path: dir_path.into(), }) } - fn config_dir_path(&self, domain: &domain::Name) -> PathBuf { + fn config_dir_path(&self, domain: &domain::Name) -> path::PathBuf { self.dir_path.join(domain.as_str()) } - fn config_file_path(&self, domain: &domain::Name) -> PathBuf { + fn config_file_path(&self, domain: &domain::Name) -> path::PathBuf { self.config_dir_path(domain).join("config.json") } } diff --git a/src/main.rs b/src/main.rs index a227714..cb2498c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,8 +33,7 @@ struct Cli { long, help = "E.g. '[::]:443', if given then SSL certs will automatically be retrieved for all domains using LetsEncrypt", env = "DOMANI_HTTPS_LISTEN_ADDR", - requires = "domain_acme_contact_email", - requires = "domain_acme_store_dir_path" + requires = "domain_acme_contact_email" )] https_listen_addr: Option, @@ -53,9 +52,6 @@ struct Cli { #[arg(long, required = true, env = "DOMANI_DOMAIN_CONFIG_STORE_DIR_PATH")] domain_config_store_dir_path: path::PathBuf, - #[arg(long, env = "DOMANI_DOMAIN_ACME_STORE_DIR_PATH")] - domain_acme_store_dir_path: Option, - #[arg(long, env = "DOMANI_DOMAIN_ACME_CONTACT_EMAIL")] domain_acme_contact_email: Option, } @@ -77,38 +73,57 @@ async fn main() { store_dir_path: config.origin_store_git_dir_path, }; + let domain_config = domani::domain::Config { + store_dir_path: config.domain_config_store_dir_path, + dns: domani::domain::ConfigDNS { + resolver_addr: std::net::SocketAddr::from_str( + config.domain_checker_resolver_addr.as_str(), + ) + .expect("parsing resolver addr"), + + target_records: vec![domani::domain::ConfigDNSTargetRecord::A { + addr: config.domain_checker_target_a, + }], + }, + acme: config + .https_listen_addr + .and(Some(domani::domain::ConfigACME { + contact_email: config.domain_acme_contact_email.unwrap(), + })), + }; + + let service_http_config = domani::service::http::Config { + http_addr: config.http_listen_addr, + https_addr: config.https_listen_addr, + primary_domain: config.http_domain, + target_a: config.domain_checker_target_a, + passphrase: config.passphrase, + }; + let origin_store = domani::origin::git::FSStore::new(&origin_config) .expect("git origin store initialization failed"); - let domain_checker = domani::domain::checker::DNSChecker::new( - config.domain_checker_target_a, - &config.domain_checker_resolver_addr, - ) - .await - .expect("domain checker initialization failed"); + let domain_checker = domani::domain::checker::DNSChecker::new(&domain_config.dns) + .await + .expect("domain checker initialization failed"); let domain_config_store = - domani::domain::store::FSStore::new(&config.domain_config_store_dir_path) + domani::domain::store::FSStore::new(&domain_config.store_dir_path.join("domains")) .expect("domain config store initialization failed"); - let domain_acme_manager = if config.https_listen_addr.is_some() { - let domain_acme_store_dir_path = config.domain_acme_store_dir_path.unwrap(); + let domain_acme_manager = if service_http_config.https_addr.is_some() { + let acme_config = domain_config + .acme + .expect("acme configuration must be set if https is enabled"); let domain_acme_store = - domani::domain::acme::store::FSStore::new(&domain_acme_store_dir_path) + domani::domain::acme::store::FSStore::new(&domain_config.store_dir_path.join("acme")) .expect("domain acme store initialization failed"); - // if https_listen_addr is set then domain_acme_contact_email is required, see the Cli/clap - // settings. - let domain_acme_contact_email = config.domain_acme_contact_email.unwrap(); - Some( - domani::domain::acme::manager::ManagerImpl::new( - domain_acme_store, - &domain_acme_contact_email, - ) - .await - .expect("domain acme manager initialization failed"), + domani::domain::acme::manager::ManagerImpl::new(domain_acme_store, &acme_config) + .await + .expect("domain acme manager initialization failed"), ) } else { None @@ -128,13 +143,7 @@ async fn main() { &mut task_stack, domain_manager.clone(), domain_manager.clone(), - domani::service::http::Config { - http_addr: config.http_listen_addr, - https_addr: config.https_listen_addr, - primary_domain: config.http_domain, - target_a: config.domain_checker_target_a, - passphrase: config.passphrase, - }, + service_http_config, ); let mut signals =