Make domain manager do cert resolving for tls

This commit is contained in:
Brian Picciano 2023-06-21 13:47:04 +02:00
parent 51cb6aadce
commit 289a185d42
6 changed files with 57 additions and 57 deletions

1
TODO
View File

@ -1,2 +1 @@
- make domain_manager implement rusttls cert resolver
- Try to switch from Arc to Box where possible - Try to switch from Arc to Box where possible

View File

@ -1,5 +1,4 @@
pub mod manager; pub mod manager;
pub mod resolver;
pub mod store; pub mod store;
mod private_key; mod private_key;

View File

@ -1,11 +1,13 @@
use std::{future, pin, sync, time}; use std::{future, pin, sync, time};
use crate::domain::acme::{Certificate, PrivateKey};
use crate::domain::{self, acme}; use crate::domain::{self, acme};
use crate::error::unexpected::{self, Intoable, Mappable}; use crate::error::unexpected::{self, Intoable, Mappable};
const LETS_ENCRYPT_URL: &str = "https://acme-v02.api.letsencrypt.org/directory"; const LETS_ENCRYPT_URL: &str = "https://acme-v02.api.letsencrypt.org/directory";
pub type GetHttp01ChallengeKeyError = acme::store::GetHttp01ChallengeKeyError; pub type GetHttp01ChallengeKeyError = acme::store::GetHttp01ChallengeKeyError;
pub type GetCertificateError = acme::store::GetCertificateError;
#[mockall::automock] #[mockall::automock]
pub trait Manager: Sync + Send { pub trait Manager: Sync + Send {
@ -14,6 +16,12 @@ pub trait Manager: Sync + Send {
domain: domain::Name, domain: domain::Name,
) -> pin::Pin<Box<dyn future::Future<Output = Result<(), unexpected::Error>> + Send + 'mgr>>; ) -> pin::Pin<Box<dyn future::Future<Output = Result<(), unexpected::Error>> + Send + 'mgr>>;
fn get_http01_challenge_key(&self, token: &str) -> Result<String, GetHttp01ChallengeKeyError>; fn get_http01_challenge_key(&self, token: &str) -> Result<String, GetHttp01ChallengeKeyError>;
/// Returned vec is guaranteed to have len > 0
fn get_certificate(
&self,
domain: &str,
) -> Result<(PrivateKey, Vec<Certificate>), GetCertificateError>;
} }
struct ManagerImpl { struct ManagerImpl {
@ -282,4 +290,12 @@ impl Manager for ManagerImpl {
fn get_http01_challenge_key(&self, token: &str) -> Result<String, GetHttp01ChallengeKeyError> { fn get_http01_challenge_key(&self, token: &str) -> Result<String, GetHttp01ChallengeKeyError> {
self.store.get_http01_challenge_key(token) self.store.get_http01_challenge_key(token)
} }
/// Returned vec is guaranteed to have len > 0
fn get_certificate(
&self,
domain: &str,
) -> Result<(PrivateKey, Vec<Certificate>), GetCertificateError> {
self.store.get_certificate(domain)
}
} }

View File

@ -1,44 +0,0 @@
use crate::domain::acme::store;
use crate::error::unexpected::Mappable;
use std::sync;
struct CertResolver(sync::Arc<dyn store::Store>);
pub fn new(
store: sync::Arc<dyn store::Store>,
) -> sync::Arc<dyn rustls::server::ResolvesServerCert> {
return sync::Arc::new(CertResolver(store));
}
impl rustls::server::ResolvesServerCert for CertResolver {
fn resolve(
&self,
client_hello: rustls::server::ClientHello<'_>,
) -> Option<sync::Arc<rustls::sign::CertifiedKey>> {
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
})
}
}

View File

@ -117,8 +117,8 @@ impl From<config::SetError> for SyncWithConfigError {
pub type GetAcmeHttp01ChallengeKeyError = acme::manager::GetHttp01ChallengeKeyError; pub type GetAcmeHttp01ChallengeKeyError = acme::manager::GetHttp01ChallengeKeyError;
#[mockall::automock] //#[mockall::automock]
pub trait Manager: Sync + Send { pub trait Manager: Sync + Send + rustls::server::ResolvesServerCert {
fn get_config(&self, domain: &domain::Name) -> Result<config::Config, GetConfigError>; fn get_config(&self, domain: &domain::Name) -> Result<config::Config, GetConfigError>;
fn get_origin( fn get_origin(
@ -272,3 +272,35 @@ impl Manager for ManagerImpl {
self.domain_config_store.all_domains() self.domain_config_store.all_domains()
} }
} }
impl rustls::server::ResolvesServerCert for ManagerImpl {
fn resolve(
&self,
client_hello: rustls::server::ClientHello<'_>,
) -> Option<sync::Arc<rustls::sign::CertifiedKey>> {
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
})
}
}

View File

@ -1,3 +1,5 @@
#![feature(trait_upcasting)]
use clap::Parser; use clap::Parser;
use futures::stream::StreamExt; use futures::stream::StreamExt;
use signal_hook_tokio::Signals; use signal_hook_tokio::Signals;
@ -61,7 +63,6 @@ struct Cli {
#[derive(Clone)] #[derive(Clone)]
struct HTTPSParams { struct HTTPSParams {
https_listen_addr: SocketAddr, https_listen_addr: SocketAddr,
domain_acme_store: sync::Arc<dyn domiply::domain::acme::store::Store>,
domain_acme_manager: sync::Arc<dyn domiply::domain::acme::manager::Manager>, domain_acme_manager: sync::Arc<dyn domiply::domain::acme::manager::Manager>,
} }
@ -101,16 +102,13 @@ async fn main() {
// settings. // settings.
let domain_acme_contact_email = config.domain_acme_contact_email.unwrap(); let domain_acme_contact_email = config.domain_acme_contact_email.unwrap();
let domain_acme_manager = domiply::domain::acme::manager::new( let domain_acme_manager =
domain_acme_store.clone(), domiply::domain::acme::manager::new(domain_acme_store, &domain_acme_contact_email)
&domain_acme_contact_email, .await
) .expect("domain acme manager initialization failed");
.await
.expect("domain acme manager initialization failed");
Some(HTTPSParams { Some(HTTPSParams {
https_listen_addr, https_listen_addr,
domain_acme_store,
domain_acme_manager, domain_acme_manager,
}) })
} else { } else {
@ -136,7 +134,7 @@ async fn main() {
config.http_domain.clone(), config.http_domain.clone(),
https_params.map(|p| domiply::service::http::HTTPSParams { https_params.map(|p| domiply::service::http::HTTPSParams {
listen_addr: p.https_listen_addr, listen_addr: p.https_listen_addr,
cert_resolver: domiply::domain::acme::resolver::new(p.domain_acme_store), cert_resolver: domain_manager.clone(),
}), }),
); );