Periodically refresh certs for all domains

This commit is contained in:
Brian Picciano 2023-05-19 13:26:27 +02:00
parent 5b26396106
commit 06cda77772
3 changed files with 76 additions and 10 deletions

View File

@ -1,4 +1,5 @@
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::{fs, io, sync};
use crate::error::{MapUnexpected, ToUnexpected};
@ -37,10 +38,14 @@ pub enum SetError {
Unexpected(#[from] error::Unexpected),
}
/// Used in the return from all_domains from Store.
pub type AllDomainsResult<T> = Result<T, error::Unexpected>;
#[mockall::automock]
pub trait Store {
fn get(&self, domain: &domain::Name) -> Result<Config, GetError>;
fn set(&self, domain: &domain::Name, config: &Config) -> Result<(), SetError>;
fn all_domains(&self) -> AllDomainsResult<Vec<AllDomainsResult<domain::Name>>>;
}
pub trait BoxedStore: Store + Send + Sync + Clone {}
@ -89,6 +94,22 @@ impl Store for sync::Arc<FSStore> {
Ok(())
}
fn all_domains(&self) -> AllDomainsResult<Vec<AllDomainsResult<domain::Name>>> {
Ok(fs::read_dir(&self.dir_path)
.map_unexpected()?
.map(
|dir_entry_res: io::Result<fs::DirEntry>| -> AllDomainsResult<domain::Name> {
let domain = dir_entry_res.map_unexpected()?.file_name();
let domain = domain.to_str().ok_or_else(|| {
error::Unexpected::from("couldn't convert os string to &str")
})?;
Ok(domain::Name::from_str(domain).map_unexpected()?)
},
)
.collect())
}
}
#[cfg(test)]

View File

@ -115,6 +115,8 @@ impl From<config::SetError> for SyncWithConfigError {
pub type GetAcmeHttp01ChallengeKeyError = acme::manager::GetHttp01ChallengeKeyError;
pub type AllDomainsResult<T> = config::AllDomainsResult<T>;
#[mockall::automock(
type Origin=origin::MockOrigin;
type SyncWithConfigFuture=future::Ready<Result<(), SyncWithConfigError>>;
@ -153,6 +155,8 @@ pub trait Manager {
&self,
token: &str,
) -> Result<String, GetAcmeHttp01ChallengeKeyError>;
fn all_domains(&self) -> AllDomainsResult<Vec<AllDomainsResult<domain::Name>>>;
}
pub trait BoxedManager: Manager + Send + Sync + Clone {}
@ -283,4 +287,8 @@ where
Err(GetAcmeHttp01ChallengeKeyError::NotFound)
}
fn all_domains(&self) -> AllDomainsResult<Vec<AllDomainsResult<domain::Name>>> {
self.domain_config_store.all_domains()
}
}

View File

@ -158,7 +158,7 @@ fn main() {
});
let service = domiply::service::new(
manager,
manager.clone(),
config.domain_checker_target_a,
config.passphrase,
config.http_domain.clone(),
@ -181,6 +181,7 @@ fn main() {
wait_group.push({
let http_domain = config.http_domain.clone();
let canceller = canceller.clone();
tokio_runtime.spawn(async move {
let addr = config.http_listen_addr;
@ -205,9 +206,20 @@ fn main() {
// if there's an acme manager then it means that https is enabled, and we should ensure that
// the http domain for domiply itself has a valid certificate.
if let Some(domain_acme_manager) = domain_acme_manager {
let manager = manager.clone();
let canceller = canceller.clone();
let http_domain = config.http_domain.clone();
// Periodically refresh all domain certs
wait_group.push(tokio_runtime.spawn(async move {
let mut interval = time::interval(time::Duration::from_secs(60 * 60));
loop {
select! {
_ = interval.tick() => (),
_ = canceller.cancelled() => return,
}
_ = domain_acme_manager
.sync_domain(http_domain.clone())
.await
@ -217,6 +229,31 @@ fn main() {
http_domain.as_str()
)
});
let domains_iter = manager.all_domains();
if let Err(err) = domains_iter {
println!("Got error calling all_domains: {err}");
continue;
}
for domain in domains_iter.unwrap().into_iter() {
match domain {
Ok(domain) => {
let _ = domain_acme_manager
.sync_domain(domain.clone())
.await
.inspect_err(|err| {
println!(
"Error while getting cert for {}: {err}",
domain.as_str(),
)
});
}
Err(err) => println!("Error iterating through domains: {err}"),
};
}
}
}));
}