From 28104f36e1582812436971367d54219d67e0069e Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Wed, 12 Jul 2023 19:01:31 +0200 Subject: [PATCH] Add token::MemStore, use it for http01 challenges --- src/domain/acme/manager.rs | 39 ++++++++++++---- src/domain/acme/store.rs | 95 -------------------------------------- src/lib.rs | 1 + src/main.rs | 12 +++-- src/token.rs | 37 +++++++++++++++ 5 files changed, 76 insertions(+), 108 deletions(-) create mode 100644 src/token.rs diff --git a/src/domain/acme/manager.rs b/src/domain/acme/manager.rs index c76900e..acb825a 100644 --- a/src/domain/acme/manager.rs +++ b/src/domain/acme/manager.rs @@ -2,11 +2,19 @@ use std::{sync, time}; use crate::domain::acme::{self, Certificate, PrivateKey}; use crate::error::unexpected::{self, Intoable, Mappable}; -use crate::{domain, util}; +use crate::{domain, token, util}; const LETS_ENCRYPT_URL: &str = "https://acme-v02.api.letsencrypt.org/directory"; -pub type GetHttp01ChallengeKeyError = acme::store::GetHttp01ChallengeKeyError; +#[derive(thiserror::Error, Debug)] +pub enum GetHttp01ChallengeKeyError { + #[error("not found")] + NotFound, + + #[error(transparent)] + Unexpected(#[from] unexpected::Error), +} + pub type GetCertificateError = acme::store::GetCertificateError; #[mockall::automock] @@ -27,14 +35,20 @@ pub trait Manager { pub struct ManagerImpl { store: Box, + token_store: Box, account: sync::Arc, } impl ManagerImpl { - pub async fn new( - store: Store, + pub async fn new( + store: AcmeStore, + token_store: TokenStore, config: &domain::ConfigACME, - ) -> Result { + ) -> Result + where + AcmeStore: acme::store::Store + Send + Sync + 'static, + TokenStore: token::Store + Send + Sync + 'static, + { let dir = acme2::DirectoryBuilder::new(LETS_ENCRYPT_URL.to_string()) .build() .await @@ -78,6 +92,7 @@ impl ManagerImpl { Ok(Self { store: Box::from(store), + token_store: Box::from(token_store), account, }) } @@ -140,8 +155,8 @@ impl Manager for ManagerImpl { .or_unexpected_while("getting challenge key from authorization")? .ok_or(unexpected::Error::from("expected challenge to have key"))?; - self.store - .set_http01_challenge_key(challenge_token, &challenge_key) + self.token_store + .set(challenge_token.clone(), challenge_key) .or_unexpected_while("storing challenge token")?; // At this point the manager is prepared to serve the challenge key via the @@ -165,8 +180,8 @@ impl Manager for ManagerImpl { let challenge_res = challenge.wait_done(time::Duration::from_secs(5), 3).await; // no matter what the result is, clean up the challenge key - self.store - .del_http01_challenge_key(challenge_token) + self.token_store + .del(challenge_token) .or_unexpected_while("deleting challenge token")?; let challenge = challenge_res.or_unexpected_while("getting challenge status")?; @@ -296,7 +311,11 @@ impl Manager for ManagerImpl { } fn get_http01_challenge_key(&self, token: &str) -> Result { - self.store.get_http01_challenge_key(token) + match self.token_store.get(token) { + Ok(Some(v)) => Ok(v), + Ok(None) => Err(GetHttp01ChallengeKeyError::NotFound), + Err(e) => Err(e.into()), + } } /// Returned vec is guaranteed to have len > 0 diff --git a/src/domain/acme/store.rs b/src/domain/acme/store.rs index 1dd6fa5..1ece3da 100644 --- a/src/domain/acme/store.rs +++ b/src/domain/acme/store.rs @@ -6,9 +6,7 @@ use crate::domain::acme::{Certificate, PrivateKey}; use crate::error::unexpected::{self, Mappable}; use crate::util; -use hex::ToHex; use serde::{Deserialize, Serialize}; -use sha2::{Digest, Sha256}; #[derive(thiserror::Error, Debug)] pub enum GetAccountKeyError { @@ -19,15 +17,6 @@ pub enum GetAccountKeyError { Unexpected(#[from] unexpected::Error), } -#[derive(thiserror::Error, Debug)] -pub enum GetHttp01ChallengeKeyError { - #[error("not found")] - NotFound, - - #[error(transparent)] - Unexpected(#[from] unexpected::Error), -} - #[derive(thiserror::Error, Debug)] pub enum GetCertificateError { #[error("not found")] @@ -42,10 +31,6 @@ pub trait Store { fn set_account_key(&self, k: &PrivateKey) -> Result<(), unexpected::Error>; fn get_account_key(&self) -> Result; - fn set_http01_challenge_key(&self, token: &str, key: &str) -> Result<(), unexpected::Error>; - fn get_http01_challenge_key(&self, token: &str) -> Result; - fn del_http01_challenge_key(&self, token: &str) -> Result<(), unexpected::Error>; - fn set_certificate( &self, domain: &str, @@ -93,16 +78,6 @@ impl FSStore { self.dir_path.join("account.key") } - 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::(); - - self.dir_path.join("http01_challenge_keys").join(n) - } - fn certificate_path(&self, domain: &str) -> path::PathBuf { let mut domain = domain.to_string(); domain.push_str(".json"); @@ -144,42 +119,6 @@ impl Store for FSStore { .map_err(|err| err.into()) } - fn set_http01_challenge_key(&self, token: &str, key: &str) -> Result<(), unexpected::Error> { - let path = self.http01_challenge_key_path(token); - { - let mut file = fs::File::create(path.as_path()).or_unexpected_while("creating file")?; - file.write_all(key.as_bytes()) - .or_unexpected_while("writing file") - } - .map_unexpected_while(|| format!("path is {}", path.display()))?; - Ok(()) - } - - fn get_http01_challenge_key(&self, token: &str) -> Result { - let path = self.http01_challenge_key_path(token); - { - let mut file = - match util::open_file(path.as_path()).or_unexpected_while("opening_file")? { - Some(file) => file, - None => return Err(GetHttp01ChallengeKeyError::NotFound), - }; - - let mut key = String::new(); - file.read_to_string(&mut key) - .or_unexpected_while("reading file")?; - - Ok::(key) - } - .map_unexpected_while(|| format!("path is {}", path.display())) - .map_err(|err| err.into()) - } - - fn del_http01_challenge_key(&self, token: &str) -> Result<(), unexpected::Error> { - let path = self.http01_challenge_key_path(token); - fs::remove_file(path.as_path()) - .map_unexpected_while(|| format!("path is {}", path.display())) - } - fn set_certificate( &self, domain: &str, @@ -251,38 +190,4 @@ mod tests { .expect("account private key retrieved") ); } - - #[test] - fn http01_challenge_key() { - let tmp_dir = TempDir::new("domain_acme_store_http01_challenge_key").unwrap(); - let store = FSStore::new(tmp_dir.path()).expect("store created"); - - let token = "foo".to_string(); - let key = "bar".to_string(); - - assert!(matches!( - store.get_http01_challenge_key(&token), - Err::(GetHttp01ChallengeKeyError::NotFound) - )); - - store - .set_http01_challenge_key(&token, &key) - .expect("http01 challenge set"); - - assert_eq!( - key, - store - .get_http01_challenge_key(&token) - .expect("retrieved http01 challenge"), - ); - - store - .del_http01_challenge_key(&token) - .expect("deleted http01 challenge"); - - assert!(matches!( - store.get_http01_challenge_key(&token), - Err::(GetHttp01ChallengeKeyError::NotFound) - )); - } } diff --git a/src/lib.rs b/src/lib.rs index 8a40578..16b4918 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,4 +7,5 @@ pub mod domain; pub mod error; pub mod origin; pub mod service; +pub mod token; pub mod util; diff --git a/src/main.rs b/src/main.rs index 409393b..72c8e5e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -78,6 +78,8 @@ async fn main() { config }; + let token_store = domani::token::MemStore::new(); + let origin_store = domani::origin::git::FSStore::new(&config.origin) .expect("git origin store initialization failed"); @@ -107,9 +109,13 @@ async fn main() { .expect("domain acme store initialization failed"); Some( - domani::domain::acme::manager::ManagerImpl::new(domain_acme_store, &acme_config) - .await - .expect("domain acme manager initialization failed"), + domani::domain::acme::manager::ManagerImpl::new( + domain_acme_store, + token_store, + &acme_config, + ) + .await + .expect("domain acme manager initialization failed"), ) } else { None diff --git a/src/token.rs b/src/token.rs new file mode 100644 index 0000000..7454297 --- /dev/null +++ b/src/token.rs @@ -0,0 +1,37 @@ +/// Provides utilites for storing and retrieving various named string tokens which are needed. +use crate::error::unexpected; +use std::{collections, sync}; + +pub trait Store { + fn get(&self, key: &str) -> unexpected::Result>; + fn set(&self, key: String, val: String) -> unexpected::Result<()>; + fn del(&self, key: &str) -> unexpected::Result<()>; +} + +pub struct MemStore { + m: sync::Mutex>, +} + +impl MemStore { + pub fn new() -> MemStore { + MemStore { + m: sync::Mutex::default(), + } + } +} + +impl Store for MemStore { + fn get(&self, key: &str) -> unexpected::Result> { + Ok(self.m.lock().unwrap().get(key).map(|s| s.to_string())) + } + + fn set(&self, key: String, val: String) -> unexpected::Result<()> { + self.m.lock().unwrap().insert(key, val); + Ok(()) + } + + fn del(&self, key: &str) -> unexpected::Result<()> { + self.m.lock().unwrap().remove(key); + Ok(()) + } +}