Got acme manager implemented, still untested. Not hooked up to user domains yet.
This commit is contained in:
parent
0f42327a57
commit
6d8799ce8c
2
.env.dev
2
.env.dev
@ -3,3 +3,5 @@ export DOMIPLY_PASSPHRASE=foobar
|
||||
export DOMIPLY_ORIGIN_STORE_GIT_DIR_PATH=/tmp/domiply_dev_env/origin/git
|
||||
export DOMIPLY_DOMAIN_CHECKER_TARGET_A=127.0.0.1
|
||||
export DOMIPLY_DOMAIN_CONFIG_STORE_DIR_PATH=/tmp/domiply_dev_env/domain/config
|
||||
export DOMIPLY_DOMAIN_ACME_STORE_DIR_PATH=/tmp/domiply_dev_env/domain/acme
|
||||
export DOMIPLY_DOMAIN_ACME_CONTACT_EMAIL=domiply@example.com
|
||||
|
@ -16,7 +16,7 @@ pub struct Name {
|
||||
}
|
||||
|
||||
impl Name {
|
||||
fn as_str(&self) -> &str {
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.utf8_str.as_str()
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,30 @@
|
||||
use std::sync;
|
||||
use std::{future, pin, sync, time};
|
||||
|
||||
use crate::domain::acme;
|
||||
use crate::domain::{self, acme};
|
||||
use crate::error;
|
||||
use crate::error::{MapUnexpected, ToUnexpected};
|
||||
|
||||
const LETS_ENCRYPT_URL: &'static str = "https://acme-v02.api.letsencrypt.org/directory";
|
||||
|
||||
#[mockall::automock]
|
||||
pub trait Manager {}
|
||||
pub type GetHttp01ChallengeKeyError = acme::store::GetHttp01ChallengeKeyError;
|
||||
|
||||
pub trait BoxedManager: Manager + Send + Sync + Clone {}
|
||||
#[mockall::automock(
|
||||
type SyncDomainFuture=future::Ready<Result<(), error::Unexpected>>;
|
||||
)]
|
||||
pub trait Manager {
|
||||
type SyncDomainFuture<'mgr>: future::Future<Output = Result<(), error::Unexpected>>
|
||||
+ Send
|
||||
+ 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>;
|
||||
}
|
||||
|
||||
pub trait BoxedManager: Manager + Send + Sync + Clone + 'static {}
|
||||
|
||||
struct ManagerImpl<Store>
|
||||
where
|
||||
@ -54,4 +69,157 @@ where
|
||||
|
||||
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 {}
|
||||
impl<Store> Manager for sync::Arc<ManagerImpl<Store>>
|
||||
where
|
||||
Store: acme::store::BoxedStore,
|
||||
{
|
||||
type SyncDomainFuture<'mgr> = pin::Pin<Box<dyn future::Future<Output = Result<(), error::Unexpected>> + Send + 'mgr>>
|
||||
where Self: 'mgr;
|
||||
|
||||
fn sync_domain(&self, domain: domain::Name) -> Self::SyncDomainFuture<'_> {
|
||||
Box::pin(async move {
|
||||
let mut builder = acme2::OrderBuilder::new(self.account.clone());
|
||||
builder.add_dns_identifier(domain.as_str().to_string());
|
||||
let order = builder.build().await.map_unexpected()?;
|
||||
|
||||
let authorizations = order.authorizations().await.map_unexpected()?;
|
||||
|
||||
for auth in authorizations {
|
||||
let challenge = auth
|
||||
.get_challenge("http-01")
|
||||
.ok_or(error::Unexpected::from("expected http-01 challenge"))?;
|
||||
|
||||
let challenge_token = challenge
|
||||
.token
|
||||
.as_ref()
|
||||
.ok_or(error::Unexpected::from("expected challenge to have token"))?;
|
||||
|
||||
let challenge_key = challenge
|
||||
.key_authorization()
|
||||
.map_unexpected()?
|
||||
.ok_or(error::Unexpected::from("expected challenge to have key"))?;
|
||||
|
||||
self.store
|
||||
.set_http01_challenge_key(challenge_token, &challenge_key)
|
||||
.map_unexpected()?;
|
||||
|
||||
// At this point the manager is prepared to serve the challenge key via the
|
||||
// `get_http01_challenge_key` method. It is expected that there is some http
|
||||
// server, with this domain pointing at it, which is prepared to serve that
|
||||
// challenge token/key under the `/.well-known/` path. The `validate()` call below
|
||||
// will instigate the acme server to make this check, and block until it succeeds.
|
||||
|
||||
let challenge = challenge.validate().await.map_unexpected()?;
|
||||
|
||||
// Poll the challenge every 5 seconds until it is in either the
|
||||
// `valid` or `invalid` state.
|
||||
let challenge = challenge
|
||||
.wait_done(time::Duration::from_secs(5), 3)
|
||||
.await
|
||||
.map_unexpected()?;
|
||||
|
||||
if challenge.status != acme2::ChallengeStatus::Valid {
|
||||
return Err(error::Unexpected::from(
|
||||
format!(
|
||||
"expected challenge status to be valid, instead it was {:?}",
|
||||
challenge.status
|
||||
)
|
||||
.as_str(),
|
||||
));
|
||||
}
|
||||
|
||||
self.store
|
||||
.del_http01_challenge_key(&challenge_token)
|
||||
.map_unexpected()?;
|
||||
|
||||
// Poll the authorization every 5 seconds until it is in either the
|
||||
// `valid` or `invalid` state.
|
||||
let authorization = auth
|
||||
.wait_done(time::Duration::from_secs(5), 3)
|
||||
.await
|
||||
.map_unexpected()?;
|
||||
|
||||
if authorization.status != acme2::AuthorizationStatus::Valid {
|
||||
return Err(error::Unexpected::from(
|
||||
format!(
|
||||
"expected authorization status to be valid, instead it was {:?}",
|
||||
authorization.status,
|
||||
)
|
||||
.as_str(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Poll the order every 5 seconds until it is in either the `ready` or `invalid` state.
|
||||
// Ready means that it is now ready for finalization (certificate creation).
|
||||
let order = order
|
||||
.wait_ready(time::Duration::from_secs(5), 3)
|
||||
.await
|
||||
.map_unexpected()?;
|
||||
|
||||
if order.status != acme2::OrderStatus::Ready {
|
||||
return Err(error::Unexpected::from(
|
||||
format!(
|
||||
"expected order status to be ready, instead it was {:?}",
|
||||
order.status,
|
||||
)
|
||||
.as_str(),
|
||||
));
|
||||
}
|
||||
|
||||
// Generate an RSA private key for the certificate.
|
||||
let pkey = acme2::gen_rsa_private_key(4096).map_unexpected()?;
|
||||
|
||||
// Create a certificate signing request for the order, and request
|
||||
// the certificate.
|
||||
let order = order
|
||||
.finalize(acme2::Csr::Automatic(pkey))
|
||||
.await
|
||||
.map_unexpected()?;
|
||||
|
||||
// Poll the order every 5 seconds until it is in either the
|
||||
// `valid` or `invalid` state. Valid means that the certificate
|
||||
// has been provisioned, and is now ready for download.
|
||||
let order = order
|
||||
.wait_done(time::Duration::from_secs(5), 3)
|
||||
.await
|
||||
.map_unexpected()?;
|
||||
|
||||
if order.status != acme2::OrderStatus::Valid {
|
||||
return Err(error::Unexpected::from(
|
||||
format!(
|
||||
"expected order status to be valid, instead it was {:?}",
|
||||
order.status,
|
||||
)
|
||||
.as_str(),
|
||||
));
|
||||
}
|
||||
|
||||
// Download the certificate, and panic if it doesn't exist.
|
||||
let cert =
|
||||
order
|
||||
.certificate()
|
||||
.await
|
||||
.map_unexpected()?
|
||||
.ok_or(error::Unexpected::from(
|
||||
"expected the order to return a certificate",
|
||||
))?;
|
||||
|
||||
if cert.len() <= 1 {
|
||||
return Err(error::Unexpected::from(
|
||||
format!(
|
||||
"expected more than one certificate to be returned, instead got {}",
|
||||
cert.len(),
|
||||
)
|
||||
.as_str(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn get_http01_challenge_key(&self, token: &str) -> Result<String, GetHttp01ChallengeKeyError> {
|
||||
self.store.get_http01_challenge_key(token)
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,9 @@ use crate::domain::acme::{AccountKey, Certificate};
|
||||
use crate::error;
|
||||
use crate::error::{MapUnexpected, ToUnexpected};
|
||||
|
||||
use hex::ToHex;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum GetAccountKeyError {
|
||||
#[error("not found")]
|
||||
@ -15,7 +18,7 @@ pub enum GetAccountKeyError {
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum GetHttp01ChallengeError {
|
||||
pub enum GetHttp01ChallengeKeyError {
|
||||
#[error("not found")]
|
||||
NotFound,
|
||||
|
||||
@ -37,9 +40,9 @@ pub trait Store {
|
||||
fn set_account_key(&self, k: &AccountKey) -> Result<(), error::Unexpected>;
|
||||
fn get_account_key(&self) -> Result<AccountKey, GetAccountKeyError>;
|
||||
|
||||
fn set_http01_challenge(&self, token: &str, key: &str) -> Result<(), error::Unexpected>;
|
||||
fn get_http01_challenge(&self, token: &str) -> Result<String, GetHttp01ChallengeError>;
|
||||
fn del_http01_challenge(&self, token: &str) -> Result<(), error::Unexpected>;
|
||||
fn set_http01_challenge_key(&self, token: &str, key: &str) -> Result<(), error::Unexpected>;
|
||||
fn get_http01_challenge_key(&self, token: &str) -> Result<String, GetHttp01ChallengeKeyError>;
|
||||
fn del_http01_challenge_key(&self, token: &str) -> Result<(), error::Unexpected>;
|
||||
|
||||
fn set_certificate(
|
||||
&self,
|
||||
@ -51,7 +54,7 @@ pub trait Store {
|
||||
fn get_certificate(&self, domain: &str) -> Result<Vec<Certificate>, GetCertificateError>;
|
||||
}
|
||||
|
||||
pub trait BoxedStore: Store + Send + Sync + Clone {}
|
||||
pub trait BoxedStore: Store + Send + Sync + Clone + 'static {}
|
||||
|
||||
struct FSStore {
|
||||
dir_path: path::PathBuf,
|
||||
@ -59,7 +62,7 @@ struct FSStore {
|
||||
|
||||
pub fn new(dir_path: &path::Path) -> Result<impl BoxedStore, error::Unexpected> {
|
||||
fs::create_dir_all(dir_path).map_unexpected()?;
|
||||
fs::create_dir_all(dir_path.join("http01_challenges")).map_unexpected()?;
|
||||
fs::create_dir_all(dir_path.join("http01_challenge_keys")).map_unexpected()?;
|
||||
fs::create_dir_all(dir_path.join("certificates")).map_unexpected()?;
|
||||
|
||||
Ok(sync::Arc::new(FSStore {
|
||||
@ -72,8 +75,14 @@ impl FSStore {
|
||||
self.dir_path.join("account.key")
|
||||
}
|
||||
|
||||
fn http01_challenge_path(&self, token: &str) -> path::PathBuf {
|
||||
self.dir_path.join("http01_challenges").join(token)
|
||||
fn http01_challenge_key_path(&self, token: &str) -> path::PathBuf {
|
||||
// hash it for safety
|
||||
let mut h = Sha256::new();
|
||||
h.write_all(token.as_bytes())
|
||||
.expect("token successfully hashed");
|
||||
let n = h.finalize().encode_hex::<String>();
|
||||
|
||||
self.dir_path.join("http01_challenge_keys").join(n)
|
||||
}
|
||||
|
||||
fn certificate_path(&self, domain: &str) -> path::PathBuf {
|
||||
@ -104,16 +113,16 @@ impl Store for sync::Arc<FSStore> {
|
||||
Ok(k)
|
||||
}
|
||||
|
||||
fn set_http01_challenge(&self, token: &str, key: &str) -> Result<(), error::Unexpected> {
|
||||
let mut file = fs::File::create(self.http01_challenge_path(token)).map_unexpected()?;
|
||||
fn set_http01_challenge_key(&self, token: &str, key: &str) -> Result<(), error::Unexpected> {
|
||||
let mut file = fs::File::create(self.http01_challenge_key_path(token)).map_unexpected()?;
|
||||
file.write_all(key.as_bytes()).map_unexpected()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_http01_challenge(&self, token: &str) -> Result<String, GetHttp01ChallengeError> {
|
||||
fn get_http01_challenge_key(&self, token: &str) -> Result<String, GetHttp01ChallengeKeyError> {
|
||||
let mut file =
|
||||
fs::File::open(self.http01_challenge_path(token)).map_err(|e| match e.kind() {
|
||||
io::ErrorKind::NotFound => GetHttp01ChallengeError::NotFound,
|
||||
fs::File::open(self.http01_challenge_key_path(token)).map_err(|e| match e.kind() {
|
||||
io::ErrorKind::NotFound => GetHttp01ChallengeKeyError::NotFound,
|
||||
_ => e.to_unexpected().into(),
|
||||
})?;
|
||||
|
||||
@ -123,8 +132,8 @@ impl Store for sync::Arc<FSStore> {
|
||||
Ok(key)
|
||||
}
|
||||
|
||||
fn del_http01_challenge(&self, token: &str) -> Result<(), error::Unexpected> {
|
||||
fs::remove_file(self.http01_challenge_path(token)).map_unexpected()?;
|
||||
fn del_http01_challenge_key(&self, token: &str) -> Result<(), error::Unexpected> {
|
||||
fs::remove_file(self.http01_challenge_key_path(token)).map_unexpected()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -196,36 +205,36 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http01_challenge() {
|
||||
let tmp_dir = TempDir::new("domain_acme_store_http01_challenge").unwrap();
|
||||
fn http01_challenge_key() {
|
||||
let tmp_dir = TempDir::new("domain_acme_store_http01_challenge_key").unwrap();
|
||||
let store = new(tmp_dir.path()).expect("store created");
|
||||
|
||||
let token = "foo".to_string();
|
||||
let key = "bar".to_string();
|
||||
|
||||
assert!(matches!(
|
||||
store.get_http01_challenge(&token),
|
||||
Err::<String, GetHttp01ChallengeError>(GetHttp01ChallengeError::NotFound)
|
||||
store.get_http01_challenge_key(&token),
|
||||
Err::<String, GetHttp01ChallengeKeyError>(GetHttp01ChallengeKeyError::NotFound)
|
||||
));
|
||||
|
||||
store
|
||||
.set_http01_challenge(&token, &key)
|
||||
.set_http01_challenge_key(&token, &key)
|
||||
.expect("http01 challenge set");
|
||||
|
||||
assert_eq!(
|
||||
key,
|
||||
store
|
||||
.get_http01_challenge(&token)
|
||||
.get_http01_challenge_key(&token)
|
||||
.expect("retrieved http01 challenge"),
|
||||
);
|
||||
|
||||
store
|
||||
.del_http01_challenge(&token)
|
||||
.del_http01_challenge_key(&token)
|
||||
.expect("deleted http01 challenge");
|
||||
|
||||
assert!(matches!(
|
||||
store.get_http01_challenge(&token),
|
||||
Err::<String, GetHttp01ChallengeError>(GetHttp01ChallengeError::NotFound)
|
||||
store.get_http01_challenge_key(&token),
|
||||
Err::<String, GetHttp01ChallengeKeyError>(GetHttp01ChallengeKeyError::NotFound)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,8 @@
|
||||
use crate::domain::{self, checker, config};
|
||||
use crate::domain::{self, acme, checker, config};
|
||||
use crate::error::{MapUnexpected, ToUnexpected};
|
||||
use crate::{error, origin};
|
||||
|
||||
use std::future;
|
||||
use std::{pin, sync};
|
||||
use std::{future, pin, sync};
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum GetConfigError {
|
||||
@ -114,6 +113,8 @@ impl From<config::SetError> for SyncWithConfigError {
|
||||
}
|
||||
}
|
||||
|
||||
pub type GetAcmeHttp01ChallengeKeyError = acme::manager::GetHttp01ChallengeKeyError;
|
||||
|
||||
#[mockall::automock(
|
||||
type Origin=origin::MockOrigin;
|
||||
type SyncWithConfigFuture=future::Ready<Result<(), SyncWithConfigError>>;
|
||||
@ -147,49 +148,61 @@ pub trait Manager {
|
||||
) -> Self::SyncWithConfigFuture<'_>;
|
||||
|
||||
fn sync_all_origins(&self) -> Result<Self::SyncAllOriginsErrorsIter<'_>, error::Unexpected>;
|
||||
|
||||
fn get_acme_http01_challenge_key(
|
||||
&self,
|
||||
token: &str,
|
||||
) -> Result<String, GetAcmeHttp01ChallengeKeyError>;
|
||||
}
|
||||
|
||||
pub trait BoxedManager: Manager + Send + Sync + Clone {}
|
||||
|
||||
struct ManagerImpl<OriginStore, DomainConfigStore>
|
||||
struct ManagerImpl<OriginStore, DomainConfigStore, AcmeManager>
|
||||
where
|
||||
OriginStore: origin::store::BoxedStore,
|
||||
DomainConfigStore: config::BoxedStore,
|
||||
AcmeManager: acme::manager::BoxedManager,
|
||||
{
|
||||
origin_store: OriginStore,
|
||||
domain_config_store: DomainConfigStore,
|
||||
domain_checker: checker::DNSChecker,
|
||||
acme_manager: Option<AcmeManager>,
|
||||
}
|
||||
|
||||
pub fn new<OriginStore, DomainConfigStore>(
|
||||
pub fn new<OriginStore, DomainConfigStore, AcmeManager>(
|
||||
origin_store: OriginStore,
|
||||
domain_config_store: DomainConfigStore,
|
||||
domain_checker: checker::DNSChecker,
|
||||
acme_manager: Option<AcmeManager>,
|
||||
) -> impl BoxedManager
|
||||
where
|
||||
OriginStore: origin::store::BoxedStore,
|
||||
DomainConfigStore: config::BoxedStore,
|
||||
AcmeManager: acme::manager::BoxedManager,
|
||||
{
|
||||
sync::Arc::new(ManagerImpl {
|
||||
origin_store,
|
||||
domain_config_store,
|
||||
domain_checker,
|
||||
acme_manager: acme_manager,
|
||||
})
|
||||
}
|
||||
|
||||
impl<OriginStore, DomainConfigStore> BoxedManager
|
||||
for sync::Arc<ManagerImpl<OriginStore, DomainConfigStore>>
|
||||
impl<OriginStore, DomainConfigStore, AcmeManager> BoxedManager
|
||||
for sync::Arc<ManagerImpl<OriginStore, DomainConfigStore, AcmeManager>>
|
||||
where
|
||||
OriginStore: origin::store::BoxedStore,
|
||||
DomainConfigStore: config::BoxedStore,
|
||||
AcmeManager: acme::manager::BoxedManager,
|
||||
{
|
||||
}
|
||||
|
||||
impl<OriginStore, DomainConfigStore> Manager
|
||||
for sync::Arc<ManagerImpl<OriginStore, DomainConfigStore>>
|
||||
impl<OriginStore, DomainConfigStore, AcmeManager> Manager
|
||||
for sync::Arc<ManagerImpl<OriginStore, DomainConfigStore, AcmeManager>>
|
||||
where
|
||||
OriginStore: origin::store::BoxedStore,
|
||||
DomainConfigStore: config::BoxedStore,
|
||||
AcmeManager: acme::manager::BoxedManager,
|
||||
{
|
||||
type Origin<'mgr> = OriginStore::Origin<'mgr>
|
||||
where Self: 'mgr;
|
||||
@ -231,6 +244,10 @@ where
|
||||
|
||||
self.domain_config_store.set(&domain, &config)?;
|
||||
|
||||
if let Some(ref acme_manager) = self.acme_manager {
|
||||
acme_manager.sync_domain(domain.clone()).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
@ -255,4 +272,15 @@ where
|
||||
None
|
||||
})))
|
||||
}
|
||||
|
||||
fn get_acme_http01_challenge_key(
|
||||
&self,
|
||||
token: &str,
|
||||
) -> Result<String, GetAcmeHttp01ChallengeKeyError> {
|
||||
if let Some(ref acme_manager) = self.acme_manager {
|
||||
return acme_manager.get_http01_challenge_key(token);
|
||||
}
|
||||
|
||||
Err(GetAcmeHttp01ChallengeKeyError::NotFound)
|
||||
}
|
||||
}
|
||||
|
64
src/main.rs
64
src/main.rs
@ -1,3 +1,5 @@
|
||||
#![feature(result_option_inspect)]
|
||||
|
||||
use clap::Parser;
|
||||
use futures::stream::StreamExt;
|
||||
use signal_hook_tokio::Signals;
|
||||
@ -10,17 +12,25 @@ use std::path;
|
||||
use std::str::FromStr;
|
||||
use std::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")]
|
||||
struct Cli {
|
||||
#[arg(long, required = true, env = "DOMIPLY_HTTP_DOMAIN")]
|
||||
http_domain: String,
|
||||
|
||||
#[arg(long, default_value_t = SocketAddr::from_str("[::]:3030").unwrap(), env = "DOMIPLY_HTTP_LISTEN_ADDR")]
|
||||
http_listen_addr: SocketAddr,
|
||||
|
||||
#[arg(long, required = true, env = "DOMIPLY_HTTP_DOMAIN")]
|
||||
http_domain: String,
|
||||
#[arg(
|
||||
long,
|
||||
help = "E.g. '[::]:443', if given then SSL certs will automatically be retrieved for all domains using LetsEncrypt",
|
||||
env = "DOMIPLY_HTTPS_LISTEN_ADDR"
|
||||
)]
|
||||
https_listen_addr: Option<SocketAddr>,
|
||||
|
||||
#[arg(long, required = true, env = "DOMIPLY_PASSPHRASE")]
|
||||
passphrase: String,
|
||||
@ -36,6 +46,12 @@ struct Cli {
|
||||
|
||||
#[arg(long, required = true, env = "DOMIPLY_DOMAIN_CONFIG_STORE_DIR_PATH")]
|
||||
domain_config_store_dir_path: path::PathBuf,
|
||||
|
||||
#[arg(long, required = true, env = "DOMIPLY_DOMAIN_ACME_STORE_DIR_PATH")]
|
||||
domain_acme_store_dir_path: path::PathBuf,
|
||||
|
||||
#[arg(long, required = true, env = "DOMIPLY_DOMAIN_ACME_CONTACT_EMAIL")]
|
||||
domain_acme_contact_email: String,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
@ -81,7 +97,29 @@ fn main() {
|
||||
let domain_config_store = domiply::domain::config::new(&config.domain_config_store_dir_path)
|
||||
.expect("domain config store initialized");
|
||||
|
||||
let manager = domiply::domain::manager::new(origin_store, domain_config_store, domain_checker);
|
||||
let domain_acme_manager = config.https_listen_addr.and_then(|_addr| {
|
||||
let domain_acme_store =
|
||||
domiply::domain::acme::store::new(&config.domain_acme_store_dir_path)
|
||||
.expect("domain acme store initialized");
|
||||
|
||||
let domain_acme_manager = tokio_runtime.block_on(async {
|
||||
domiply::domain::acme::manager::new(
|
||||
domain_acme_store,
|
||||
&config.domain_acme_contact_email,
|
||||
)
|
||||
.await
|
||||
.expect("domain acme manager initialized")
|
||||
});
|
||||
|
||||
Some(domain_acme_manager)
|
||||
});
|
||||
|
||||
let manager = domiply::domain::manager::new(
|
||||
origin_store,
|
||||
domain_config_store,
|
||||
domain_checker,
|
||||
domain_acme_manager.clone(),
|
||||
);
|
||||
|
||||
let origin_syncer_handler = {
|
||||
let manager = manager.clone();
|
||||
@ -137,10 +175,12 @@ fn main() {
|
||||
});
|
||||
|
||||
let server_handler = {
|
||||
let http_domain = config.http_domain.clone();
|
||||
|
||||
tokio_runtime.spawn(async move {
|
||||
let addr = config.http_listen_addr;
|
||||
|
||||
println!("Listening on http://{}:{}", config.http_domain, addr.port());
|
||||
println!("Listening on http://{}:{}", http_domain, addr.port());
|
||||
let server = hyper::Server::bind(&addr).serve(make_service);
|
||||
|
||||
let graceful = server.with_graceful_shutdown(async {
|
||||
@ -153,6 +193,22 @@ 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 domain = domiply::domain::Name::from_str(&config.http_domain)
|
||||
.expect("--http-domain parses as a domain");
|
||||
|
||||
tokio_runtime.spawn(async move {
|
||||
_ = domain_acme_manager
|
||||
.sync_domain(domain.clone())
|
||||
.await
|
||||
.inspect_err(|err| {
|
||||
println!("Error while getting cert for {}: {err}", domain.as_str())
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
tokio_runtime
|
||||
.block_on(async { futures::try_join!(origin_syncer_handler, server_handler) })
|
||||
.unwrap();
|
||||
|
@ -341,6 +341,21 @@ where
|
||||
return svc.render(200, path, ());
|
||||
}
|
||||
|
||||
if method == Method::GET && path.starts_with("/.well-known/") {
|
||||
let token = path.trim_start_matches("/.well-known/");
|
||||
|
||||
if let Ok(key) = svc.domain_manager.get_acme_http01_challenge_key(token) {
|
||||
let body: hyper::Body = key.into();
|
||||
return match Response::builder().status(200).body(body) {
|
||||
Ok(res) => Ok(res),
|
||||
Err(err) => Err(format!(
|
||||
"failed to write acme http-01 challenge key: {}",
|
||||
err
|
||||
)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
match (method, path) {
|
||||
(&Method::GET, "/") | (&Method::GET, "/index.html") => svc.render_page("/index.html", ()),
|
||||
(&Method::GET, "/domain.html") => {
|
||||
|
Loading…
Reference in New Issue
Block a user