diff --git a/rpxy-acme/src/targets.rs b/rpxy-acme/src/targets.rs new file mode 100644 index 0000000..0cc6f99 --- /dev/null +++ b/rpxy-acme/src/targets.rs @@ -0,0 +1,83 @@ +use rustc_hash::FxHashMap as HashMap; +use std::path::PathBuf; +use url::Url; + +use crate::{ + constants::{ACME_ACCOUNT_SUBDIR, ACME_CERTIFICATE_FILE_NAME, ACME_DIR_URL, ACME_PRIVATE_KEY_FILE_NAME, ACME_REGISTRY_PATH}, + error::RpxyAcmeError, + log::*, +}; + +#[derive(Debug)] +/// ACME settings +pub struct AcmeTargets { + /// ACME account email + pub email: String, + /// ACME directory url + pub acme_dir_url: Url, + /// ACME registry path that stores account key and certificate + pub acme_registry_path: PathBuf, + /// ACME accounts directory, subdirectory of ACME_REGISTRY_PATH + pub acme_accounts_dir: PathBuf, + /// ACME target info map + pub acme_targets: HashMap, +} + +#[derive(Debug)] +/// ACME settings for each server name +pub struct AcmeTargetInfo { + /// Server name + pub server_name: String, + /// private key path + pub private_key_path: PathBuf, + /// certificate path + pub certificate_path: PathBuf, +} + +impl AcmeTargets { + /// Create a new instance + pub fn try_new(email: &str, acme_dir_url: Option<&str>, acme_registry_path: Option<&str>) -> Result { + let acme_dir_url = Url::parse(acme_dir_url.unwrap_or(ACME_DIR_URL))?; + let acme_registry_path = acme_registry_path.map_or_else(|| PathBuf::from(ACME_REGISTRY_PATH), PathBuf::from); + if acme_registry_path.exists() && !acme_registry_path.is_dir() { + return Err(RpxyAcmeError::InvalidAcmeRegistryPath); + } + let acme_account_dir = acme_registry_path.join(ACME_ACCOUNT_SUBDIR); + if acme_account_dir.exists() && !acme_account_dir.is_dir() { + return Err(RpxyAcmeError::InvalidAcmeRegistryPath); + } + std::fs::create_dir_all(&acme_account_dir)?; + + Ok(Self { + email: email.to_owned(), + acme_dir_url, + acme_registry_path, + acme_accounts_dir: acme_account_dir, + acme_targets: HashMap::default(), + }) + } + + /// Add a new target + /// Write dummy cert and key files if not exists + pub fn add_target(&mut self, server_name: &str) -> Result<(), RpxyAcmeError> { + info!("Adding ACME target: {}", server_name); + let parent_dir = self.acme_registry_path.join(server_name); + let private_key_path = parent_dir.join(ACME_PRIVATE_KEY_FILE_NAME); + let certificate_path = parent_dir.join(ACME_CERTIFICATE_FILE_NAME); + + if !parent_dir.exists() { + warn!("Creating ACME target directory: {}", parent_dir.display()); + std::fs::create_dir_all(parent_dir)?; + } + + self.acme_targets.insert( + server_name.to_owned(), + AcmeTargetInfo { + server_name: server_name.to_owned(), + private_key_path, + certificate_path, + }, + ); + Ok(()) + } +}