Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ All request information is available via the `r` object:
- `r.host` - Hostname from the URL
- `r.scheme` - URL scheme (http or https)
- `r.path` - Path portion of the URL
- `r.requester_ip` - IP address of the client making the request
- `r.block_message` - Optional message to set when denying (writable)

**JavaScript evaluation rules:**
Expand Down Expand Up @@ -225,6 +226,7 @@ If `--sh` has spaces, it's run through `sh`; otherwise it's executed directly.
- `HTTPJAIL_HOST` - Hostname from the URL
- `HTTPJAIL_SCHEME` - URL scheme (http or https)
- `HTTPJAIL_PATH` - Path component of the URL
- `HTTPJAIL_REQUESTER_IP` - IP address of the client making the request

**Script requirements:**

Expand All @@ -237,7 +239,6 @@ If `--sh` has spaces, it's run through `sh`; otherwise it's executed directly.
> Script-based evaluation can also be used for custom logging! Your script can log requests to a database, send metrics to a monitoring service, or implement complex audit trails before returning the allow/deny decision.

## Advanced Options

```bash
# Verbose logging
httpjail -vvv --js "true" -- curl https://example.com
Expand Down
30 changes: 23 additions & 7 deletions src/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,8 @@ impl ProxyServer {

tokio::spawn(async move {
if let Err(e) =
handle_http_connection(stream, rule_engine, cert_manager).await
handle_http_connection(stream, rule_engine, cert_manager, addr)
.await
{
error!("Error handling HTTP connection: {:?}", e);
}
Expand Down Expand Up @@ -335,7 +336,8 @@ impl ProxyServer {

tokio::spawn(async move {
if let Err(e) =
handle_https_connection(stream, rule_engine, cert_manager).await
handle_https_connection(stream, rule_engine, cert_manager, addr)
.await
{
error!("Error handling HTTPS connection: {:?}", e);
}
Expand Down Expand Up @@ -364,10 +366,16 @@ async fn handle_http_connection(
stream: TcpStream,
rule_engine: Arc<RuleEngine>,
cert_manager: Arc<CertificateManager>,
remote_addr: SocketAddr,
) -> Result<()> {
let io = TokioIo::new(stream);
let service = service_fn(move |req| {
handle_http_request(req, Arc::clone(&rule_engine), Arc::clone(&cert_manager))
handle_http_request(
req,
Arc::clone(&rule_engine),
Arc::clone(&cert_manager),
remote_addr,
)
});

http1::Builder::new()
Expand All @@ -383,15 +391,17 @@ async fn handle_https_connection(
stream: TcpStream,
rule_engine: Arc<RuleEngine>,
cert_manager: Arc<CertificateManager>,
remote_addr: SocketAddr,
) -> Result<()> {
// Delegate to the TLS-specific module
crate::proxy_tls::handle_https_connection(stream, rule_engine, cert_manager).await
crate::proxy_tls::handle_https_connection(stream, rule_engine, cert_manager, remote_addr).await
}

pub async fn handle_http_request(
req: Request<Incoming>,
rule_engine: Arc<RuleEngine>,
_cert_manager: Arc<CertificateManager>,
remote_addr: SocketAddr,
) -> Result<Response<BoxBody<Bytes, HyperError>>, std::convert::Infallible> {
let method = req.method().clone();
let uri = req.uri().clone();
Expand All @@ -412,10 +422,16 @@ pub async fn handle_http_request(
format!("http://{}{}", host, path)
};

debug!("Proxying HTTP request: {} {}", method, full_url);
debug!(
"Proxying HTTP request: {} {} from {}",
method, full_url, remote_addr
);

// Evaluate rules with method
let evaluation = rule_engine.evaluate_with_context(method, &full_url).await;
// Evaluate rules with method and requester IP
let requester_ip = remote_addr.ip().to_string();
let evaluation = rule_engine
.evaluate_with_context_and_ip(method, &full_url, &requester_ip)
.await;
match evaluation.action {
Action::Allow => {
debug!("Request allowed: {}", full_url);
Expand Down
61 changes: 39 additions & 22 deletions src/proxy_tls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ pub async fn handle_https_connection(
stream: TcpStream,
rule_engine: Arc<RuleEngine>,
cert_manager: Arc<CertificateManager>,
remote_addr: std::net::SocketAddr,
) -> Result<()> {
debug!("Handling new HTTPS connection");
debug!("Handling new HTTPS connection from {}", remote_addr);

// Peek at the first few bytes to determine if this is HTTP or TLS
let mut peek_buf = [0; 6];
Expand All @@ -64,18 +65,18 @@ pub async fn handle_https_connection(
if peek_buf[0] == 0x16 && n > 1 && (peek_buf[1] == 0x03 || peek_buf[1] == 0x02) {
// This is a TLS ClientHello - we're in transparent proxy mode
debug!("Detected TLS ClientHello - transparent proxy mode");
handle_transparent_tls(stream, rule_engine, cert_manager).await
handle_transparent_tls(stream, rule_engine, cert_manager, remote_addr).await
} else if peek_buf[0] >= 0x41 && peek_buf[0] <= 0x5A {
// This looks like HTTP (starts with uppercase ASCII letter)
// Check if it's a CONNECT request
let request_str = String::from_utf8_lossy(&peek_buf);
if request_str.starts_with("CONNEC") {
debug!("Detected CONNECT request - explicit proxy mode");
handle_connect_tunnel(stream, rule_engine, cert_manager).await
handle_connect_tunnel(stream, rule_engine, cert_manager, remote_addr).await
} else {
// Regular HTTP on HTTPS port
debug!("Detected plain HTTP on HTTPS port");
handle_plain_http(stream, rule_engine, cert_manager).await
handle_plain_http(stream, rule_engine, cert_manager, remote_addr).await
}
} else {
warn!(
Expand Down Expand Up @@ -159,6 +160,7 @@ async fn handle_transparent_tls(
mut stream: TcpStream,
rule_engine: Arc<RuleEngine>,
cert_manager: Arc<CertificateManager>,
remote_addr: std::net::SocketAddr,
) -> Result<()> {
debug!("Handling transparent TLS connection");

Expand Down Expand Up @@ -212,7 +214,7 @@ async fn handle_transparent_tls(
let io = TokioIo::new(tls_stream);
let service = service_fn(move |req| {
let host_clone = hostname.clone();
handle_decrypted_https_request(req, Arc::clone(&rule_engine), host_clone)
handle_decrypted_https_request(req, Arc::clone(&rule_engine), host_clone, remote_addr)
});

debug!("Starting HTTP/1.1 server for decrypted requests");
Expand All @@ -230,6 +232,7 @@ async fn handle_connect_tunnel(
stream: TcpStream,
rule_engine: Arc<RuleEngine>,
cert_manager: Arc<CertificateManager>,
remote_addr: std::net::SocketAddr,
) -> Result<()> {
debug!("Handling CONNECT tunnel");

Expand Down Expand Up @@ -305,8 +308,9 @@ async fn handle_connect_tunnel(

// Check if this host is allowed
let full_url = format!("https://{}", target);
let requester_ip = remote_addr.ip().to_string();
let evaluation = rule_engine
.evaluate_with_context(Method::GET, &full_url)
.evaluate_with_context_and_ip(Method::GET, &full_url, &requester_ip)
.await;
match evaluation.action {
Action::Allow => {
Expand Down Expand Up @@ -337,7 +341,7 @@ async fn handle_connect_tunnel(
debug!("Sent 200 Connection Established, starting TLS handshake");

// Now perform TLS handshake with the client
perform_tls_interception(stream, rule_engine, cert_manager, host).await
perform_tls_interception(stream, rule_engine, cert_manager, host, remote_addr).await
}
Action::Deny => {
warn!("CONNECT denied to: {}", host);
Expand Down Expand Up @@ -372,6 +376,7 @@ async fn perform_tls_interception(
rule_engine: Arc<RuleEngine>,
cert_manager: Arc<CertificateManager>,
host: &str,
remote_addr: std::net::SocketAddr,
) -> Result<()> {
// Get certificate for the host
let (cert_chain, key) = cert_manager
Expand Down Expand Up @@ -405,9 +410,10 @@ async fn perform_tls_interception(
// Now handle the decrypted HTTPS requests
let io = TokioIo::new(tls_stream);
let host_string = host.to_string();
let remote_addr_copy = remote_addr; // Copy for the closure
let service = service_fn(move |req| {
let host_clone = host_string.clone();
handle_decrypted_https_request(req, Arc::clone(&rule_engine), host_clone)
handle_decrypted_https_request(req, Arc::clone(&rule_engine), host_clone, remote_addr_copy)
});

debug!("Starting HTTP/1.1 server for decrypted requests");
Expand All @@ -425,12 +431,18 @@ async fn handle_plain_http(
stream: TcpStream,
rule_engine: Arc<RuleEngine>,
cert_manager: Arc<CertificateManager>,
remote_addr: std::net::SocketAddr,
) -> Result<()> {
debug!("Handling plain HTTP on HTTPS port");

let io = TokioIo::new(stream);
let service = service_fn(move |req| {
crate::proxy::handle_http_request(req, Arc::clone(&rule_engine), Arc::clone(&cert_manager))
crate::proxy::handle_http_request(
req,
Arc::clone(&rule_engine),
Arc::clone(&cert_manager),
remote_addr,
)
});

http1::Builder::new()
Expand All @@ -447,6 +459,7 @@ async fn handle_decrypted_https_request(
req: Request<Incoming>,
rule_engine: Arc<RuleEngine>,
host: String,
remote_addr: std::net::SocketAddr,
) -> Result<Response<BoxBody<Bytes, HyperError>>, std::convert::Infallible> {
let method = req.method().clone();
let uri = req.uri().clone();
Expand All @@ -455,11 +468,15 @@ async fn handle_decrypted_https_request(
let path = uri.path_and_query().map(|pq| pq.as_str()).unwrap_or("/");
let full_url = format!("https://{}{}", host, path);

debug!("Proxying HTTPS request: {} {}", method, full_url);
debug!(
"Proxying HTTPS request: {} {} from {}",
method, full_url, remote_addr
);

// Evaluate rules with method
// Evaluate rules with method and requester IP
let requester_ip = remote_addr.ip().to_string();
let evaluation = rule_engine
.evaluate_with_context(method.clone(), &full_url)
.evaluate_with_context_and_ip(method.clone(), &full_url, &requester_ip)
.await;
match evaluation.action {
Action::Allow => {
Expand Down Expand Up @@ -671,8 +688,8 @@ mod tests {

// Spawn proxy handler
tokio::spawn(async move {
let (stream, _) = listener.accept().await.unwrap();
let _ = handle_connect_tunnel(stream, rule_engine, cert_manager).await;
let (stream, addr) = listener.accept().await.unwrap();
let _ = handle_connect_tunnel(stream, rule_engine, cert_manager, addr).await;
});

// Connect to proxy
Expand Down Expand Up @@ -706,8 +723,8 @@ mod tests {

// Spawn proxy handler
tokio::spawn(async move {
let (stream, _) = listener.accept().await.unwrap();
let _ = handle_connect_tunnel(stream, rule_engine, cert_manager).await;
let (stream, addr) = listener.accept().await.unwrap();
let _ = handle_connect_tunnel(stream, rule_engine, cert_manager, addr).await;
});

// Connect to proxy
Expand Down Expand Up @@ -743,8 +760,8 @@ mod tests {

// Spawn proxy handler
tokio::spawn(async move {
let (stream, _) = listener.accept().await.unwrap();
let _ = handle_transparent_tls(stream, rule_engine, cert_manager).await;
let (stream, addr) = listener.accept().await.unwrap();
let _ = handle_transparent_tls(stream, rule_engine, cert_manager, addr).await;
});

// Connect to proxy with TLS directly (transparent mode)
Expand Down Expand Up @@ -815,8 +832,8 @@ mod tests {
let cert_manager = cert_manager.clone();
let rule_engine = rule_engine.clone();
tokio::spawn(async move {
let (stream, _) = listener.accept().await.unwrap();
let _ = handle_https_connection(stream, rule_engine, cert_manager).await;
let (stream, addr) = listener.accept().await.unwrap();
let _ = handle_https_connection(stream, rule_engine, cert_manager, addr).await;
});

let mut stream = TcpStream::connect(addr).await.unwrap();
Expand Down Expand Up @@ -848,9 +865,9 @@ mod tests {

// Start proxy handler
tokio::spawn(async move {
let (stream, _) = listener.accept().await.unwrap();
let (stream, addr) = listener.accept().await.unwrap();
// Use the actual transparent TLS handler (which will extract SNI, etc.)
let _ = handle_transparent_tls(stream, rule_engine, cert_manager).await;
let _ = handle_transparent_tls(stream, rule_engine, cert_manager, addr).await;
});

// Give the server time to start
Expand Down
26 changes: 21 additions & 5 deletions src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ impl EvaluationResult {

#[async_trait]
pub trait RuleEngineTrait: Send + Sync {
async fn evaluate(&self, method: Method, url: &str) -> EvaluationResult;
async fn evaluate(&self, method: Method, url: &str, requester_ip: &str) -> EvaluationResult;
Comment on lines 44 to +47

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Update tests for new requester_ip parameter

The trait method RuleEngineTrait::evaluate now requires a requester_ip argument, but the unit tests still invoke engine.evaluate(method, url) with the old two-parameter signature (see src/rules/script.rs and src/rules.rs). Running cargo test will fail to compile until those call sites provide an IP value or the API offers a backwards-compatible wrapper. Consider updating the tests to pass a dummy IP so the suite builds again.

Useful? React with 👍 / 👎.


fn name(&self) -> &str;
}
Expand All @@ -65,8 +65,11 @@ impl LoggingRuleEngine {

#[async_trait]
impl RuleEngineTrait for LoggingRuleEngine {
async fn evaluate(&self, method: Method, url: &str) -> EvaluationResult {
let result = self.engine.evaluate(method.clone(), url).await;
async fn evaluate(&self, method: Method, url: &str, requester_ip: &str) -> EvaluationResult {
let result = self
.engine
.evaluate(method.clone(), url, requester_ip)
.await;

if let Some(log) = &self.request_log
&& let Ok(mut file) = log.lock()
Expand Down Expand Up @@ -110,11 +113,24 @@ impl RuleEngine {
}

pub async fn evaluate(&self, method: Method, url: &str) -> Action {
self.inner.evaluate(method, url).await.action
self.inner.evaluate(method, url, "127.0.0.1").await.action
}

pub async fn evaluate_with_context(&self, method: Method, url: &str) -> EvaluationResult {
self.inner.evaluate(method, url).await
self.inner.evaluate(method, url, "127.0.0.1").await
}

pub async fn evaluate_with_ip(&self, method: Method, url: &str, requester_ip: &str) -> Action {
self.inner.evaluate(method, url, requester_ip).await.action
}

pub async fn evaluate_with_context_and_ip(
&self,
method: Method,
url: &str,
requester_ip: &str,
) -> EvaluationResult {
self.inner.evaluate(method, url, requester_ip).await
}
}

Expand Down
Loading
Loading