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

View File

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

View File

@ -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<Box<dyn future::Future<Output = Result<(), unexpected::Error>> + Send + 'mgr>>;
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 {
@ -282,4 +290,12 @@ impl Manager for ManagerImpl {
fn get_http01_challenge_key(&self, token: &str) -> Result<String, GetHttp01ChallengeKeyError> {
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;
#[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<config::Config, GetConfigError>;
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<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 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<dyn domiply::domain::acme::store::Store>,
domain_acme_manager: sync::Arc<dyn domiply::domain::acme::manager::Manager>,
}
@ -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(),
}),
);