From f2327778f6c7b2db4056e19ccfd38e7e6f9fcfb0 Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Wed, 12 Jul 2023 19:11:30 +0900 Subject: [PATCH 1/5] refactor --- src/{cert_reader.rs => cert_file_reader.rs} | 2 +- src/certs.rs | 17 +++++++++++++++++ src/main.rs | 3 ++- src/proxy/crypto_service.rs | 13 +++---------- src/proxy/mod.rs | 1 - 5 files changed, 23 insertions(+), 13 deletions(-) rename src/{cert_reader.rs => cert_file_reader.rs} (98%) create mode 100644 src/certs.rs diff --git a/src/cert_reader.rs b/src/cert_file_reader.rs similarity index 98% rename from src/cert_reader.rs rename to src/cert_file_reader.rs index a52f2e2..53a736e 100644 --- a/src/cert_reader.rs +++ b/src/cert_file_reader.rs @@ -1,4 +1,4 @@ -use crate::{log::*, proxy::CertsAndKeys}; +use crate::{certs::CertsAndKeys, log::*}; use rustls::{Certificate, PrivateKey}; use std::{ fs::File, diff --git a/src/certs.rs b/src/certs.rs new file mode 100644 index 0000000..3008900 --- /dev/null +++ b/src/certs.rs @@ -0,0 +1,17 @@ +use async_trait::async_trait; +use rustls::{Certificate, PrivateKey}; + +/// Certificates and private keys in rustls loaded from files +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct CertsAndKeys { + pub certs: Vec, + pub cert_keys: Vec, + pub client_ca_certs: Option>, +} + +#[async_trait] +// Trait to read certs and keys anywhere from KVS, file, sqlite, etc. +pub trait ReadCerts { + type Error; + async fn read_crypto_source(&self) -> Result; +} diff --git a/src/main.rs b/src/main.rs index ea47e57..526c290 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,8 @@ use tikv_jemallocator::Jemalloc; static GLOBAL: Jemalloc = Jemalloc; mod backend; -mod cert_reader; +mod cert_file_reader; +mod certs; mod config; mod constants; mod error; diff --git a/src/proxy/crypto_service.rs b/src/proxy/crypto_service.rs index 728a531..629119b 100644 --- a/src/proxy/crypto_service.rs +++ b/src/proxy/crypto_service.rs @@ -1,5 +1,6 @@ use crate::{ - cert_reader::read_certs_and_keys, // TODO: Trait defining read_certs_and_keys and add struct implementing the trait to backend when build backend + cert_file_reader::read_certs_and_keys, // TODO: Trait defining read_certs_and_keys and add struct implementing the trait to backend when build backend + certs::CertsAndKeys, globals::Globals, log::*, utils::ServerNameBytesExp, @@ -10,7 +11,7 @@ use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet}; use rustls::{ server::ResolvesServerCertUsingSni, sign::{any_supported_type, CertifiedKey}, - Certificate, OwnedTrustAnchor, PrivateKey, RootCertStore, ServerConfig, + OwnedTrustAnchor, RootCertStore, ServerConfig, }; use std::{io, sync::Arc}; use x509_parser::prelude::*; @@ -21,14 +22,6 @@ pub struct CryptoReloader { globals: Arc, } -/// Certificates and private keys in rustls loaded from files -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct CertsAndKeys { - pub certs: Vec, - pub cert_keys: Vec, - pub client_ca_certs: Option>, -} - pub type SniServerCryptoMap = HashMap>; pub struct ServerCrypto { // For Quic/HTTP3, only servers with no client authentication diff --git a/src/proxy/mod.rs b/src/proxy/mod.rs index d8fdc83..73a4002 100644 --- a/src/proxy/mod.rs +++ b/src/proxy/mod.rs @@ -5,5 +5,4 @@ mod proxy_h3; mod proxy_main; mod proxy_tls; -pub use crypto_service::CertsAndKeys; pub use proxy_main::{Proxy, ProxyBuilder, ProxyBuilderError}; From db329e38b4575f29213a9fdb946925fa8dd0b1e8 Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Wed, 12 Jul 2023 19:21:43 +0900 Subject: [PATCH 2/5] refactor: define crypto source trait --- src/cert_file_reader.rs | 26 +++++++++++++++++++++++++- src/certs.rs | 4 ++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/cert_file_reader.rs b/src/cert_file_reader.rs index 53a736e..2a800b3 100644 --- a/src/cert_file_reader.rs +++ b/src/cert_file_reader.rs @@ -1,4 +1,8 @@ -use crate::{certs::CertsAndKeys, log::*}; +use crate::{ + certs::{CertsAndKeys, CryptoSource}, + log::*, +}; +use async_trait::async_trait; use rustls::{Certificate, PrivateKey}; use std::{ fs::File, @@ -6,6 +10,26 @@ use std::{ path::PathBuf, }; +/// Crypto-related file reader implementing certs::CryptoRead trait +pub struct CryptoFileSource { + /// tls settings in file + pub tls_cert_path: PathBuf, + pub tls_cert_key_path: PathBuf, + pub client_ca_cert_path: Option, +} + +#[async_trait] +impl CryptoSource for CryptoFileSource { + type Error = io::Error; + async fn read(&self) -> Result { + read_certs_and_keys( + &self.tls_cert_path, + &self.tls_cert_key_path, + self.client_ca_cert_path.as_ref(), + ) + } +} + /// Read certificates and private keys from file pub(crate) fn read_certs_and_keys( cert_path: &PathBuf, diff --git a/src/certs.rs b/src/certs.rs index 3008900..da51e14 100644 --- a/src/certs.rs +++ b/src/certs.rs @@ -11,7 +11,7 @@ pub struct CertsAndKeys { #[async_trait] // Trait to read certs and keys anywhere from KVS, file, sqlite, etc. -pub trait ReadCerts { +pub trait CryptoSource { type Error; - async fn read_crypto_source(&self) -> Result; + async fn read(&self) -> Result; } From b6073e5d12d299ba358fc245ca876228562c80dd Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Wed, 12 Jul 2023 19:51:48 +0900 Subject: [PATCH 3/5] refactor: implement tests for crypto file source reader --- src/cert_file_reader.rs | 65 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/src/cert_file_reader.rs b/src/cert_file_reader.rs index 2a800b3..c3a3310 100644 --- a/src/cert_file_reader.rs +++ b/src/cert_file_reader.rs @@ -3,6 +3,7 @@ use crate::{ log::*, }; use async_trait::async_trait; +use derive_builder::Builder; use rustls::{Certificate, PrivateKey}; use std::{ fs::File, @@ -10,14 +11,37 @@ use std::{ path::PathBuf, }; +#[derive(Builder, Debug)] /// Crypto-related file reader implementing certs::CryptoRead trait pub struct CryptoFileSource { - /// tls settings in file + #[builder(setter(custom))] + /// Always exist pub tls_cert_path: PathBuf, + + #[builder(setter(custom))] + /// Always exist pub tls_cert_key_path: PathBuf, + + #[builder(setter(custom), default)] + /// This may not exist pub client_ca_cert_path: Option, } +impl CryptoFileSourceBuilder { + pub fn tls_cert_path(&mut self, v: &str) -> &mut Self { + self.tls_cert_path = Some(PathBuf::from(v)); + self + } + pub fn tls_cert_key_path(&mut self, v: &str) -> &mut Self { + self.tls_cert_key_path = Some(PathBuf::from(v)); + self + } + pub fn client_ca_cert_path(&mut self, v: &str) -> &mut Self { + self.client_ca_cert_path = Some(Some(PathBuf::from(v))); + self + } +} + #[async_trait] impl CryptoSource for CryptoFileSource { type Error = io::Error; @@ -115,3 +139,42 @@ pub(crate) fn read_certs_and_keys( client_ca_certs, }) } + +#[cfg(test)] +mod tests { + use super::*; + #[tokio::test] + async fn read_server_crt_key_files() { + let tls_cert_path = "example-certs/server.crt"; + let tls_cert_key_path = "example-certs/server.key"; + let crypto_file_source = CryptoFileSourceBuilder::default() + .tls_cert_key_path(tls_cert_key_path) + .tls_cert_path(tls_cert_path) + .build(); + assert!(crypto_file_source.is_ok()); + + let crypto_file_source = crypto_file_source.unwrap(); + let crypto_elem = crypto_file_source.read().await; + assert!(crypto_elem.is_ok()); + } + + #[tokio::test] + async fn read_server_crt_key_files_with_client_ca_crt() { + let tls_cert_path = "example-certs/server.crt"; + let tls_cert_key_path = "example-certs/server.key"; + let client_ca_cert_path = "example-certs/client.ca.crt"; + let crypto_file_source = CryptoFileSourceBuilder::default() + .tls_cert_key_path(tls_cert_key_path) + .tls_cert_path(tls_cert_path) + .client_ca_cert_path(client_ca_cert_path) + .build(); + assert!(crypto_file_source.is_ok()); + + let crypto_file_source = crypto_file_source.unwrap(); + let crypto_elem = crypto_file_source.read().await; + assert!(crypto_elem.is_ok()); + + let crypto_elem = crypto_elem.unwrap(); + assert!(crypto_elem.client_ca_certs.is_some()); + } +} From 6c0fd85ca5099a4e7705c170b080a57f63d2b658 Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Wed, 12 Jul 2023 20:31:31 +0900 Subject: [PATCH 4/5] refactor: add cert_reader object in backend --- src/backend/mod.rs | 38 +++++++++++++++++++++++++++++++------ src/cert_file_reader.rs | 2 +- src/config/parse.rs | 9 ++++++--- src/config/toml.rs | 8 ++++++-- src/globals.rs | 8 ++++++-- src/handler/handler_main.rs | 18 ++++++++++-------- src/main.rs | 11 ++++++++--- src/proxy/crypto_service.rs | 16 +++++++++++----- src/proxy/proxy_h3.rs | 5 +++-- src/proxy/proxy_main.rs | 14 +++++++++----- src/proxy/proxy_tls.rs | 7 ++++--- 11 files changed, 96 insertions(+), 40 deletions(-) diff --git a/src/backend/mod.rs b/src/backend/mod.rs index c8298c3..9bc28e5 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -13,14 +13,20 @@ pub use self::{ upstream::{ReverseProxy, Upstream, UpstreamGroup, UpstreamGroupBuilder}, upstream_opts::UpstreamOption, }; -use crate::utils::{BytesName, PathNameBytesExp, ServerNameBytesExp}; +use crate::{ + certs::CryptoSource, + utils::{BytesName, PathNameBytesExp, ServerNameBytesExp}, +}; use derive_builder::Builder; use rustc_hash::FxHashMap as HashMap; use std::{borrow::Cow, path::PathBuf}; /// Struct serving information to route incoming connections, like server name to be handled and tls certs/keys settings. #[derive(Builder)] -pub struct Backend { +pub struct Backend +where + T: CryptoSource, +{ #[builder(setter(into))] /// backend application name, e.g., app1 pub app_name: String, @@ -39,8 +45,14 @@ pub struct Backend { pub https_redirection: Option, #[builder(setter(custom), default)] pub client_ca_cert_path: Option, + + #[builder(default)] + pub crypto_source: Option, } -impl<'a> BackendBuilder { +impl<'a, T> BackendBuilder +where + T: CryptoSource, +{ pub fn server_name(&mut self, server_name: impl Into>) -> &mut Self { self.server_name = Some(server_name.into().to_ascii_lowercase()); self @@ -63,9 +75,23 @@ fn opt_string_to_opt_pathbuf(input: &Option) -> Option { input.to_owned().as_ref().map(PathBuf::from) } -#[derive(Default)] /// HashMap and some meta information for multiple Backend structs. -pub struct Backends { - pub apps: HashMap, // hyper::uriで抜いたhostで引っ掛ける +pub struct Backends +where + T: CryptoSource, +{ + pub apps: HashMap>, // hyper::uriで抜いたhostで引っ掛ける pub default_server_name_bytes: Option, // for plaintext http } + +impl Backends +where + T: CryptoSource, +{ + pub fn new() -> Self { + Backends { + apps: HashMap::>::default(), + default_server_name_bytes: None, + } + } +} diff --git a/src/cert_file_reader.rs b/src/cert_file_reader.rs index c3a3310..dc25b09 100644 --- a/src/cert_file_reader.rs +++ b/src/cert_file_reader.rs @@ -11,7 +11,7 @@ use std::{ path::PathBuf, }; -#[derive(Builder, Debug)] +#[derive(Builder, Debug, Clone)] /// Crypto-related file reader implementing certs::CryptoRead trait pub struct CryptoFileSource { #[builder(setter(custom))] diff --git a/src/config/parse.rs b/src/config/parse.rs index 1593aba..83aa546 100644 --- a/src/config/parse.rs +++ b/src/config/parse.rs @@ -1,9 +1,12 @@ use super::toml::ConfigToml; -use crate::{backend::Backends, error::*, globals::*, log::*, utils::BytesName}; +use crate::{backend::Backends, certs::CryptoSource, error::*, globals::*, log::*, utils::BytesName}; use clap::Arg; use tokio::runtime::Handle; -pub fn build_globals(runtime_handle: Handle) -> std::result::Result { +pub fn build_globals(runtime_handle: Handle) -> std::result::Result, anyhow::Error> +where + T: CryptoSource + Clone, +{ let _ = include_str!("../../Cargo.toml"); let options = clap::command!().arg( Arg::new("config_file") @@ -72,7 +75,7 @@ pub fn build_globals(runtime_handle: Handle) -> std::result::Result for &Application { +impl TryInto> for &Application +where + T: CryptoSource + Clone, +{ type Error = anyhow::Error; - fn try_into(self) -> std::result::Result { + fn try_into(self) -> std::result::Result, Self::Error> { let server_name_string = self.server_name.as_ref().ok_or(anyhow!("Missing server_name"))?; // backend builder diff --git a/src/globals.rs b/src/globals.rs index 64f9d8d..b85733b 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -1,3 +1,4 @@ +use crate::certs::CryptoSource; use crate::{backend::Backends, constants::*}; use std::net::SocketAddr; use std::sync::{ @@ -8,12 +9,15 @@ use tokio::time::Duration; /// Global object containing proxy configurations and shared object like counters. /// But note that in Globals, we do not have Mutex and RwLock. It is indeed, the context shared among async tasks. -pub struct Globals { +pub struct Globals +where + T: CryptoSource, +{ /// Configuration parameters for proxy transport and request handlers pub proxy_config: ProxyConfig, // TODO: proxy configはarcに包んでこいつだけ使いまわせばいいように変えていく。backendsも? /// Backend application objects to which http request handler forward incoming requests - pub backends: Backends, + pub backends: Backends, /// Shared context - Counter for serving requests pub request_count: RequestCount, diff --git a/src/handler/handler_main.rs b/src/handler/handler_main.rs index d2a47be..2016f2c 100644 --- a/src/handler/handler_main.rs +++ b/src/handler/handler_main.rs @@ -2,6 +2,7 @@ use super::{utils_headers::*, utils_request::*, utils_synth_response::*, HandlerContext}; use crate::{ backend::{Backend, UpstreamGroup}, + certs::CryptoSource, error::*, globals::Globals, log::*, @@ -18,17 +19,19 @@ use std::{env, net::SocketAddr, sync::Arc}; use tokio::{io::copy_bidirectional, time::timeout}; #[derive(Clone, Builder)] -pub struct HttpMessageHandler +pub struct HttpMessageHandler where T: Connect + Clone + Sync + Send + 'static, + U: CryptoSource + Clone, { forwarder: Arc>, - globals: Arc, + globals: Arc>, } -impl HttpMessageHandler +impl HttpMessageHandler where T: Connect + Clone + Sync + Send + 'static, + U: CryptoSource + Clone, { fn return_with_error_log(&self, status_code: StatusCode, log_data: &mut MessageLog) -> Result> { log_data.status_code(&status_code).output(); @@ -194,11 +197,10 @@ where //////////////////////////////////////////////////// // Functions to generate messages - fn generate_response_forwarded( - &self, - response: &mut Response, - chosen_backend: &Backend, - ) -> Result<()> { + fn generate_response_forwarded(&self, response: &mut Response, chosen_backend: &Backend) -> Result<()> + where + B: core::fmt::Debug, + { let headers = response.headers_mut(); remove_connection_header(headers); remove_hop_header(headers); diff --git a/src/main.rs b/src/main.rs index 526c290..7f8dcfc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +use certs::CryptoSource; #[cfg(not(target_env = "msvc"))] use tikv_jemallocator::Jemalloc; @@ -18,7 +19,8 @@ mod proxy; mod utils; use crate::{ - config::build_globals, error::*, globals::*, handler::HttpMessageHandlerBuilder, log::*, proxy::ProxyBuilder, + cert_file_reader::CryptoFileSource, config::build_globals, error::*, globals::*, handler::HttpMessageHandlerBuilder, + log::*, proxy::ProxyBuilder, }; use futures::future::select_all; use hyper::Client; @@ -34,7 +36,7 @@ fn main() { let runtime = runtime_builder.build().unwrap(); runtime.block_on(async { - let globals = match build_globals(runtime.handle().clone()) { + let globals: Globals = match build_globals(runtime.handle().clone()) { Ok(g) => g, Err(e) => { error!("Invalid configuration: {}", e); @@ -48,7 +50,10 @@ fn main() { } // entrypoint creates and spawns tasks of proxy services -async fn entrypoint(globals: Arc) -> Result<()> { +async fn entrypoint(globals: Arc>) -> Result<()> +where + T: CryptoSource + Clone + Send + Sync + 'static, +{ // let connector = TrustDnsResolver::default().into_rustls_webpki_https_connector(); let connector = hyper_rustls::HttpsConnectorBuilder::new() .with_webpki_roots() diff --git a/src/proxy/crypto_service.rs b/src/proxy/crypto_service.rs index 629119b..bb582fa 100644 --- a/src/proxy/crypto_service.rs +++ b/src/proxy/crypto_service.rs @@ -1,6 +1,6 @@ use crate::{ cert_file_reader::read_certs_and_keys, // TODO: Trait defining read_certs_and_keys and add struct implementing the trait to backend when build backend - certs::CertsAndKeys, + certs::{CertsAndKeys, CryptoSource}, globals::Globals, log::*, utils::ServerNameBytesExp, @@ -18,8 +18,11 @@ use x509_parser::prelude::*; #[derive(Clone)] /// Reloader service for certificates and keys for TLS -pub struct CryptoReloader { - globals: Arc, +pub struct CryptoReloader +where + T: CryptoSource, +{ + globals: Arc>, } pub type SniServerCryptoMap = HashMap>; @@ -37,8 +40,11 @@ pub struct ServerCryptoBase { } #[async_trait] -impl Reload for CryptoReloader { - type Source = Arc; +impl Reload for CryptoReloader +where + T: CryptoSource + Sync + Send, +{ + type Source = Arc>; async fn new(source: &Self::Source) -> Result> { Ok(Self { globals: source.clone(), diff --git a/src/proxy/proxy_h3.rs b/src/proxy/proxy_h3.rs index 12ebd7d..324060f 100644 --- a/src/proxy/proxy_h3.rs +++ b/src/proxy/proxy_h3.rs @@ -1,14 +1,15 @@ use super::Proxy; -use crate::{error::*, log::*, utils::ServerNameBytesExp}; +use crate::{certs::CryptoSource, error::*, log::*, utils::ServerNameBytesExp}; use bytes::{Buf, Bytes}; use h3::{quic::BidiStream, server::RequestStream}; use hyper::{client::connect::Connect, Body, Request, Response}; use std::net::SocketAddr; use tokio::time::{timeout, Duration}; -impl Proxy +impl Proxy where T: Connect + Clone + Sync + Send + 'static, + U: CryptoSource + Clone + Sync + Send + 'static, { pub(super) async fn connection_serve_h3( self, diff --git a/src/proxy/proxy_main.rs b/src/proxy/proxy_main.rs index a0f9660..e5a02a5 100644 --- a/src/proxy/proxy_main.rs +++ b/src/proxy/proxy_main.rs @@ -1,5 +1,7 @@ // use super::proxy_handler::handle_request; -use crate::{error::*, globals::Globals, handler::HttpMessageHandler, log::*, utils::ServerNameBytesExp}; +use crate::{ + certs::CryptoSource, error::*, globals::Globals, handler::HttpMessageHandler, log::*, utils::ServerNameBytesExp, +}; use derive_builder::{self, Builder}; use hyper::{client::connect::Connect, server::conn::Http, service::service_fn, Body, Request}; use std::{net::SocketAddr, sync::Arc}; @@ -32,19 +34,21 @@ where } #[derive(Clone, Builder)] -pub struct Proxy +pub struct Proxy where T: Connect + Clone + Sync + Send + 'static, + U: CryptoSource + Clone + Sync + Send + 'static, { pub listening_on: SocketAddr, pub tls_enabled: bool, // TCP待受がTLSかどうか - pub msg_handler: HttpMessageHandler, - pub globals: Arc, + pub msg_handler: HttpMessageHandler, + pub globals: Arc>, } -impl Proxy +impl Proxy where T: Connect + Clone + Sync + Send + 'static, + U: CryptoSource + Clone + Sync + Send, { pub(super) fn client_serve( self, diff --git a/src/proxy/proxy_tls.rs b/src/proxy/proxy_tls.rs index e01f9d3..5e846f0 100644 --- a/src/proxy/proxy_tls.rs +++ b/src/proxy/proxy_tls.rs @@ -2,7 +2,7 @@ use super::{ crypto_service::{CryptoReloader, ServerCrypto, ServerCryptoBase, SniServerCryptoMap}, proxy_main::{LocalExecutor, Proxy}, }; -use crate::{constants::*, error::*, log::*, utils::BytesName}; +use crate::{certs::CryptoSource, constants::*, error::*, log::*, utils::BytesName}; use hot_reload::{ReloaderReceiver, ReloaderService}; use hyper::{client::connect::Connect, server::conn::Http}; #[cfg(feature = "http3")] @@ -15,9 +15,10 @@ use tokio::{ time::{timeout, Duration}, }; -impl Proxy +impl Proxy where T: Connect + Clone + Sync + Send + 'static, + U: CryptoSource + Clone + Sync + Send + 'static, { // TCP Listener Service, i.e., http/2 and http/1.1 async fn listener_service( @@ -181,7 +182,7 @@ where } pub async fn start_with_tls(self, server: Http) -> Result<()> { - let (cert_reloader_service, cert_reloader_rx) = ReloaderService::::new( + let (cert_reloader_service, cert_reloader_rx) = ReloaderService::, ServerCryptoBase>::new( &self.globals.clone(), CERTS_WATCH_DELAY_SECS, !LOAD_CERTS_ONLY_WHEN_UPDATED, From 05b2aab8b0ee727526aabc001ae242109430c867 Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Wed, 12 Jul 2023 21:40:08 +0900 Subject: [PATCH 5/5] refactor: remove explict cert file path from backend mods and define abstracted trait for the cert source preparing librarization --- src/backend/mod.rs | 27 +++------------------------ src/cert_file_reader.rs | 15 ++++++++++----- src/certs.rs | 5 +++++ src/config/parse.rs | 14 +++++++++----- src/config/toml.rs | 31 ++++++++++++++++--------------- src/error.rs | 5 ++--- src/handler/handler_main.rs | 7 ++++++- src/proxy/crypto_service.rs | 11 ++++------- 8 files changed, 55 insertions(+), 60 deletions(-) diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 9bc28e5..524f30b 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -19,7 +19,7 @@ use crate::{ }; use derive_builder::Builder; use rustc_hash::FxHashMap as HashMap; -use std::{borrow::Cow, path::PathBuf}; +use std::borrow::Cow; /// Struct serving information to route incoming connections, like server name to be handled and tls certs/keys settings. #[derive(Builder)] @@ -36,16 +36,11 @@ where /// struct of reverse proxy serving incoming request pub reverse_proxy: ReverseProxy, - /// tls settings - #[builder(setter(custom), default)] - pub tls_cert_path: Option, - #[builder(setter(custom), default)] - pub tls_cert_key_path: Option, + /// tls settings: https redirection with 30x #[builder(default)] pub https_redirection: Option, - #[builder(setter(custom), default)] - pub client_ca_cert_path: Option, + /// TLS settings: source meta for server cert, key, client ca cert #[builder(default)] pub crypto_source: Option, } @@ -57,22 +52,6 @@ where self.server_name = Some(server_name.into().to_ascii_lowercase()); self } - pub fn tls_cert_path(&mut self, v: &Option) -> &mut Self { - self.tls_cert_path = Some(opt_string_to_opt_pathbuf(v)); - self - } - pub fn tls_cert_key_path(&mut self, v: &Option) -> &mut Self { - self.tls_cert_key_path = Some(opt_string_to_opt_pathbuf(v)); - self - } - pub fn client_ca_cert_path(&mut self, v: &Option) -> &mut Self { - self.client_ca_cert_path = Some(opt_string_to_opt_pathbuf(v)); - self - } -} - -fn opt_string_to_opt_pathbuf(input: &Option) -> Option { - input.to_owned().as_ref().map(PathBuf::from) } /// HashMap and some meta information for multiple Backend structs. diff --git a/src/cert_file_reader.rs b/src/cert_file_reader.rs index dc25b09..e25dcf7 100644 --- a/src/cert_file_reader.rs +++ b/src/cert_file_reader.rs @@ -36,8 +36,8 @@ impl CryptoFileSourceBuilder { self.tls_cert_key_path = Some(PathBuf::from(v)); self } - pub fn client_ca_cert_path(&mut self, v: &str) -> &mut Self { - self.client_ca_cert_path = Some(Some(PathBuf::from(v))); + pub fn client_ca_cert_path(&mut self, v: &Option) -> &mut Self { + self.client_ca_cert_path = Some(v.to_owned().as_ref().map(PathBuf::from)); self } } @@ -45,6 +45,7 @@ impl CryptoFileSourceBuilder { #[async_trait] impl CryptoSource for CryptoFileSource { type Error = io::Error; + /// read crypto materials from source async fn read(&self) -> Result { read_certs_and_keys( &self.tls_cert_path, @@ -52,10 +53,14 @@ impl CryptoSource for CryptoFileSource { self.client_ca_cert_path.as_ref(), ) } + /// Returns true when mutual tls is enabled + fn is_mutual_tls(&self) -> bool { + self.client_ca_cert_path.is_some() + } } /// Read certificates and private keys from file -pub(crate) fn read_certs_and_keys( +fn read_certs_and_keys( cert_path: &PathBuf, cert_key_path: &PathBuf, client_ca_cert_path: Option<&PathBuf>, @@ -162,11 +167,11 @@ mod tests { async fn read_server_crt_key_files_with_client_ca_crt() { let tls_cert_path = "example-certs/server.crt"; let tls_cert_key_path = "example-certs/server.key"; - let client_ca_cert_path = "example-certs/client.ca.crt"; + let client_ca_cert_path = Some("example-certs/client.ca.crt".to_string()); let crypto_file_source = CryptoFileSourceBuilder::default() .tls_cert_key_path(tls_cert_key_path) .tls_cert_path(tls_cert_path) - .client_ca_cert_path(client_ca_cert_path) + .client_ca_cert_path(&client_ca_cert_path) .build(); assert!(crypto_file_source.is_ok()); diff --git a/src/certs.rs b/src/certs.rs index da51e14..2ed0198 100644 --- a/src/certs.rs +++ b/src/certs.rs @@ -13,5 +13,10 @@ pub struct CertsAndKeys { // Trait to read certs and keys anywhere from KVS, file, sqlite, etc. pub trait CryptoSource { type Error; + + /// read crypto materials from source async fn read(&self) -> Result; + + /// Returns true when mutual tls is enabled + fn is_mutual_tls(&self) -> bool; } diff --git a/src/config/parse.rs b/src/config/parse.rs index 83aa546..1dc2545 100644 --- a/src/config/parse.rs +++ b/src/config/parse.rs @@ -1,12 +1,16 @@ use super::toml::ConfigToml; -use crate::{backend::Backends, certs::CryptoSource, error::*, globals::*, log::*, utils::BytesName}; +use crate::{ + backend::Backends, + cert_file_reader::CryptoFileSource, + error::{anyhow, ensure}, + globals::*, + log::*, + utils::BytesName, +}; use clap::Arg; use tokio::runtime::Handle; -pub fn build_globals(runtime_handle: Handle) -> std::result::Result, anyhow::Error> -where - T: CryptoSource + Clone, -{ +pub fn build_globals(runtime_handle: Handle) -> std::result::Result, anyhow::Error> { let _ = include_str!("../../Cargo.toml"); let options = clap::command!().arg( Arg::new("config_file") diff --git a/src/config/toml.rs b/src/config/toml.rs index a68b82b..f33ea4d 100644 --- a/src/config/toml.rs +++ b/src/config/toml.rs @@ -1,8 +1,8 @@ use crate::{ backend::{Backend, BackendBuilder, ReverseProxy, Upstream, UpstreamGroup, UpstreamGroupBuilder, UpstreamOption}, - certs::CryptoSource, + cert_file_reader::{CryptoFileSource, CryptoFileSourceBuilder}, constants::*, - error::*, + error::{anyhow, ensure}, globals::ProxyConfig, utils::PathNameBytesExp, }; @@ -164,20 +164,17 @@ impl TryInto for &ConfigToml { } impl ConfigToml { - pub fn new(config_file: &str) -> std::result::Result { - let config_str = fs::read_to_string(config_file).map_err(RpxyError::Io)?; + pub fn new(config_file: &str) -> std::result::Result { + let config_str = fs::read_to_string(config_file)?; - toml::from_str(&config_str).map_err(RpxyError::TomlDe) + toml::from_str(&config_str).map_err(|e| anyhow!(e)) } } -impl TryInto> for &Application -where - T: CryptoSource + Clone, -{ +impl TryInto> for &Application { type Error = anyhow::Error; - fn try_into(self) -> std::result::Result, Self::Error> { + fn try_into(self) -> std::result::Result, Self::Error> { let server_name_string = self.server_name.as_ref().ok_or(anyhow!("Missing server_name"))?; // backend builder @@ -203,11 +200,15 @@ where tls.https_redirection }; - backend_builder - .tls_cert_path(&tls.tls_cert_path) - .tls_cert_key_path(&tls.tls_cert_key_path) - .https_redirection(https_redirection) + let crypto_source = CryptoFileSourceBuilder::default() + .tls_cert_path(tls.tls_cert_path.as_ref().unwrap()) + .tls_cert_key_path(tls.tls_cert_key_path.as_ref().unwrap()) .client_ca_cert_path(&tls.client_ca_cert_path) + .build()?; + + backend_builder + .https_redirection(https_redirection) + .crypto_source(Some(crypto_source)) .build()? }; Ok(backend) @@ -255,7 +256,7 @@ impl TryInto for &Application { } impl TryInto for &UpstreamParams { - type Error = RpxyError; + type Error = anyhow::Error; fn try_into(self) -> std::result::Result { let scheme = match self.tls { diff --git a/src/error.rs b/src/error.rs index 18b4307..187c993 100644 --- a/src/error.rs +++ b/src/error.rs @@ -29,9 +29,8 @@ pub enum RpxyError { #[error("I/O Error")] Io(#[from] io::Error), - #[error("Toml Deserialization Error")] - TomlDe(#[from] toml::de::Error), - + // #[error("Toml Deserialization Error")] + // TomlDe(#[from] toml::de::Error), #[cfg(feature = "http3")] #[error("Quic Connection Error")] QuicConn(#[from] quinn::ConnectionError), diff --git a/src/handler/handler_main.rs b/src/handler/handler_main.rs index 2016f2c..c23fd24 100644 --- a/src/handler/handler_main.rs +++ b/src/handler/handler_main.rs @@ -209,7 +209,12 @@ where #[cfg(feature = "http3")] { // TODO: Workaround for avoid h3 for client authentication - if self.globals.proxy_config.http3 && chosen_backend.client_ca_cert_path.is_none() { + if self.globals.proxy_config.http3 + && chosen_backend + .crypto_source + .as_ref() + .is_some_and(|v| !v.is_mutual_tls()) + { if let Some(port) = self.globals.proxy_config.https_port { add_header_entry_overwrite_if_exist( headers, diff --git a/src/proxy/crypto_service.rs b/src/proxy/crypto_service.rs index bb582fa..8d7f00d 100644 --- a/src/proxy/crypto_service.rs +++ b/src/proxy/crypto_service.rs @@ -1,5 +1,4 @@ use crate::{ - cert_file_reader::read_certs_and_keys, // TODO: Trait defining read_certs_and_keys and add struct implementing the trait to backend when build backend certs::{CertsAndKeys, CryptoSource}, globals::Globals, log::*, @@ -55,13 +54,11 @@ where let mut certs_and_keys_map = ServerCryptoBase::default(); for (server_name_bytes_exp, backend) in self.globals.backends.apps.iter() { - if backend.tls_cert_key_path.is_some() && backend.tls_cert_path.is_some() { - let tls_cert_key_path = backend.tls_cert_key_path.as_ref().unwrap(); - let tls_cert_path = backend.tls_cert_path.as_ref().unwrap(); - let tls_client_ca_cert_path = backend.client_ca_cert_path.as_ref(); - let certs_and_keys = read_certs_and_keys(tls_cert_path, tls_cert_key_path, tls_client_ca_cert_path) + if let Some(crypto_source) = &backend.crypto_source { + let certs_and_keys = crypto_source + .read() + .await .map_err(|_e| ReloaderError::::Reload("Failed to reload cert, key or ca cert"))?; - certs_and_keys_map .inner .insert(server_name_bytes_exp.to_owned(), certs_and_keys);