Merge pull request #56 from junkurihara/cert-reader-trait
Refactor: preparing the rpxy librarization first step
This commit is contained in:
commit
f9150a6d10
15 changed files with 334 additions and 186 deletions
|
|
@ -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};
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// 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<T>
|
||||
where
|
||||
T: CryptoSource,
|
||||
{
|
||||
#[builder(setter(into))]
|
||||
/// backend application name, e.g., app1
|
||||
pub app_name: String,
|
||||
|
|
@ -30,42 +36,41 @@ pub struct Backend {
|
|||
/// struct of reverse proxy serving incoming request
|
||||
pub reverse_proxy: ReverseProxy,
|
||||
|
||||
/// tls settings
|
||||
#[builder(setter(custom), default)]
|
||||
pub tls_cert_path: Option<PathBuf>,
|
||||
#[builder(setter(custom), default)]
|
||||
pub tls_cert_key_path: Option<PathBuf>,
|
||||
/// tls settings: https redirection with 30x
|
||||
#[builder(default)]
|
||||
pub https_redirection: Option<bool>,
|
||||
#[builder(setter(custom), default)]
|
||||
pub client_ca_cert_path: Option<PathBuf>,
|
||||
|
||||
/// TLS settings: source meta for server cert, key, client ca cert
|
||||
#[builder(default)]
|
||||
pub crypto_source: Option<T>,
|
||||
}
|
||||
impl<'a> BackendBuilder {
|
||||
impl<'a, T> BackendBuilder<T>
|
||||
where
|
||||
T: CryptoSource,
|
||||
{
|
||||
pub fn server_name(&mut self, server_name: impl Into<Cow<'a, str>>) -> &mut Self {
|
||||
self.server_name = Some(server_name.into().to_ascii_lowercase());
|
||||
self
|
||||
}
|
||||
pub fn tls_cert_path(&mut self, v: &Option<String>) -> &mut Self {
|
||||
self.tls_cert_path = Some(opt_string_to_opt_pathbuf(v));
|
||||
self
|
||||
}
|
||||
pub fn tls_cert_key_path(&mut self, v: &Option<String>) -> &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<String>) -> &mut Self {
|
||||
self.client_ca_cert_path = Some(opt_string_to_opt_pathbuf(v));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
fn opt_string_to_opt_pathbuf(input: &Option<String>) -> Option<PathBuf> {
|
||||
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<ServerNameBytesExp, Backend>, // hyper::uriで抜いたhostで引っ掛ける
|
||||
pub struct Backends<T>
|
||||
where
|
||||
T: CryptoSource,
|
||||
{
|
||||
pub apps: HashMap<ServerNameBytesExp, Backend<T>>, // hyper::uriで抜いたhostで引っ掛ける
|
||||
pub default_server_name_bytes: Option<ServerNameBytesExp>, // for plaintext http
|
||||
}
|
||||
|
||||
impl<T> Backends<T>
|
||||
where
|
||||
T: CryptoSource,
|
||||
{
|
||||
pub fn new() -> Self {
|
||||
Backends {
|
||||
apps: HashMap::<ServerNameBytesExp, Backend<T>>::default(),
|
||||
default_server_name_bytes: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
185
src/cert_file_reader.rs
Normal file
185
src/cert_file_reader.rs
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
use crate::{
|
||||
certs::{CertsAndKeys, CryptoSource},
|
||||
log::*,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use derive_builder::Builder;
|
||||
use rustls::{Certificate, PrivateKey};
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{self, BufReader, Cursor, Read},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
#[derive(Builder, Debug, Clone)]
|
||||
/// Crypto-related file reader implementing certs::CryptoRead trait
|
||||
pub struct CryptoFileSource {
|
||||
#[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<PathBuf>,
|
||||
}
|
||||
|
||||
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: &Option<String>) -> &mut Self {
|
||||
self.client_ca_cert_path = Some(v.to_owned().as_ref().map(PathBuf::from));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CryptoSource for CryptoFileSource {
|
||||
type Error = io::Error;
|
||||
/// read crypto materials from source
|
||||
async fn read(&self) -> Result<CertsAndKeys, Self::Error> {
|
||||
read_certs_and_keys(
|
||||
&self.tls_cert_path,
|
||||
&self.tls_cert_key_path,
|
||||
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
|
||||
fn read_certs_and_keys(
|
||||
cert_path: &PathBuf,
|
||||
cert_key_path: &PathBuf,
|
||||
client_ca_cert_path: Option<&PathBuf>,
|
||||
) -> Result<CertsAndKeys, io::Error> {
|
||||
debug!("Read TLS server certificates and private key");
|
||||
|
||||
let certs: Vec<_> = {
|
||||
let certs_path_str = cert_path.display().to_string();
|
||||
let mut reader = BufReader::new(File::open(cert_path).map_err(|e| {
|
||||
io::Error::new(
|
||||
e.kind(),
|
||||
format!("Unable to load the certificates [{certs_path_str}]: {e}"),
|
||||
)
|
||||
})?);
|
||||
rustls_pemfile::certs(&mut reader)
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "Unable to parse the certificates"))?
|
||||
}
|
||||
.drain(..)
|
||||
.map(Certificate)
|
||||
.collect();
|
||||
|
||||
let cert_keys: Vec<_> = {
|
||||
let cert_key_path_str = cert_key_path.display().to_string();
|
||||
let encoded_keys = {
|
||||
let mut encoded_keys = vec![];
|
||||
File::open(cert_key_path)
|
||||
.map_err(|e| {
|
||||
io::Error::new(
|
||||
e.kind(),
|
||||
format!("Unable to load the certificate keys [{cert_key_path_str}]: {e}"),
|
||||
)
|
||||
})?
|
||||
.read_to_end(&mut encoded_keys)?;
|
||||
encoded_keys
|
||||
};
|
||||
let mut reader = Cursor::new(encoded_keys);
|
||||
let pkcs8_keys = rustls_pemfile::pkcs8_private_keys(&mut reader).map_err(|_| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Unable to parse the certificates private keys (PKCS8)",
|
||||
)
|
||||
})?;
|
||||
reader.set_position(0);
|
||||
let mut rsa_keys = rustls_pemfile::rsa_private_keys(&mut reader)?;
|
||||
let mut keys = pkcs8_keys;
|
||||
keys.append(&mut rsa_keys);
|
||||
if keys.is_empty() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"No private keys found - Make sure that they are in PKCS#8/PEM format",
|
||||
));
|
||||
}
|
||||
keys.drain(..).map(PrivateKey).collect()
|
||||
};
|
||||
|
||||
let client_ca_certs = if let Some(path) = client_ca_cert_path {
|
||||
debug!("Read CA certificates for client authentication");
|
||||
// Reads client certificate and returns client
|
||||
let certs: Vec<_> = {
|
||||
let certs_path_str = path.display().to_string();
|
||||
let mut reader = BufReader::new(File::open(path).map_err(|e| {
|
||||
io::Error::new(
|
||||
e.kind(),
|
||||
format!("Unable to load the client certificates [{certs_path_str}]: {e}"),
|
||||
)
|
||||
})?);
|
||||
rustls_pemfile::certs(&mut reader)
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "Unable to parse the client certificates"))?
|
||||
}
|
||||
.drain(..)
|
||||
.map(Certificate)
|
||||
.collect();
|
||||
Some(certs)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(CertsAndKeys {
|
||||
certs,
|
||||
cert_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 = 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)
|
||||
.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());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
use crate::{log::*, proxy::CertsAndKeys};
|
||||
use rustls::{Certificate, PrivateKey};
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{self, BufReader, Cursor, Read},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
/// Read certificates and private keys from file
|
||||
pub(crate) fn read_certs_and_keys(
|
||||
cert_path: &PathBuf,
|
||||
cert_key_path: &PathBuf,
|
||||
client_ca_cert_path: Option<&PathBuf>,
|
||||
) -> Result<CertsAndKeys, io::Error> {
|
||||
debug!("Read TLS server certificates and private key");
|
||||
|
||||
let certs: Vec<_> = {
|
||||
let certs_path_str = cert_path.display().to_string();
|
||||
let mut reader = BufReader::new(File::open(cert_path).map_err(|e| {
|
||||
io::Error::new(
|
||||
e.kind(),
|
||||
format!("Unable to load the certificates [{certs_path_str}]: {e}"),
|
||||
)
|
||||
})?);
|
||||
rustls_pemfile::certs(&mut reader)
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "Unable to parse the certificates"))?
|
||||
}
|
||||
.drain(..)
|
||||
.map(Certificate)
|
||||
.collect();
|
||||
|
||||
let cert_keys: Vec<_> = {
|
||||
let cert_key_path_str = cert_key_path.display().to_string();
|
||||
let encoded_keys = {
|
||||
let mut encoded_keys = vec![];
|
||||
File::open(cert_key_path)
|
||||
.map_err(|e| {
|
||||
io::Error::new(
|
||||
e.kind(),
|
||||
format!("Unable to load the certificate keys [{cert_key_path_str}]: {e}"),
|
||||
)
|
||||
})?
|
||||
.read_to_end(&mut encoded_keys)?;
|
||||
encoded_keys
|
||||
};
|
||||
let mut reader = Cursor::new(encoded_keys);
|
||||
let pkcs8_keys = rustls_pemfile::pkcs8_private_keys(&mut reader).map_err(|_| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Unable to parse the certificates private keys (PKCS8)",
|
||||
)
|
||||
})?;
|
||||
reader.set_position(0);
|
||||
let mut rsa_keys = rustls_pemfile::rsa_private_keys(&mut reader)?;
|
||||
let mut keys = pkcs8_keys;
|
||||
keys.append(&mut rsa_keys);
|
||||
if keys.is_empty() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"No private keys found - Make sure that they are in PKCS#8/PEM format",
|
||||
));
|
||||
}
|
||||
keys.drain(..).map(PrivateKey).collect()
|
||||
};
|
||||
|
||||
let client_ca_certs = if let Some(path) = client_ca_cert_path {
|
||||
debug!("Read CA certificates for client authentication");
|
||||
// Reads client certificate and returns client
|
||||
let certs: Vec<_> = {
|
||||
let certs_path_str = path.display().to_string();
|
||||
let mut reader = BufReader::new(File::open(path).map_err(|e| {
|
||||
io::Error::new(
|
||||
e.kind(),
|
||||
format!("Unable to load the client certificates [{certs_path_str}]: {e}"),
|
||||
)
|
||||
})?);
|
||||
rustls_pemfile::certs(&mut reader)
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "Unable to parse the client certificates"))?
|
||||
}
|
||||
.drain(..)
|
||||
.map(Certificate)
|
||||
.collect();
|
||||
Some(certs)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(CertsAndKeys {
|
||||
certs,
|
||||
cert_keys,
|
||||
client_ca_certs,
|
||||
})
|
||||
}
|
||||
22
src/certs.rs
Normal file
22
src/certs.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
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<Certificate>,
|
||||
pub cert_keys: Vec<PrivateKey>,
|
||||
pub client_ca_certs: Option<Vec<Certificate>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
// 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<CertsAndKeys, Self::Error>;
|
||||
|
||||
/// Returns true when mutual tls is enabled
|
||||
fn is_mutual_tls(&self) -> bool;
|
||||
}
|
||||
|
|
@ -1,9 +1,16 @@
|
|||
use super::toml::ConfigToml;
|
||||
use crate::{backend::Backends, 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<Globals, anyhow::Error> {
|
||||
pub fn build_globals(runtime_handle: Handle) -> std::result::Result<Globals<CryptoFileSource>, anyhow::Error> {
|
||||
let _ = include_str!("../../Cargo.toml");
|
||||
let options = clap::command!().arg(
|
||||
Arg::new("config_file")
|
||||
|
|
@ -72,7 +79,7 @@ pub fn build_globals(runtime_handle: Handle) -> std::result::Result<Globals, any
|
|||
}
|
||||
|
||||
// build backends
|
||||
let mut backends = Backends::default();
|
||||
let mut backends = Backends::new();
|
||||
for (app_name, app) in apps.0.iter() {
|
||||
let server_name_string = app.server_name.as_ref().ok_or(anyhow!("No server name"))?;
|
||||
let backend = app.try_into()?;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
use crate::{
|
||||
backend::{Backend, BackendBuilder, ReverseProxy, Upstream, UpstreamGroup, UpstreamGroupBuilder, UpstreamOption},
|
||||
cert_file_reader::{CryptoFileSource, CryptoFileSourceBuilder},
|
||||
constants::*,
|
||||
error::*,
|
||||
error::{anyhow, ensure},
|
||||
globals::ProxyConfig,
|
||||
utils::PathNameBytesExp,
|
||||
};
|
||||
|
|
@ -163,17 +164,17 @@ impl TryInto<ProxyConfig> for &ConfigToml {
|
|||
}
|
||||
|
||||
impl ConfigToml {
|
||||
pub fn new(config_file: &str) -> std::result::Result<Self, RpxyError> {
|
||||
let config_str = fs::read_to_string(config_file).map_err(RpxyError::Io)?;
|
||||
pub fn new(config_file: &str) -> std::result::Result<Self, anyhow::Error> {
|
||||
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<Backend> for &Application {
|
||||
impl TryInto<Backend<CryptoFileSource>> for &Application {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_into(self) -> std::result::Result<Backend, Self::Error> {
|
||||
fn try_into(self) -> std::result::Result<Backend<CryptoFileSource>, Self::Error> {
|
||||
let server_name_string = self.server_name.as_ref().ok_or(anyhow!("Missing server_name"))?;
|
||||
|
||||
// backend builder
|
||||
|
|
@ -199,11 +200,15 @@ impl TryInto<Backend> for &Application {
|
|||
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)
|
||||
|
|
@ -251,7 +256,7 @@ impl TryInto<ReverseProxy> for &Application {
|
|||
}
|
||||
|
||||
impl TryInto<Upstream> for &UpstreamParams {
|
||||
type Error = RpxyError;
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_into(self) -> std::result::Result<Upstream, Self::Error> {
|
||||
let scheme = match self.tls {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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<T>
|
||||
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<T>,
|
||||
|
||||
/// Shared context - Counter for serving requests
|
||||
pub request_count: RequestCount,
|
||||
|
|
|
|||
|
|
@ -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<T>
|
||||
pub struct HttpMessageHandler<T, U>
|
||||
where
|
||||
T: Connect + Clone + Sync + Send + 'static,
|
||||
U: CryptoSource + Clone,
|
||||
{
|
||||
forwarder: Arc<Client<T>>,
|
||||
globals: Arc<Globals>,
|
||||
globals: Arc<Globals<U>>,
|
||||
}
|
||||
|
||||
impl<T> HttpMessageHandler<T>
|
||||
impl<T, U> HttpMessageHandler<T, U>
|
||||
where
|
||||
T: Connect + Clone + Sync + Send + 'static,
|
||||
U: CryptoSource + Clone,
|
||||
{
|
||||
fn return_with_error_log(&self, status_code: StatusCode, log_data: &mut MessageLog) -> Result<Response<Body>> {
|
||||
log_data.status_code(&status_code).output();
|
||||
|
|
@ -194,11 +197,10 @@ where
|
|||
////////////////////////////////////////////////////
|
||||
// Functions to generate messages
|
||||
|
||||
fn generate_response_forwarded<B: core::fmt::Debug>(
|
||||
&self,
|
||||
response: &mut Response<B>,
|
||||
chosen_backend: &Backend,
|
||||
) -> Result<()> {
|
||||
fn generate_response_forwarded<B>(&self, response: &mut Response<B>, chosen_backend: &Backend<U>) -> Result<()>
|
||||
where
|
||||
B: core::fmt::Debug,
|
||||
{
|
||||
let headers = response.headers_mut();
|
||||
remove_connection_header(headers);
|
||||
remove_hop_header(headers);
|
||||
|
|
@ -207,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,
|
||||
|
|
|
|||
14
src/main.rs
14
src/main.rs
|
|
@ -1,3 +1,4 @@
|
|||
use certs::CryptoSource;
|
||||
#[cfg(not(target_env = "msvc"))]
|
||||
use tikv_jemallocator::Jemalloc;
|
||||
|
||||
|
|
@ -6,7 +7,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;
|
||||
|
|
@ -17,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;
|
||||
|
|
@ -33,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<CryptoFileSource> = match build_globals(runtime.handle().clone()) {
|
||||
Ok(g) => g,
|
||||
Err(e) => {
|
||||
error!("Invalid configuration: {}", e);
|
||||
|
|
@ -47,7 +50,10 @@ fn main() {
|
|||
}
|
||||
|
||||
// entrypoint creates and spawns tasks of proxy services
|
||||
async fn entrypoint(globals: Arc<Globals>) -> Result<()> {
|
||||
async fn entrypoint<T>(globals: Arc<Globals<T>>) -> 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()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
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
|
||||
certs::{CertsAndKeys, CryptoSource},
|
||||
globals::Globals,
|
||||
log::*,
|
||||
utils::ServerNameBytesExp,
|
||||
|
|
@ -10,23 +10,18 @@ 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::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
/// Reloader service for certificates and keys for TLS
|
||||
pub struct CryptoReloader {
|
||||
globals: Arc<Globals>,
|
||||
}
|
||||
|
||||
/// Certificates and private keys in rustls loaded from files
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct CertsAndKeys {
|
||||
pub certs: Vec<Certificate>,
|
||||
pub cert_keys: Vec<PrivateKey>,
|
||||
pub client_ca_certs: Option<Vec<Certificate>>,
|
||||
pub struct CryptoReloader<T>
|
||||
where
|
||||
T: CryptoSource,
|
||||
{
|
||||
globals: Arc<Globals<T>>,
|
||||
}
|
||||
|
||||
pub type SniServerCryptoMap = HashMap<ServerNameBytesExp, Arc<ServerConfig>>;
|
||||
|
|
@ -44,8 +39,11 @@ pub struct ServerCryptoBase {
|
|||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Reload<ServerCryptoBase> for CryptoReloader {
|
||||
type Source = Arc<Globals>;
|
||||
impl<T> Reload<ServerCryptoBase> for CryptoReloader<T>
|
||||
where
|
||||
T: CryptoSource + Sync + Send,
|
||||
{
|
||||
type Source = Arc<Globals<T>>;
|
||||
async fn new(source: &Self::Source) -> Result<Self, ReloaderError<ServerCryptoBase>> {
|
||||
Ok(Self {
|
||||
globals: source.clone(),
|
||||
|
|
@ -56,13 +54,11 @@ impl Reload<ServerCryptoBase> for CryptoReloader {
|
|||
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::<ServerCryptoBase>::Reload("Failed to reload cert, key or ca cert"))?;
|
||||
|
||||
certs_and_keys_map
|
||||
.inner
|
||||
.insert(server_name_bytes_exp.to_owned(), certs_and_keys);
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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<T> Proxy<T>
|
||||
impl<T, U> Proxy<T, U>
|
||||
where
|
||||
T: Connect + Clone + Sync + Send + 'static,
|
||||
U: CryptoSource + Clone + Sync + Send + 'static,
|
||||
{
|
||||
pub(super) async fn connection_serve_h3(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -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<T>
|
||||
pub struct Proxy<T, U>
|
||||
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<T>,
|
||||
pub globals: Arc<Globals>,
|
||||
pub msg_handler: HttpMessageHandler<T, U>,
|
||||
pub globals: Arc<Globals<U>>,
|
||||
}
|
||||
|
||||
impl<T> Proxy<T>
|
||||
impl<T, U> Proxy<T, U>
|
||||
where
|
||||
T: Connect + Clone + Sync + Send + 'static,
|
||||
U: CryptoSource + Clone + Sync + Send,
|
||||
{
|
||||
pub(super) fn client_serve<I>(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -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<T> Proxy<T>
|
||||
impl<T, U> Proxy<T, U>
|
||||
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<LocalExecutor>) -> Result<()> {
|
||||
let (cert_reloader_service, cert_reloader_rx) = ReloaderService::<CryptoReloader, ServerCryptoBase>::new(
|
||||
let (cert_reloader_service, cert_reloader_rx) = ReloaderService::<CryptoReloader<U>, ServerCryptoBase>::new(
|
||||
&self.globals.clone(),
|
||||
CERTS_WATCH_DELAY_SECS,
|
||||
!LOAD_CERTS_ONLY_WHEN_UPDATED,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue