Got rid of Boxed acme types
This commit is contained in:
parent
52f87dc625
commit
3d3dfb34ed
@ -1,4 +1,5 @@
|
|||||||
pub mod manager;
|
pub mod manager;
|
||||||
|
pub mod resolver;
|
||||||
pub mod store;
|
pub mod store;
|
||||||
|
|
||||||
mod private_key;
|
mod private_key;
|
||||||
|
@ -7,39 +7,24 @@ const LETS_ENCRYPT_URL: &str = "https://acme-v02.api.letsencrypt.org/directory";
|
|||||||
|
|
||||||
pub type GetHttp01ChallengeKeyError = acme::store::GetHttp01ChallengeKeyError;
|
pub type GetHttp01ChallengeKeyError = acme::store::GetHttp01ChallengeKeyError;
|
||||||
|
|
||||||
#[mockall::automock(
|
#[mockall::automock]
|
||||||
type SyncDomainFuture=future::Ready<Result<(), unexpected::Error>>;
|
pub trait Manager: Sync + Send {
|
||||||
)]
|
fn sync_domain<'mgr>(
|
||||||
pub trait Manager {
|
&'mgr self,
|
||||||
type SyncDomainFuture<'mgr>: future::Future<Output = Result<(), unexpected::Error>>
|
domain: domain::Name,
|
||||||
+ Send
|
) -> pin::Pin<Box<dyn future::Future<Output = Result<(), unexpected::Error>> + Send + 'mgr>>;
|
||||||
+ Unpin
|
|
||||||
+ 'mgr
|
|
||||||
where
|
|
||||||
Self: 'mgr;
|
|
||||||
|
|
||||||
fn sync_domain(&self, domain: domain::Name) -> Self::SyncDomainFuture<'_>;
|
|
||||||
|
|
||||||
fn get_http01_challenge_key(&self, token: &str) -> Result<String, GetHttp01ChallengeKeyError>;
|
fn get_http01_challenge_key(&self, token: &str) -> Result<String, GetHttp01ChallengeKeyError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait BoxedManager: Manager + Send + Sync + Clone + 'static {}
|
struct ManagerImpl {
|
||||||
|
store: sync::Arc<dyn acme::store::Store>,
|
||||||
struct ManagerImpl<Store>
|
|
||||||
where
|
|
||||||
Store: acme::store::BoxedStore,
|
|
||||||
{
|
|
||||||
store: Store,
|
|
||||||
account: sync::Arc<acme2::Account>,
|
account: sync::Arc<acme2::Account>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn new<Store>(
|
pub async fn new(
|
||||||
store: Store,
|
store: sync::Arc<dyn acme::store::Store>,
|
||||||
contact_email: &str,
|
contact_email: &str,
|
||||||
) -> Result<impl BoxedManager, unexpected::Error>
|
) -> Result<sync::Arc<dyn Manager>, unexpected::Error> {
|
||||||
where
|
|
||||||
Store: acme::store::BoxedStore,
|
|
||||||
{
|
|
||||||
let dir = acme2::DirectoryBuilder::new(LETS_ENCRYPT_URL.to_string())
|
let dir = acme2::DirectoryBuilder::new(LETS_ENCRYPT_URL.to_string())
|
||||||
.build()
|
.build()
|
||||||
.await
|
.await
|
||||||
@ -81,16 +66,12 @@ where
|
|||||||
Ok(sync::Arc::new(ManagerImpl { store, account }))
|
Ok(sync::Arc::new(ManagerImpl { store, account }))
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Store> BoxedManager for sync::Arc<ManagerImpl<Store>> where Store: acme::store::BoxedStore {}
|
impl Manager for ManagerImpl {
|
||||||
|
fn sync_domain<'mgr>(
|
||||||
impl<Store> Manager for sync::Arc<ManagerImpl<Store>>
|
&'mgr self,
|
||||||
where
|
domain: domain::Name,
|
||||||
Store: acme::store::BoxedStore,
|
) -> pin::Pin<Box<dyn future::Future<Output = Result<(), unexpected::Error>> + Send + 'mgr>>
|
||||||
{
|
{
|
||||||
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<'_> {
|
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
// if there's an existing cert, and its expiry (determined by the soonest value of
|
// 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
|
// not_after amongst its parts) is later than 30 days from now, then we consider it to be
|
||||||
|
44
src/domain/acme/resolver.rs
Normal file
44
src/domain/acme/resolver.rs
Normal 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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -38,7 +38,7 @@ pub enum GetCertificateError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[mockall::automock]
|
#[mockall::automock]
|
||||||
pub trait Store {
|
pub trait Store: Sync + Send {
|
||||||
fn set_account_key(&self, k: &PrivateKey) -> Result<(), unexpected::Error>;
|
fn set_account_key(&self, k: &PrivateKey) -> Result<(), unexpected::Error>;
|
||||||
fn get_account_key(&self) -> Result<PrivateKey, GetAccountKeyError>;
|
fn get_account_key(&self) -> Result<PrivateKey, GetAccountKeyError>;
|
||||||
|
|
||||||
@ -60,8 +60,6 @@ pub trait Store {
|
|||||||
) -> Result<(PrivateKey, Vec<Certificate>), GetCertificateError>;
|
) -> Result<(PrivateKey, Vec<Certificate>), GetCertificateError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait BoxedStore: Store + Send + Sync + Clone + 'static {}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
struct StoredPKeyCert {
|
struct StoredPKeyCert {
|
||||||
private_key: PrivateKey,
|
private_key: PrivateKey,
|
||||||
@ -72,12 +70,7 @@ struct FSStore {
|
|||||||
dir_path: path::PathBuf,
|
dir_path: path::PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
pub fn new(dir_path: &path::Path) -> Result<sync::Arc<dyn Store>, unexpected::Error> {
|
||||||
struct BoxedFSStore(sync::Arc<FSStore>);
|
|
||||||
|
|
||||||
pub fn new(
|
|
||||||
dir_path: &path::Path,
|
|
||||||
) -> Result<impl BoxedStore + rustls::server::ResolvesServerCert, unexpected::Error> {
|
|
||||||
vec![
|
vec![
|
||||||
dir_path,
|
dir_path,
|
||||||
dir_path.join("http01_challenge_keys").as_ref(),
|
dir_path.join("http01_challenge_keys").as_ref(),
|
||||||
@ -89,14 +82,14 @@ pub fn new(
|
|||||||
})
|
})
|
||||||
.try_collect()?;
|
.try_collect()?;
|
||||||
|
|
||||||
Ok(BoxedFSStore(sync::Arc::new(FSStore {
|
Ok(sync::Arc::new(FSStore {
|
||||||
dir_path: dir_path.into(),
|
dir_path: dir_path.into(),
|
||||||
})))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BoxedFSStore {
|
impl FSStore {
|
||||||
fn account_key_path(&self) -> path::PathBuf {
|
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 {
|
fn http01_challenge_key_path(&self, token: &str) -> path::PathBuf {
|
||||||
@ -106,20 +99,18 @@ impl BoxedFSStore {
|
|||||||
.expect("token successfully hashed");
|
.expect("token successfully hashed");
|
||||||
let n = h.finalize().encode_hex::<String>();
|
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 {
|
fn certificate_path(&self, domain: &str) -> path::PathBuf {
|
||||||
let mut domain = domain.to_string();
|
let mut domain = domain.to_string();
|
||||||
domain.push_str(".json");
|
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 FSStore {
|
||||||
|
|
||||||
impl Store for BoxedFSStore {
|
|
||||||
fn set_account_key(&self, k: &PrivateKey) -> Result<(), unexpected::Error> {
|
fn set_account_key(&self, k: &PrivateKey) -> Result<(), unexpected::Error> {
|
||||||
let path = self.account_key_path();
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -141,15 +141,14 @@ pub trait Manager: Sync + Send {
|
|||||||
fn all_domains(&self) -> AllDomainsResult<Vec<AllDomainsResult<domain::Name>>>;
|
fn all_domains(&self) -> AllDomainsResult<Vec<AllDomainsResult<domain::Name>>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ManagerImpl<DomainConfigStore, AcmeManager>
|
struct ManagerImpl<DomainConfigStore>
|
||||||
where
|
where
|
||||||
DomainConfigStore: config::BoxedStore,
|
DomainConfigStore: config::BoxedStore,
|
||||||
AcmeManager: acme::manager::BoxedManager,
|
|
||||||
{
|
{
|
||||||
origin_store: sync::Arc<dyn origin::store::Store>,
|
origin_store: sync::Arc<dyn origin::store::Store>,
|
||||||
domain_config_store: DomainConfigStore,
|
domain_config_store: DomainConfigStore,
|
||||||
domain_checker: checker::DNSChecker,
|
domain_checker: checker::DNSChecker,
|
||||||
acme_manager: Option<AcmeManager>,
|
acme_manager: Option<sync::Arc<dyn acme::manager::Manager>>,
|
||||||
|
|
||||||
canceller: CancellationToken,
|
canceller: CancellationToken,
|
||||||
origin_sync_handler: tokio::task::JoinHandle<()>,
|
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>,
|
origin_store: sync::Arc<dyn origin::store::Store>,
|
||||||
domain_config_store: DomainConfigStore,
|
domain_config_store: DomainConfigStore,
|
||||||
domain_checker: checker::DNSChecker,
|
domain_checker: checker::DNSChecker,
|
||||||
acme_manager: Option<AcmeManager>,
|
acme_manager: Option<sync::Arc<dyn acme::manager::Manager>>,
|
||||||
) -> sync::Arc<dyn Manager>
|
) -> sync::Arc<dyn Manager>
|
||||||
where
|
where
|
||||||
DomainConfigStore: config::BoxedStore,
|
DomainConfigStore: config::BoxedStore,
|
||||||
AcmeManager: acme::manager::BoxedManager,
|
|
||||||
{
|
{
|
||||||
let canceller = CancellationToken::new();
|
let canceller = CancellationToken::new();
|
||||||
|
|
||||||
@ -208,10 +206,9 @@ where
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<DomainConfigStore, AcmeManager> ManagerImpl<DomainConfigStore, AcmeManager>
|
impl<DomainConfigStore> ManagerImpl<DomainConfigStore>
|
||||||
where
|
where
|
||||||
DomainConfigStore: config::BoxedStore,
|
DomainConfigStore: config::BoxedStore,
|
||||||
AcmeManager: acme::manager::BoxedManager,
|
|
||||||
{
|
{
|
||||||
pub async fn stop(self) {
|
pub async fn stop(self) {
|
||||||
self.canceller.cancel();
|
self.canceller.cancel();
|
||||||
@ -221,10 +218,9 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<DomainConfigStore, AcmeManager> Manager for ManagerImpl<DomainConfigStore, AcmeManager>
|
impl<DomainConfigStore> Manager for ManagerImpl<DomainConfigStore>
|
||||||
where
|
where
|
||||||
DomainConfigStore: config::BoxedStore,
|
DomainConfigStore: config::BoxedStore,
|
||||||
AcmeManager: acme::manager::BoxedManager,
|
|
||||||
{
|
{
|
||||||
fn get_config(&self, domain: &domain::Name) -> Result<config::Config, GetConfigError> {
|
fn get_config(&self, domain: &domain::Name) -> Result<config::Config, GetConfigError> {
|
||||||
Ok(self.domain_config_store.get(domain)?)
|
Ok(self.domain_config_store.get(domain)?)
|
||||||
|
19
src/main.rs
19
src/main.rs
@ -12,9 +12,6 @@ use std::net::SocketAddr;
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::{future, path, sync};
|
use std::{future, path, sync};
|
||||||
|
|
||||||
use domiply::domain::acme::manager::Manager as AcmeManager;
|
|
||||||
use domiply::domain::manager::Manager;
|
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(version)]
|
#[command(version)]
|
||||||
#[command(about = "A domiply to another dimension")]
|
#[command(about = "A domiply to another dimension")]
|
||||||
@ -68,14 +65,10 @@ struct Cli {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct HTTPSParams<DomainAcmeStore, DomainAcmeManager>
|
struct HTTPSParams {
|
||||||
where
|
|
||||||
DomainAcmeStore: domiply::domain::acme::store::BoxedStore,
|
|
||||||
DomainAcmeManager: domiply::domain::acme::manager::BoxedManager,
|
|
||||||
{
|
|
||||||
https_listen_addr: SocketAddr,
|
https_listen_addr: SocketAddr,
|
||||||
domain_acme_store: DomainAcmeStore,
|
domain_acme_store: sync::Arc<dyn domiply::domain::acme::store::Store>,
|
||||||
domain_acme_manager: DomainAcmeManager,
|
domain_acme_manager: sync::Arc<dyn domiply::domain::acme::manager::Manager>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
@ -263,7 +256,6 @@ async fn main() {
|
|||||||
|
|
||||||
// HTTPS server
|
// HTTPS server
|
||||||
wait_group.push({
|
wait_group.push({
|
||||||
let https_params = https_params;
|
|
||||||
let http_domain = config.http_domain.clone();
|
let http_domain = config.http_domain.clone();
|
||||||
let canceller = canceller.clone();
|
let canceller = canceller.clone();
|
||||||
let service = service.clone();
|
let service = service.clone();
|
||||||
@ -281,12 +273,15 @@ async fn main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
|
let cert_resolver =
|
||||||
|
domiply::domain::acme::resolver::new(https_params.domain_acme_store);
|
||||||
let canceller = canceller.clone();
|
let canceller = canceller.clone();
|
||||||
|
|
||||||
let server_config: tokio_rustls::TlsAcceptor = sync::Arc::new(
|
let server_config: tokio_rustls::TlsAcceptor = sync::Arc::new(
|
||||||
rustls::server::ServerConfig::builder()
|
rustls::server::ServerConfig::builder()
|
||||||
.with_safe_defaults()
|
.with_safe_defaults()
|
||||||
.with_no_client_auth()
|
.with_no_client_auth()
|
||||||
.with_cert_resolver(sync::Arc::from(https_params.domain_acme_store)),
|
.with_cert_resolver(cert_resolver),
|
||||||
)
|
)
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user