Introduce domain::Config

This commit is contained in:
Brian Picciano 2023-07-09 15:08:33 +02:00
parent 80e96c47fb
commit 7c68702ab8
7 changed files with 94 additions and 66 deletions

View File

@ -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};

View File

@ -33,7 +33,7 @@ pub struct ManagerImpl {
impl ManagerImpl {
pub async fn new<Store: acme::store::Store + Send + Sync + 'static>(
store: Store,
contact_email: &str,
config: &domain::ConfigACME,
) -> Result<Self, unexpected::Error> {
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]);

View File

@ -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<Self, NewDNSCheckerError> {
let resolver_addr = resolver_addr
.parse()
.map_err(|_| NewDNSCheckerError::InvalidResolverAddress)?;
let stream = udp::UdpClientStream::<tokio::net::UdpSocket>::new(resolver_addr);
pub async fn new(config: &domain::ConfigDNS) -> Result<Self, unexpected::Error> {
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::<tokio::net::UdpSocket>::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,

26
src/domain/config.rs Normal file
View File

@ -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<ConfigDNSTargetRecord>,
}
#[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<ConfigACME>,
}

View File

@ -163,7 +163,7 @@ pub trait Manager: Sync + Send + rustls::server::ResolvesServerCert {
pub struct ManagerImpl {
origin_store: Box<dyn origin::Store + Send + Sync>,
domain_config_store: Box<dyn store::Store + Send + Sync>,
domain_store: Box<dyn store::Store + Send + Sync>,
domain_checker: checker::DNSChecker,
acme_manager: Option<Box<dyn acme::manager::Manager + Send + Sync>>,
}
@ -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<unexpected::Error>,
origin_store: OriginStore,
domain_config_store: DomainConfigStore,
domain_store: DomainStore,
domain_checker: checker::DNSChecker,
acme_manager: Option<AcmeManager>,
) -> sync::Arc<Self> {
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<dyn acme::manager::Manager + Send + Sync>),
@ -225,7 +225,7 @@ impl ManagerImpl {
impl Manager for ManagerImpl {
fn get_config(&self, domain: &domain::Name) -> Result<domain::Domain, GetConfigError> {
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<util::BoxByteStream, GetFileError> {
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<Vec<domain::Name>, unexpected::Error> {
self.domain_config_store.all_domains()
self.domain_store.all_domains()
}
}

View File

@ -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<Self> {
pub fn new(dir_path: &path::Path) -> io::Result<Self> {
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")
}
}

View File

@ -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<SocketAddr>,
@ -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<path::PathBuf>,
#[arg(long, env = "DOMANI_DOMAIN_ACME_CONTACT_EMAIL")]
domain_acme_contact_email: Option<String>,
}
@ -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 =