diff --git a/TODO b/TODO index e55da09..64ea93b 100644 --- a/TODO +++ b/TODO @@ -1,2 +1 @@ -- make domain_manager implement rusttls cert resolver - Try to switch from Arc to Box where possible diff --git a/src/domain/acme.rs b/src/domain/acme.rs index 94150d1..8b2e126 100644 --- a/src/domain/acme.rs +++ b/src/domain/acme.rs @@ -1,5 +1,4 @@ pub mod manager; -pub mod resolver; pub mod store; mod private_key; diff --git a/src/domain/acme/manager.rs b/src/domain/acme/manager.rs index f12336b..2eb4b2c 100644 --- a/src/domain/acme/manager.rs +++ b/src/domain/acme/manager.rs @@ -1,11 +1,13 @@ use std::{future, pin, sync, time}; +use crate::domain::acme::{Certificate, PrivateKey}; use crate::domain::{self, acme}; use crate::error::unexpected::{self, Intoable, Mappable}; const LETS_ENCRYPT_URL: &str = "https://acme-v02.api.letsencrypt.org/directory"; pub type GetHttp01ChallengeKeyError = acme::store::GetHttp01ChallengeKeyError; +pub type GetCertificateError = acme::store::GetCertificateError; #[mockall::automock] pub trait Manager: Sync + Send { @@ -14,6 +16,12 @@ pub trait Manager: Sync + Send { domain: domain::Name, ) -> pin::Pin> + Send + 'mgr>>; fn get_http01_challenge_key(&self, token: &str) -> Result; + + /// Returned vec is guaranteed to have len > 0 + fn get_certificate( + &self, + domain: &str, + ) -> Result<(PrivateKey, Vec), GetCertificateError>; } struct ManagerImpl { @@ -282,4 +290,12 @@ impl Manager for ManagerImpl { fn get_http01_challenge_key(&self, token: &str) -> Result { self.store.get_http01_challenge_key(token) } + + /// Returned vec is guaranteed to have len > 0 + fn get_certificate( + &self, + domain: &str, + ) -> Result<(PrivateKey, Vec), GetCertificateError> { + self.store.get_certificate(domain) + } } diff --git a/src/domain/acme/resolver.rs b/src/domain/acme/resolver.rs deleted file mode 100644 index f9d7908..0000000 --- a/src/domain/acme/resolver.rs +++ /dev/null @@ -1,44 +0,0 @@ -use crate::domain::acme::store; -use crate::error::unexpected::Mappable; - -use std::sync; - -struct CertResolver(sync::Arc); - -pub fn new( - store: sync::Arc, -) -> sync::Arc { - return sync::Arc::new(CertResolver(store)); -} - -impl rustls::server::ResolvesServerCert for CertResolver { - fn resolve( - &self, - client_hello: rustls::server::ClientHello<'_>, - ) -> Option> { - let domain = client_hello.server_name()?; - - match self.0.get_certificate(domain) { - Err(store::GetCertificateError::NotFound) => { - log::warn!("No cert found for domain {domain}"); - Ok(None) - } - Err(store::GetCertificateError::Unexpected(err)) => Err(err), - Ok((key, cert)) => { - match rustls::sign::any_supported_type(&key.into()).or_unexpected() { - Err(err) => Err(err), - Ok(key) => Ok(Some(sync::Arc::new(rustls::sign::CertifiedKey { - cert: cert.into_iter().map(|cert| cert.into()).collect(), - key, - ocsp: None, - sct_list: None, - }))), - } - } - } - .unwrap_or_else(|err| { - log::error!("Unexpected error getting cert for domain {domain}: {err}"); - None - }) - } -} diff --git a/src/domain/manager.rs b/src/domain/manager.rs index 7e2f9b1..44cc9c8 100644 --- a/src/domain/manager.rs +++ b/src/domain/manager.rs @@ -117,8 +117,8 @@ impl From for SyncWithConfigError { pub type GetAcmeHttp01ChallengeKeyError = acme::manager::GetHttp01ChallengeKeyError; -#[mockall::automock] -pub trait Manager: Sync + Send { +//#[mockall::automock] +pub trait Manager: Sync + Send + rustls::server::ResolvesServerCert { fn get_config(&self, domain: &domain::Name) -> Result; fn get_origin( @@ -272,3 +272,35 @@ impl Manager for ManagerImpl { self.domain_config_store.all_domains() } } + +impl rustls::server::ResolvesServerCert for ManagerImpl { + fn resolve( + &self, + client_hello: rustls::server::ClientHello<'_>, + ) -> Option> { + let domain = client_hello.server_name()?; + + match self.acme_manager.as_ref()?.get_certificate(domain) { + Err(acme::manager::GetCertificateError::NotFound) => { + log::warn!("No cert found for domain {domain}"); + Ok(None) + } + Err(acme::manager::GetCertificateError::Unexpected(err)) => Err(err), + Ok((key, cert)) => { + match rustls::sign::any_supported_type(&key.into()).or_unexpected() { + Err(err) => Err(err), + Ok(key) => Ok(Some(sync::Arc::new(rustls::sign::CertifiedKey { + cert: cert.into_iter().map(|cert| cert.into()).collect(), + key, + ocsp: None, + sct_list: None, + }))), + } + } + } + .unwrap_or_else(|err| { + log::error!("Unexpected error getting cert for domain {domain}: {err}"); + None + }) + } +} diff --git a/src/main.rs b/src/main.rs index c5eaa12..658d5f5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +#![feature(trait_upcasting)] + use clap::Parser; use futures::stream::StreamExt; use signal_hook_tokio::Signals; @@ -61,7 +63,6 @@ struct Cli { #[derive(Clone)] struct HTTPSParams { https_listen_addr: SocketAddr, - domain_acme_store: sync::Arc, domain_acme_manager: sync::Arc, } @@ -101,16 +102,13 @@ async fn main() { // settings. let domain_acme_contact_email = config.domain_acme_contact_email.unwrap(); - let domain_acme_manager = domiply::domain::acme::manager::new( - domain_acme_store.clone(), - &domain_acme_contact_email, - ) - .await - .expect("domain acme manager initialization failed"); + let domain_acme_manager = + domiply::domain::acme::manager::new(domain_acme_store, &domain_acme_contact_email) + .await + .expect("domain acme manager initialization failed"); Some(HTTPSParams { https_listen_addr, - domain_acme_store, domain_acme_manager, }) } else { @@ -136,7 +134,7 @@ async fn main() { config.http_domain.clone(), https_params.map(|p| domiply::service::http::HTTPSParams { listen_addr: p.https_listen_addr, - cert_resolver: domiply::domain::acme::resolver::new(p.domain_acme_store), + cert_resolver: domain_manager.clone(), }), );