Introduce domain::Config
This commit is contained in:
parent
80e96c47fb
commit
7c68702ab8
@ -1,9 +1,11 @@
|
|||||||
pub mod acme;
|
pub mod acme;
|
||||||
pub mod checker;
|
pub mod checker;
|
||||||
|
mod config;
|
||||||
pub mod manager;
|
pub mod manager;
|
||||||
mod name;
|
mod name;
|
||||||
pub mod store;
|
pub mod store;
|
||||||
|
|
||||||
|
pub use config::*;
|
||||||
pub use name::*;
|
pub use name::*;
|
||||||
|
|
||||||
use crate::error::unexpected::{self, Mappable};
|
use crate::error::unexpected::{self, Mappable};
|
||||||
|
@ -33,7 +33,7 @@ pub struct ManagerImpl {
|
|||||||
impl ManagerImpl {
|
impl ManagerImpl {
|
||||||
pub async fn new<Store: acme::store::Store + Send + Sync + 'static>(
|
pub async fn new<Store: acme::store::Store + Send + Sync + 'static>(
|
||||||
store: Store,
|
store: Store,
|
||||||
contact_email: &str,
|
config: &domain::ConfigACME,
|
||||||
) -> Result<Self, unexpected::Error> {
|
) -> Result<Self, unexpected::Error> {
|
||||||
let dir = acme2::DirectoryBuilder::new(LETS_ENCRYPT_URL.to_string())
|
let dir = acme2::DirectoryBuilder::new(LETS_ENCRYPT_URL.to_string())
|
||||||
.build()
|
.build()
|
||||||
@ -41,7 +41,7 @@ impl ManagerImpl {
|
|||||||
.or_unexpected_while("creating acme2 directory builder")?;
|
.or_unexpected_while("creating acme2 directory builder")?;
|
||||||
|
|
||||||
let mut contact = String::from("mailto:");
|
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);
|
let mut builder = acme2::AccountBuilder::new(dir);
|
||||||
builder.contact(vec![contact]);
|
builder.contact(vec![contact]);
|
||||||
|
@ -8,15 +8,6 @@ use trust_dns_client::client::{AsyncClient, ClientHandle};
|
|||||||
use trust_dns_client::rr::{DNSClass, Name, RData, RecordType};
|
use trust_dns_client::rr::{DNSClass, Name, RData, RecordType};
|
||||||
use trust_dns_client::udp;
|
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)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum CheckDomainError {
|
pub enum CheckDomainError {
|
||||||
#[error("target A not set")]
|
#[error("target A not set")]
|
||||||
@ -37,19 +28,19 @@ pub struct DNSChecker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DNSChecker {
|
impl DNSChecker {
|
||||||
pub async fn new(
|
pub async fn new(config: &domain::ConfigDNS) -> Result<Self, unexpected::Error> {
|
||||||
target_a: net::Ipv4Addr,
|
let target_a = match config
|
||||||
resolver_addr: &str,
|
.target_records
|
||||||
) -> Result<Self, NewDNSCheckerError> {
|
.get(0)
|
||||||
let resolver_addr = resolver_addr
|
.expect("at least one target record expected")
|
||||||
.parse()
|
{
|
||||||
.map_err(|_| NewDNSCheckerError::InvalidResolverAddress)?;
|
domain::ConfigDNSTargetRecord::A { addr } => addr.clone(),
|
||||||
|
};
|
||||||
let stream = udp::UdpClientStream::<tokio::net::UdpSocket>::new(resolver_addr);
|
|
||||||
|
|
||||||
|
let stream = udp::UdpClientStream::<tokio::net::UdpSocket>::new(config.resolver_addr);
|
||||||
let (client, bg) = AsyncClient::connect(stream).await.or_unexpected()?;
|
let (client, bg) = AsyncClient::connect(stream).await.or_unexpected()?;
|
||||||
|
|
||||||
tokio::spawn(bg);
|
tokio::spawn(bg);
|
||||||
|
// TODO there should be a mechanism to clean this up
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
target_a,
|
target_a,
|
||||||
|
26
src/domain/config.rs
Normal file
26
src/domain/config.rs
Normal 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>,
|
||||||
|
}
|
@ -163,7 +163,7 @@ pub trait Manager: Sync + Send + rustls::server::ResolvesServerCert {
|
|||||||
|
|
||||||
pub struct ManagerImpl {
|
pub struct ManagerImpl {
|
||||||
origin_store: Box<dyn origin::Store + Send + Sync>,
|
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,
|
domain_checker: checker::DNSChecker,
|
||||||
acme_manager: Option<Box<dyn acme::manager::Manager + Send + Sync>>,
|
acme_manager: Option<Box<dyn acme::manager::Manager + Send + Sync>>,
|
||||||
}
|
}
|
||||||
@ -171,18 +171,18 @@ pub struct ManagerImpl {
|
|||||||
impl ManagerImpl {
|
impl ManagerImpl {
|
||||||
pub fn new<
|
pub fn new<
|
||||||
OriginStore: origin::Store + Send + Sync + 'static,
|
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,
|
AcmeManager: acme::manager::Manager + Send + Sync + 'static,
|
||||||
>(
|
>(
|
||||||
task_stack: &mut util::TaskStack<unexpected::Error>,
|
task_stack: &mut util::TaskStack<unexpected::Error>,
|
||||||
origin_store: OriginStore,
|
origin_store: OriginStore,
|
||||||
domain_config_store: DomainConfigStore,
|
domain_store: DomainStore,
|
||||||
domain_checker: checker::DNSChecker,
|
domain_checker: checker::DNSChecker,
|
||||||
acme_manager: Option<AcmeManager>,
|
acme_manager: Option<AcmeManager>,
|
||||||
) -> sync::Arc<Self> {
|
) -> sync::Arc<Self> {
|
||||||
let manager = sync::Arc::new(ManagerImpl {
|
let manager = sync::Arc::new(ManagerImpl {
|
||||||
origin_store: Box::from(origin_store),
|
origin_store: Box::from(origin_store),
|
||||||
domain_config_store: Box::from(domain_config_store),
|
domain_store: Box::from(domain_store),
|
||||||
domain_checker,
|
domain_checker,
|
||||||
acme_manager: acme_manager
|
acme_manager: acme_manager
|
||||||
.map(|m| Box::new(m) as Box<dyn acme::manager::Manager + Send + Sync>),
|
.map(|m| Box::new(m) as Box<dyn acme::manager::Manager + Send + Sync>),
|
||||||
@ -225,7 +225,7 @@ impl ManagerImpl {
|
|||||||
|
|
||||||
impl Manager for ManagerImpl {
|
impl Manager for ManagerImpl {
|
||||||
fn get_config(&self, domain: &domain::Name) -> Result<domain::Domain, GetConfigError> {
|
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>(
|
fn get_file<'store>(
|
||||||
@ -233,7 +233,7 @@ impl Manager for ManagerImpl {
|
|||||||
domain: &domain::Name,
|
domain: &domain::Name,
|
||||||
path: &str,
|
path: &str,
|
||||||
) -> Result<util::BoxByteStream, GetFileError> {
|
) -> 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)?;
|
let f = self.origin_store.get_file(&config.origin_descr, path)?;
|
||||||
Ok(f)
|
Ok(f)
|
||||||
}
|
}
|
||||||
@ -267,7 +267,7 @@ impl Manager for ManagerImpl {
|
|||||||
|
|
||||||
self.origin_store.sync(&config.origin_descr)?;
|
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?;
|
self.sync_cert(domain).await?;
|
||||||
|
|
||||||
@ -287,7 +287,7 @@ impl Manager for ManagerImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn all_domains(&self) -> Result<Vec<domain::Name>, unexpected::Error> {
|
fn all_domains(&self) -> Result<Vec<domain::Name>, unexpected::Error> {
|
||||||
self.domain_config_store.all_domains()
|
self.domain_store.all_domains()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::{fs, io};
|
use std::{fs, io};
|
||||||
|
|
||||||
@ -28,22 +28,22 @@ pub trait Store {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct FSStore {
|
pub struct FSStore {
|
||||||
dir_path: PathBuf,
|
dir_path: path::PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FSStore {
|
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)?;
|
fs::create_dir_all(dir_path)?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
dir_path: dir_path.into(),
|
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())
|
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")
|
self.config_dir_path(domain).join("config.json")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
73
src/main.rs
73
src/main.rs
@ -33,8 +33,7 @@ struct Cli {
|
|||||||
long,
|
long,
|
||||||
help = "E.g. '[::]:443', if given then SSL certs will automatically be retrieved for all domains using LetsEncrypt",
|
help = "E.g. '[::]:443', if given then SSL certs will automatically be retrieved for all domains using LetsEncrypt",
|
||||||
env = "DOMANI_HTTPS_LISTEN_ADDR",
|
env = "DOMANI_HTTPS_LISTEN_ADDR",
|
||||||
requires = "domain_acme_contact_email",
|
requires = "domain_acme_contact_email"
|
||||||
requires = "domain_acme_store_dir_path"
|
|
||||||
)]
|
)]
|
||||||
https_listen_addr: Option<SocketAddr>,
|
https_listen_addr: Option<SocketAddr>,
|
||||||
|
|
||||||
@ -53,9 +52,6 @@ struct Cli {
|
|||||||
#[arg(long, required = true, env = "DOMANI_DOMAIN_CONFIG_STORE_DIR_PATH")]
|
#[arg(long, required = true, env = "DOMANI_DOMAIN_CONFIG_STORE_DIR_PATH")]
|
||||||
domain_config_store_dir_path: path::PathBuf,
|
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")]
|
#[arg(long, env = "DOMANI_DOMAIN_ACME_CONTACT_EMAIL")]
|
||||||
domain_acme_contact_email: Option<String>,
|
domain_acme_contact_email: Option<String>,
|
||||||
}
|
}
|
||||||
@ -77,38 +73,57 @@ async fn main() {
|
|||||||
store_dir_path: config.origin_store_git_dir_path,
|
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)
|
let origin_store = domani::origin::git::FSStore::new(&origin_config)
|
||||||
.expect("git origin store initialization failed");
|
.expect("git origin store initialization failed");
|
||||||
|
|
||||||
let domain_checker = domani::domain::checker::DNSChecker::new(
|
let domain_checker = domani::domain::checker::DNSChecker::new(&domain_config.dns)
|
||||||
config.domain_checker_target_a,
|
.await
|
||||||
&config.domain_checker_resolver_addr,
|
.expect("domain checker initialization failed");
|
||||||
)
|
|
||||||
.await
|
|
||||||
.expect("domain checker initialization failed");
|
|
||||||
|
|
||||||
let domain_config_store =
|
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");
|
.expect("domain config store initialization failed");
|
||||||
|
|
||||||
let domain_acme_manager = if config.https_listen_addr.is_some() {
|
let domain_acme_manager = if service_http_config.https_addr.is_some() {
|
||||||
let domain_acme_store_dir_path = config.domain_acme_store_dir_path.unwrap();
|
let acme_config = domain_config
|
||||||
|
.acme
|
||||||
|
.expect("acme configuration must be set if https is enabled");
|
||||||
|
|
||||||
let domain_acme_store =
|
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");
|
.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(
|
Some(
|
||||||
domani::domain::acme::manager::ManagerImpl::new(
|
domani::domain::acme::manager::ManagerImpl::new(domain_acme_store, &acme_config)
|
||||||
domain_acme_store,
|
.await
|
||||||
&domain_acme_contact_email,
|
.expect("domain acme manager initialization failed"),
|
||||||
)
|
|
||||||
.await
|
|
||||||
.expect("domain acme manager initialization failed"),
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@ -128,13 +143,7 @@ async fn main() {
|
|||||||
&mut task_stack,
|
&mut task_stack,
|
||||||
domain_manager.clone(),
|
domain_manager.clone(),
|
||||||
domain_manager.clone(),
|
domain_manager.clone(),
|
||||||
domani::service::http::Config {
|
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 mut signals =
|
let mut signals =
|
||||||
|
Loading…
Reference in New Issue
Block a user