Got rid of Boxed acme types

This commit is contained in:
Brian Picciano 2023-06-18 13:44:15 +02:00
parent 52f87dc625
commit 3d3dfb34ed
6 changed files with 84 additions and 108 deletions

View File

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

View File

@ -7,39 +7,24 @@ const LETS_ENCRYPT_URL: &str = "https://acme-v02.api.letsencrypt.org/directory";
pub type GetHttp01ChallengeKeyError = acme::store::GetHttp01ChallengeKeyError;
#[mockall::automock(
type SyncDomainFuture=future::Ready<Result<(), unexpected::Error>>;
)]
pub trait Manager {
type SyncDomainFuture<'mgr>: future::Future<Output = Result<(), unexpected::Error>>
+ Send
+ Unpin
+ 'mgr
where
Self: 'mgr;
fn sync_domain(&self, domain: domain::Name) -> Self::SyncDomainFuture<'_>;
#[mockall::automock]
pub trait Manager: Sync + Send {
fn sync_domain<'mgr>(
&'mgr self,
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>;
}
pub trait BoxedManager: Manager + Send + Sync + Clone + 'static {}
struct ManagerImpl<Store>
where
Store: acme::store::BoxedStore,
{
store: Store,
struct ManagerImpl {
store: sync::Arc<dyn acme::store::Store>,
account: sync::Arc<acme2::Account>,
}
pub async fn new<Store>(
store: Store,
pub async fn new(
store: sync::Arc<dyn acme::store::Store>,
contact_email: &str,
) -> Result<impl BoxedManager, unexpected::Error>
where
Store: acme::store::BoxedStore,
{
) -> Result<sync::Arc<dyn Manager>, unexpected::Error> {
let dir = acme2::DirectoryBuilder::new(LETS_ENCRYPT_URL.to_string())
.build()
.await
@ -81,16 +66,12 @@ where
Ok(sync::Arc::new(ManagerImpl { store, account }))
}
impl<Store> BoxedManager for sync::Arc<ManagerImpl<Store>> where Store: acme::store::BoxedStore {}
impl<Store> Manager for sync::Arc<ManagerImpl<Store>>
where
Store: acme::store::BoxedStore,
{
type SyncDomainFuture<'mgr> = pin::Pin<Box<dyn future::Future<Output = Result<(), unexpected::Error>> + Send + 'mgr>>
where Self: 'mgr;
fn sync_domain(&self, domain: domain::Name) -> Self::SyncDomainFuture<'_> {
impl Manager for ManagerImpl {
fn sync_domain<'mgr>(
&'mgr self,
domain: domain::Name,
) -> pin::Pin<Box<dyn future::Future<Output = Result<(), unexpected::Error>> + Send + 'mgr>>
{
Box::pin(async move {
// if there's an existing cert, and its expiry (determined by the soonest value of
// not_after amongst its parts) is later than 30 days from now, then we consider it to be

View File

@ -0,0 +1,44 @@
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

@ -38,7 +38,7 @@ pub enum GetCertificateError {
}
#[mockall::automock]
pub trait Store {
pub trait Store: Sync + Send {
fn set_account_key(&self, k: &PrivateKey) -> Result<(), unexpected::Error>;
fn get_account_key(&self) -> Result<PrivateKey, GetAccountKeyError>;
@ -60,8 +60,6 @@ pub trait Store {
) -> Result<(PrivateKey, Vec<Certificate>), GetCertificateError>;
}
pub trait BoxedStore: Store + Send + Sync + Clone + 'static {}
#[derive(Debug, Serialize, Deserialize)]
struct StoredPKeyCert {
private_key: PrivateKey,
@ -72,12 +70,7 @@ struct FSStore {
dir_path: path::PathBuf,
}
#[derive(Clone)]
struct BoxedFSStore(sync::Arc<FSStore>);
pub fn new(
dir_path: &path::Path,
) -> Result<impl BoxedStore + rustls::server::ResolvesServerCert, unexpected::Error> {
pub fn new(dir_path: &path::Path) -> Result<sync::Arc<dyn Store>, unexpected::Error> {
vec![
dir_path,
dir_path.join("http01_challenge_keys").as_ref(),
@ -89,14 +82,14 @@ pub fn new(
})
.try_collect()?;
Ok(BoxedFSStore(sync::Arc::new(FSStore {
Ok(sync::Arc::new(FSStore {
dir_path: dir_path.into(),
})))
}))
}
impl BoxedFSStore {
impl FSStore {
fn account_key_path(&self) -> path::PathBuf {
self.0.dir_path.join("account.key")
self.dir_path.join("account.key")
}
fn http01_challenge_key_path(&self, token: &str) -> path::PathBuf {
@ -106,20 +99,18 @@ impl BoxedFSStore {
.expect("token successfully hashed");
let n = h.finalize().encode_hex::<String>();
self.0.dir_path.join("http01_challenge_keys").join(n)
self.dir_path.join("http01_challenge_keys").join(n)
}
fn certificate_path(&self, domain: &str) -> path::PathBuf {
let mut domain = domain.to_string();
domain.push_str(".json");
self.0.dir_path.join("certificates").join(domain)
self.dir_path.join("certificates").join(domain)
}
}
impl BoxedStore for BoxedFSStore {}
impl Store for BoxedFSStore {
impl Store for FSStore {
fn set_account_key(&self, k: &PrivateKey) -> Result<(), unexpected::Error> {
let path = self.account_key_path();
{
@ -232,38 +223,6 @@ impl Store for BoxedFSStore {
}
}
impl rustls::server::ResolvesServerCert for BoxedFSStore {
fn resolve(
&self,
client_hello: rustls::server::ClientHello<'_>,
) -> Option<sync::Arc<rustls::sign::CertifiedKey>> {
let domain = client_hello.server_name()?;
match self.get_certificate(domain) {
Err(GetCertificateError::NotFound) => {
log::warn!("No cert found for domain {domain}");
Ok(None)
}
Err(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
})
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -141,15 +141,14 @@ pub trait Manager: Sync + Send {
fn all_domains(&self) -> AllDomainsResult<Vec<AllDomainsResult<domain::Name>>>;
}
struct ManagerImpl<DomainConfigStore, AcmeManager>
struct ManagerImpl<DomainConfigStore>
where
DomainConfigStore: config::BoxedStore,
AcmeManager: acme::manager::BoxedManager,
{
origin_store: sync::Arc<dyn origin::store::Store>,
domain_config_store: DomainConfigStore,
domain_checker: checker::DNSChecker,
acme_manager: Option<AcmeManager>,
acme_manager: Option<sync::Arc<dyn acme::manager::Manager>>,
canceller: CancellationToken,
origin_sync_handler: tokio::task::JoinHandle<()>,
@ -171,15 +170,14 @@ fn sync_origins(origin_store: &dyn origin::store::Store) {
});
}
pub fn new<'mgr, DomainConfigStore, AcmeManager>(
pub fn new<'mgr, DomainConfigStore>(
origin_store: sync::Arc<dyn origin::store::Store>,
domain_config_store: DomainConfigStore,
domain_checker: checker::DNSChecker,
acme_manager: Option<AcmeManager>,
acme_manager: Option<sync::Arc<dyn acme::manager::Manager>>,
) -> sync::Arc<dyn Manager>
where
DomainConfigStore: config::BoxedStore,
AcmeManager: acme::manager::BoxedManager,
{
let canceller = CancellationToken::new();
@ -208,10 +206,9 @@ where
})
}
impl<DomainConfigStore, AcmeManager> ManagerImpl<DomainConfigStore, AcmeManager>
impl<DomainConfigStore> ManagerImpl<DomainConfigStore>
where
DomainConfigStore: config::BoxedStore,
AcmeManager: acme::manager::BoxedManager,
{
pub async fn stop(self) {
self.canceller.cancel();
@ -221,10 +218,9 @@ where
}
}
impl<DomainConfigStore, AcmeManager> Manager for ManagerImpl<DomainConfigStore, AcmeManager>
impl<DomainConfigStore> Manager for ManagerImpl<DomainConfigStore>
where
DomainConfigStore: config::BoxedStore,
AcmeManager: acme::manager::BoxedManager,
{
fn get_config(&self, domain: &domain::Name) -> Result<config::Config, GetConfigError> {
Ok(self.domain_config_store.get(domain)?)

View File

@ -12,9 +12,6 @@ use std::net::SocketAddr;
use std::str::FromStr;
use std::{future, path, sync};
use domiply::domain::acme::manager::Manager as AcmeManager;
use domiply::domain::manager::Manager;
#[derive(Parser, Debug)]
#[command(version)]
#[command(about = "A domiply to another dimension")]
@ -68,14 +65,10 @@ struct Cli {
}
#[derive(Clone)]
struct HTTPSParams<DomainAcmeStore, DomainAcmeManager>
where
DomainAcmeStore: domiply::domain::acme::store::BoxedStore,
DomainAcmeManager: domiply::domain::acme::manager::BoxedManager,
{
struct HTTPSParams {
https_listen_addr: SocketAddr,
domain_acme_store: DomainAcmeStore,
domain_acme_manager: DomainAcmeManager,
domain_acme_store: sync::Arc<dyn domiply::domain::acme::store::Store>,
domain_acme_manager: sync::Arc<dyn domiply::domain::acme::manager::Manager>,
}
#[tokio::main]
@ -263,7 +256,6 @@ async fn main() {
// HTTPS server
wait_group.push({
let https_params = https_params;
let http_domain = config.http_domain.clone();
let canceller = canceller.clone();
let service = service.clone();
@ -281,12 +273,15 @@ async fn main() {
});
tokio::spawn(async move {
let cert_resolver =
domiply::domain::acme::resolver::new(https_params.domain_acme_store);
let canceller = canceller.clone();
let server_config: tokio_rustls::TlsAcceptor = sync::Arc::new(
rustls::server::ServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth()
.with_cert_resolver(sync::Arc::from(https_params.domain_acme_store)),
.with_cert_resolver(cert_resolver),
)
.into();