From d3a45cba66592cb1355901991d28ac3f2788a9b2 Mon Sep 17 00:00:00 2001 From: hatoo Date: Sun, 17 Nov 2024 20:35:27 +0900 Subject: [PATCH 1/3] wip --- Cargo.toml | 1 + examples/reqwest_proxy.rs | 120 ++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/reqwest_client.rs | 30 ++++++++++ 4 files changed, 152 insertions(+) create mode 100644 examples/reqwest_proxy.rs create mode 100644 src/reqwest_client.rs diff --git a/Cargo.toml b/Cargo.toml index 4eee9e9..3ea4e3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ native-tls = { version = "0.2.12", features = ["alpn"] } thiserror = "2.0.2" moka = { version = "0.12.8", features = ["sync"] } winnow = { version = "0.6.20", optional = true } +reqwest = "0.12.7" [dev-dependencies] axum = { version = "0.7.2", features = ["http2"] } diff --git a/examples/reqwest_proxy.rs b/examples/reqwest_proxy.rs new file mode 100644 index 0000000..7fcac0e --- /dev/null +++ b/examples/reqwest_proxy.rs @@ -0,0 +1,120 @@ +use std::path::PathBuf; + +use clap::{Args, Parser}; +use http_mitm_proxy::MitmProxy; +use hyper::service::service_fn; +use moka::sync::Cache; +use tracing_subscriber::EnvFilter; + +#[derive(Parser)] +struct Opt { + #[clap(flatten)] + external_cert: Option, +} + +#[derive(Args, Debug)] +struct ExternalCert { + #[arg(required = false)] + cert: PathBuf, + #[arg(required = false)] + private_key: PathBuf, +} + +fn make_root_cert() -> rcgen::CertifiedKey { + let mut param = rcgen::CertificateParams::default(); + + param.distinguished_name = rcgen::DistinguishedName::new(); + param.distinguished_name.push( + rcgen::DnType::CommonName, + rcgen::DnValue::Utf8String("".to_string()), + ); + param.key_usages = vec![ + rcgen::KeyUsagePurpose::KeyCertSign, + rcgen::KeyUsagePurpose::CrlSign, + ]; + param.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained); + + let key_pair = rcgen::KeyPair::generate().unwrap(); + let cert = param.self_signed(&key_pair).unwrap(); + + rcgen::CertifiedKey { cert, key_pair } +} + +#[tokio::main] +async fn main() { + let opt = Opt::parse(); + + tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_default_env()) + .init(); + + let root_cert = if let Some(external_cert) = opt.external_cert { + // Use existing key + let param = rcgen::CertificateParams::from_ca_cert_pem( + &std::fs::read_to_string(&external_cert.cert).unwrap(), + ) + .unwrap(); + let key_pair = + rcgen::KeyPair::from_pem(&std::fs::read_to_string(&external_cert.private_key).unwrap()) + .unwrap(); + + let cert = param.self_signed(&key_pair).unwrap(); + + rcgen::CertifiedKey { cert, key_pair } + } else { + make_root_cert() + }; + + let root_cert_pem = root_cert.cert.pem(); + let root_cert_key = root_cert.key_pair.serialize_pem(); + + let proxy = MitmProxy::new( + // This is the root cert that will be used to sign the fake certificates + Some(root_cert), + Some(Cache::new(128)), + ); + + let client = reqwest::Client::new(); + let server = proxy + .bind( + ("127.0.0.1", 3003), + service_fn(move |req| { + let client = client.clone(); + async move { + let uri = req.uri().clone(); + + // You can modify request here + // or You can just return response anywhere + + let req = http_mitm_proxy::reqwest_client::to_reqwest(req); + let res = client.execute(req).await?; + + println!("{} -> {}", uri, res.status()); + + // You can modify response here + + Ok::<_, reqwest::Error>(http_mitm_proxy::reqwest_client::from_reqwest(res)) + } + }), + ) + .await + .unwrap(); + + println!("HTTP Proxy is listening on http://127.0.0.1:3003"); + + println!(); + println!("Trust this cert if you want to use HTTPS"); + println!(); + println!("{}", root_cert_pem); + println!(); + + /* + Save this cert to ca.crt and use it with curl like this: + curl https://www.google.com -x http://127.0.0.1:3003 --cacert ca.crt + */ + + println!("Private key"); + println!("{}", root_cert_key); + + server.await; +} diff --git a/src/lib.rs b/src/lib.rs index f514d86..4edd750 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,7 @@ pub use moka; pub use tokio_native_tls; pub mod default_client; +pub mod reqwest_client; mod tls; pub use default_client::DefaultClient; diff --git a/src/reqwest_client.rs b/src/reqwest_client.rs new file mode 100644 index 0000000..3577acc --- /dev/null +++ b/src/reqwest_client.rs @@ -0,0 +1,30 @@ +use bytes::Bytes; +use hyper::{body::Body, Request}; + +pub fn to_reqwest(req: Request) -> reqwest::Request +where + T: Body + Send + Sync + 'static, + T::Data: Into, + T::Error: Into>, +{ + let (parts, body) = req.into_parts(); + let url = reqwest::Url::parse(&parts.uri.to_string()).unwrap(); + let mut req = reqwest::Request::new(parts.method, url); + *req.headers_mut() = parts.headers; + req.body_mut().replace(reqwest::Body::wrap(body)); + *req.version_mut() = parts.version; + + req +} + +pub fn from_reqwest(res: reqwest::Response) -> hyper::Response { + let mut hres = hyper::Response::builder() + .status(res.status()) + .version(res.version()); + + *hres.headers_mut().unwrap() = res.headers().clone(); + + let body = reqwest::Body::from(res); + + hres.body(body).unwrap() +} From 16fc3db64d2d97a7e29006fa89666d749ed3c071 Mon Sep 17 00:00:00 2001 From: hatoo Date: Sun, 17 Nov 2024 20:43:55 +0900 Subject: [PATCH 2/3] add feature gate --- Cargo.toml | 10 +++++++--- src/lib.rs | 1 + 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3ea4e3f..be9e47f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,9 +10,13 @@ authors = ["hatoo "] license = "MIT" categories = ["network-programming", "web-programming::http-server"] keywords = ["http", "proxy", "http-proxy"] +resolver = "2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +reqwest = ["dep:reqwest"] + [dependencies] tokio = { version = "1.39.3", features = [ "macros", @@ -35,8 +39,7 @@ hyper-util = { version = "0.1.7", features = ["tokio"] } native-tls = { version = "0.2.12", features = ["alpn"] } thiserror = "2.0.2" moka = { version = "0.12.8", features = ["sync"] } -winnow = { version = "0.6.20", optional = true } -reqwest = "0.12.7" +reqwest = { version = "0.12.7", optional = true } [dev-dependencies] axum = { version = "0.7.2", features = ["http2"] } @@ -47,4 +50,5 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } winnow = "0.6.20" [[example]] -name = "websocket" +name = "reqwest_proxy" +required-features = ["reqwest"] diff --git a/src/lib.rs b/src/lib.rs index 4edd750..cce2a79 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,7 @@ pub use moka; pub use tokio_native_tls; pub mod default_client; +#[cfg(feature = "reqwest")] pub mod reqwest_client; mod tls; From 567db4f45885e15e9bd6661c96fadf0b375a0fd3 Mon Sep 17 00:00:00 2001 From: hatoo Date: Sun, 17 Nov 2024 20:48:26 +0900 Subject: [PATCH 3/3] OK --- Cargo.toml | 8 -------- examples/reqwest_proxy.rs | 36 +++++++++++++++++++++++++++++++++--- src/lib.rs | 2 -- src/reqwest_client.rs | 30 ------------------------------ 4 files changed, 33 insertions(+), 43 deletions(-) delete mode 100644 src/reqwest_client.rs diff --git a/Cargo.toml b/Cargo.toml index be9e47f..d7fa6d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,9 +14,6 @@ resolver = "2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[features] -reqwest = ["dep:reqwest"] - [dependencies] tokio = { version = "1.39.3", features = [ "macros", @@ -39,7 +36,6 @@ hyper-util = { version = "0.1.7", features = ["tokio"] } native-tls = { version = "0.2.12", features = ["alpn"] } thiserror = "2.0.2" moka = { version = "0.12.8", features = ["sync"] } -reqwest = { version = "0.12.7", optional = true } [dev-dependencies] axum = { version = "0.7.2", features = ["http2"] } @@ -48,7 +44,3 @@ rcgen = { version = "0.13.1", features = ["x509-parser"] } reqwest = { version = "0.12.7", features = ["native-tls-alpn"] } tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } winnow = "0.6.20" - -[[example]] -name = "reqwest_proxy" -required-features = ["reqwest"] diff --git a/examples/reqwest_proxy.rs b/examples/reqwest_proxy.rs index 7fcac0e..93d1161 100644 --- a/examples/reqwest_proxy.rs +++ b/examples/reqwest_proxy.rs @@ -1,8 +1,10 @@ +/// This example demonstrates how to use `http-mitm-proxy` with `reqwest`. use std::path::PathBuf; +use bytes::Bytes; use clap::{Args, Parser}; use http_mitm_proxy::MitmProxy; -use hyper::service::service_fn; +use hyper::{body::Body, service::service_fn}; use moka::sync::Cache; use tracing_subscriber::EnvFilter; @@ -86,14 +88,14 @@ async fn main() { // You can modify request here // or You can just return response anywhere - let req = http_mitm_proxy::reqwest_client::to_reqwest(req); + let req = to_reqwest(req); let res = client.execute(req).await?; println!("{} -> {}", uri, res.status()); // You can modify response here - Ok::<_, reqwest::Error>(http_mitm_proxy::reqwest_client::from_reqwest(res)) + Ok::<_, reqwest::Error>(from_reqwest(res)) } }), ) @@ -118,3 +120,31 @@ async fn main() { server.await; } + +fn to_reqwest(req: hyper::Request) -> reqwest::Request +where + T: Body + Send + Sync + 'static, + T::Data: Into, + T::Error: Into>, +{ + let (parts, body) = req.into_parts(); + let url = reqwest::Url::parse(&parts.uri.to_string()).unwrap(); + let mut req = reqwest::Request::new(parts.method, url); + *req.headers_mut() = parts.headers; + req.body_mut().replace(reqwest::Body::wrap(body)); + *req.version_mut() = parts.version; + + req +} + +fn from_reqwest(res: reqwest::Response) -> hyper::Response { + let mut hres = hyper::Response::builder() + .status(res.status()) + .version(res.version()); + + *hres.headers_mut().unwrap() = res.headers().clone(); + + let body = reqwest::Body::from(res); + + hres.body(body).unwrap() +} diff --git a/src/lib.rs b/src/lib.rs index cce2a79..f514d86 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,8 +19,6 @@ pub use moka; pub use tokio_native_tls; pub mod default_client; -#[cfg(feature = "reqwest")] -pub mod reqwest_client; mod tls; pub use default_client::DefaultClient; diff --git a/src/reqwest_client.rs b/src/reqwest_client.rs deleted file mode 100644 index 3577acc..0000000 --- a/src/reqwest_client.rs +++ /dev/null @@ -1,30 +0,0 @@ -use bytes::Bytes; -use hyper::{body::Body, Request}; - -pub fn to_reqwest(req: Request) -> reqwest::Request -where - T: Body + Send + Sync + 'static, - T::Data: Into, - T::Error: Into>, -{ - let (parts, body) = req.into_parts(); - let url = reqwest::Url::parse(&parts.uri.to_string()).unwrap(); - let mut req = reqwest::Request::new(parts.method, url); - *req.headers_mut() = parts.headers; - req.body_mut().replace(reqwest::Body::wrap(body)); - *req.version_mut() = parts.version; - - req -} - -pub fn from_reqwest(res: reqwest::Response) -> hyper::Response { - let mut hres = hyper::Response::builder() - .status(res.status()) - .version(res.version()); - - *hres.headers_mut().unwrap() = res.headers().clone(); - - let body = reqwest::Body::from(res); - - hres.body(body).unwrap() -}