From 63f9d1dabc0b63fb7c7764768c968fd4c92132e1 Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Sat, 13 Jul 2024 05:07:57 +0900 Subject: [PATCH] wip: designing acme --- rpxy-acme/src/dir_cache.rs | 8 ++-- rpxy-acme/src/targets.rs | 78 ++++++++++++++++++++++++------------ rpxy-bin/src/config/parse.rs | 52 ++++++++++++++---------- 3 files changed, 87 insertions(+), 51 deletions(-) diff --git a/rpxy-acme/src/dir_cache.rs b/rpxy-acme/src/dir_cache.rs index 2f613d8..33504fa 100644 --- a/rpxy-acme/src/dir_cache.rs +++ b/rpxy-acme/src/dir_cache.rs @@ -15,14 +15,14 @@ enum FileType { Cert, } -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub struct DirCache { - account_dir: PathBuf, - cert_dir: PathBuf, + pub(super) account_dir: PathBuf, + pub(super) cert_dir: PathBuf, } impl DirCache { - pub fn new

(dir: P, server_name: impl AsRef) -> Self + pub fn new

(dir: P, server_name: &str) -> Self where P: AsRef, { diff --git a/rpxy-acme/src/targets.rs b/rpxy-acme/src/targets.rs index 08c4685..74e6262 100644 --- a/rpxy-acme/src/targets.rs +++ b/rpxy-acme/src/targets.rs @@ -1,32 +1,28 @@ -use crate::dir_cache::DirCache; use crate::{ constants::{ACME_DIR_URL, ACME_REGISTRY_PATH}, + dir_cache::DirCache, error::RpxyAcmeError, log::*, }; use rustc_hash::FxHashMap as HashMap; -use rustls_acme::AcmeConfig; -use std::{fmt::Debug, path::PathBuf, sync::Arc}; +// use rustls_acme::AcmeConfig; +use std::path::PathBuf; use url::Url; #[derive(Debug)] /// ACME settings -pub struct AcmeContexts -where - EC: Debug + 'static, - EA: Debug + 'static, -{ +pub struct AcmeContexts { /// ACME directory url acme_dir_url: Url, /// ACME registry directory acme_registry_dir: PathBuf, /// ACME contacts contacts: Vec, - /// ACME config - inner: HashMap>>, + /// ACME directly cache information + inner: HashMap, } -impl AcmeContexts { +impl AcmeContexts { /// Create a new instance. Note that for each domain, a new AcmeConfig is created. /// This means that for each domain, a distinct operation will be dispatched and separated certificates will be generated. pub fn try_new( @@ -35,6 +31,9 @@ impl AcmeContexts { contacts: &[String], domains: &[String], ) -> Result { + // Install aws_lc_rs as default crypto provider for rustls + let _ = rustls::crypto::CryptoProvider::install_default(rustls::crypto::aws_lc_rs::default_provider()); + let acme_registry_dir = acme_registry_dir .map(|v| v.to_ascii_lowercase()) .map_or_else(|| PathBuf::from(ACME_REGISTRY_PATH), PathBuf::from); @@ -46,25 +45,33 @@ impl AcmeContexts { .as_deref() .map_or_else(|| Url::parse(ACME_DIR_URL), Url::parse)?; let contacts = contacts.iter().map(|email| format!("mailto:{email}")).collect::>(); - let rustls_client_config = rustls::ClientConfig::builder() - .dangerous() // The `Verifier` we're using is actually safe - .with_custom_certificate_verifier(std::sync::Arc::new(rustls_platform_verifier::Verifier::new())) - .with_no_client_auth(); - let rustls_client_config = Arc::new(rustls_client_config); + // let rustls_client_config = rustls::ClientConfig::builder() + // .dangerous() // The `Verifier` we're using is actually safe + // .with_custom_certificate_verifier(std::sync::Arc::new(rustls_platform_verifier::Verifier::new())) + // .with_no_client_auth(); + // let rustls_client_config = Arc::new(rustls_client_config); let inner = domains .iter() .map(|domain| { - let dir_cache = DirCache::new(&acme_registry_dir, domain); - let config = AcmeConfig::new([domain]) - .contact(&contacts) - .cache(dir_cache) - .directory(acme_dir_url.as_str()) - .client_tls_config(rustls_client_config.clone()); - let config = Box::new(config); - (domain.to_ascii_lowercase(), config) + let domain = domain.to_ascii_lowercase(); + let dir_cache = DirCache::new(&acme_registry_dir, &domain); + (domain, dir_cache) }) .collect::>(); + // let inner = domains + // .iter() + // .map(|domain| { + // let dir_cache = DirCache::new(&acme_registry_dir, domain); + // let config = AcmeConfig::new([domain]) + // .contact(&contacts) + // .cache(dir_cache) + // .directory(acme_dir_url.as_str()) + // .client_tls_config(rustls_client_config.clone()); + // let config = Box::new(config); + // (domain.to_ascii_lowercase(), config) + // }) + // .collect::>(); Ok(Self { acme_dir_url, @@ -77,6 +84,8 @@ impl AcmeContexts { #[cfg(test)] mod tests { + use crate::constants::ACME_ACCOUNT_SUBDIR; + use super::*; #[test] @@ -84,13 +93,30 @@ mod tests { let acme_dir_url = "https://acme.example.com/directory"; let acme_registry_dir = "/tmp/acme"; let contacts = vec!["test@example.com".to_string()]; - let acme_contexts: AcmeContexts = AcmeContexts::try_new( + let acme_contexts: AcmeContexts = AcmeContexts::try_new( Some(acme_dir_url), Some(acme_registry_dir), &contacts, &["example.com".to_string(), "example.org".to_string()], ) .unwrap(); - println!("{:#?}", acme_contexts); + assert_eq!(acme_contexts.inner.len(), 2); + assert_eq!(acme_contexts.contacts, vec!["mailto:test@example.com".to_string()]); + assert_eq!(acme_contexts.acme_dir_url.as_str(), acme_dir_url); + assert_eq!(acme_contexts.acme_registry_dir, PathBuf::from(acme_registry_dir)); + assert_eq!( + acme_contexts.inner["example.com"], + DirCache { + account_dir: PathBuf::from(acme_registry_dir).join(ACME_ACCOUNT_SUBDIR), + cert_dir: PathBuf::from(acme_registry_dir).join("example.com"), + } + ); + assert_eq!( + acme_contexts.inner["example.org"], + DirCache { + account_dir: PathBuf::from(acme_registry_dir).join(ACME_ACCOUNT_SUBDIR), + cert_dir: PathBuf::from(acme_registry_dir).join("example.org"), + } + ); } } diff --git a/rpxy-bin/src/config/parse.rs b/rpxy-bin/src/config/parse.rs index 741cc7a..3dd6599 100644 --- a/rpxy-bin/src/config/parse.rs +++ b/rpxy-bin/src/config/parse.rs @@ -7,7 +7,7 @@ use rpxy_lib::{AppConfig, AppConfigList, ProxyConfig}; use rustc_hash::FxHashMap as HashMap; #[cfg(feature = "acme")] -use rpxy_acme::{ACME_DIR_URL, ACME_REGISTRY_PATH}; +use rpxy_acme::{AcmeContexts, ACME_DIR_URL, ACME_REGISTRY_PATH}; /// Parsed options pub struct Opts { @@ -160,27 +160,37 @@ pub async fn build_cert_manager( /// Build acme manager and dummy cert and key as initial states if not exists /// TODO: CURRENTLY NOT IMPLEMENTED, UNDER DESIGNING pub async fn build_acme_manager(config: &ConfigToml) -> Result<(), anyhow::Error> { - // let acme_option = config.experimental.as_ref().and_then(|v| v.acme.clone()); - // if acme_option.is_none() { - // return Ok(()); - // } - // let acme_option = acme_option.unwrap(); - // let mut acme_targets = AcmeTargets::try_new( - // acme_option.email.as_ref(), - // acme_option.dir_url.as_deref(), - // acme_option.registry_path.as_deref(), - // ) - // .map_err(|e| anyhow!("Invalid acme configuration: {e}"))?; + let acme_option = config.experimental.as_ref().and_then(|v| v.acme.clone()); + if acme_option.is_none() { + return Ok(()); + } + let acme_option = acme_option.unwrap(); + + let domains = config + .apps + .as_ref() + .unwrap() + .0 + .values() + .filter_map(|app| { + // + if let Some(tls) = app.tls.as_ref() { + if let Some(true) = tls.acme { + return Some(app.server_name.as_ref().unwrap().to_owned()); + } + } + None + }) + .collect::>(); + + let acme_contexts = AcmeContexts::try_new( + acme_option.dir_url.as_deref(), + acme_option.registry_path.as_deref(), + &[acme_option.email], + domains.as_slice(), + )?; - // let apps = config.apps.as_ref().unwrap(); - // for app in apps.0.values() { - // if let Some(tls) = app.tls.as_ref() { - // if tls.acme.unwrap_or(false) { - // acme_targets.add_target(app.server_name.as_ref().unwrap())?; - // } - // } - // } // TODO: remove later - // println!("ACME targets: {:#?}", acme_targets); + println!("ACME contexts: {:#?}", acme_contexts); Ok(()) }