From ff7265b03d9e2e3dc9d4ef822b99bbc199dcb18a Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Wed, 10 Sep 2025 14:02:22 -0500 Subject: [PATCH 1/3] chore: merge main --- CONTRIBUTING.md | 5 +- README.md | 61 +++++++++++++---------- src/main.rs | 41 ++++++++------- src/proxy.rs | 4 +- src/proxy_tls.rs | 2 +- src/rules.rs | 96 +++++++++++++++++++++++++----------- tests/platform_test_macro.rs | 4 +- tests/system_integration.rs | 42 ++++++++++------ 8 files changed, 157 insertions(+), 98 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d1df5aef..bf5e3e93 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -62,8 +62,9 @@ sudo ./target/release/httpjail --allow "httpbin\.org" -- curl http://httpbin.org # Test with method-specific rules sudo ./target/release/httpjail --allow-get ".*" -- curl -X POST http://httpbin.org/post -# Test log-only mode -sudo ./target/release/httpjail --log-only -- curl http://example.com +# Test request logging +sudo ./target/release/httpjail --request-log requests.log -r "allow: .*" -- curl http://example.com +# Log format: " <+/-> " (+ = allowed, - = blocked) ``` ### Test Organization diff --git a/README.md b/README.md index 56ce603e..3b7aca69 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,9 @@ cargo install httpjail # Allow only requests to github.com httpjail -r "allow: github\.com" -r "deny: .*" -- claude -# Monitor all requests without blocking -httpjail --log-only -- npm install +# Log requests to a file +httpjail --request-log requests.log -r "allow: .*" -- npm install +# Log format: " <+/-> " (+ = allowed, - = blocked) # Block specific domains httpjail -r "deny: telemetry\..*" -r "allow: .*" -- ./my-app @@ -58,7 +59,7 @@ httpjail creates an isolated network environment for the target process, interce │ 1. Create network namespace │ │ 2. Setup nftables rules │ │ 3. Start embedded proxy │ -│ 4. Inject CA certificate │ +│ 4. Export CA trust env vars │ │ 5. Execute target process in namespace │ └─────────────────────────────────────────────────┘ ↓ @@ -66,7 +67,7 @@ httpjail creates an isolated network environment for the target process, interce │ Target Process │ │ • Isolated in network namespace │ │ • All HTTP/HTTPS → local proxy │ -│ • CA cert in trust store │ +│ • CA cert trusted via env vars │ └─────────────────────────────────────────────────┘ ``` @@ -94,12 +95,12 @@ httpjail creates an isolated network environment for the target process, interce ## Platform Support -| Feature | Linux | macOS | Windows | -| ----------------- | ------------------------ | --------------------------- | ------------- | -| Traffic isolation | ✅ Namespaces + nftables | ⚠️ Env vars only | 🚧 Planned | -| TLS interception | ✅ CA injection | ✅ Env variables | 🚧 Cert store | -| Sudo required | ⚠️ Yes | ✅ No | 🚧 | -| Force all traffic | ✅ Yes | ❌ No (apps must cooperate) | 🚧 | +| Feature | Linux | macOS | Windows | +| ----------------- | ---------------------------- | --------------------------- | ------------- | +| Traffic isolation | ✅ Namespaces + nftables | ⚠️ Env vars only | 🚧 Planned | +| TLS interception | ✅ Transparent MITM + env CA | ✅ Env variables | 🚧 Cert store | +| Sudo required | ⚠️ Yes | ✅ No | 🚧 | +| Force all traffic | ✅ Yes | ❌ No (apps must cooperate) | 🚧 | ## Prerequisites @@ -172,30 +173,36 @@ httpjail --dry-run --config rules.txt -- ./app # Verbose logging httpjail -vvv --allow ".*" -- curl https://example.com -# Interactive mode - approve/deny requests in real-time -httpjail --interactive -- ./app ``` ## TLS Interception -httpjail uses a locally-generated Certificate Authority (CA) to intercept HTTPS traffic: +httpjail performs HTTPS interception using a locally-generated Certificate Authority (CA). The tool does not modify your system trust store. Instead, it configures the jailed process to trust the httpjail CA via environment variables. -1. **Automatic CA Generation**: On first run, httpjail generates a unique CA certificate -2. **Persistent CA Storage**: The CA is cached in the user's config directory: +How it works: + +1. **CA generation (first run)**: A unique CA keypair is created and persisted. +2. **Persistent storage** (via `dirs::config_dir()`): - macOS: `~/Library/Application Support/httpjail/` - Linux: `~/.config/httpjail/` - Windows: `%APPDATA%\httpjail\` -3. **Trust Store Injection**: The CA is temporarily added to the system trust store -4. **Certificate Generation**: Dynamic certificate generation for intercepted domains -5. **Cleanup**: CA is removed from trust store after process termination - -### Security Considerations - -- CA private key is stored with 600 permissions (Unix) in the config directory -- CA is only trusted for the duration of the jailed process -- Each httpjail installation has a unique CA -- The same CA is reused across runs for consistency -- Certificates are generated on-the-fly and not persisted + Files: `ca-cert.pem`, `ca-key.pem` (key is chmod 600 on Unix). +3. **Per‑process trust via env vars**: For the jailed command, httpjail sets common variables so clients trust the CA without touching system stores: + - `SSL_CERT_FILE` and `SSL_CERT_DIR` + - `CURL_CA_BUNDLE` + - `GIT_SSL_CAINFO` + - `REQUESTS_CA_BUNDLE` + - `NODE_EXTRA_CA_CERTS` + These apply on both Linux (strong/transparent mode) and macOS (`--weak` env‑only mode). +4. **Transparent MITM**: + - Linux strong mode redirects TCP 80/443 to the local proxy. HTTPS is intercepted transparently by extracting SNI from ClientHello and presenting a per‑host certificate signed by the httpjail CA. + - macOS uses explicit proxying via `HTTP_PROXY`/`HTTPS_PROXY` and typically negotiates HTTPS via CONNECT; interception occurs after CONNECT. +5. **No system trust changes**: httpjail never installs the CA into OS trust stores; there is no global modification and thus no trust cleanup step. The CA files remain in the config dir for reuse across runs. + +Notes and limits: + +- Tools that ignore the above env vars will fail TLS verification when intercepted. For those, either add tool‑specific flags to point at `ca-cert.pem` or run with `--no-tls-intercept`. +- Long‑lived connections are supported: timeouts are applied only to protocol detection, CONNECT header reads, and TLS handshakes — not to proxied streams (e.g., gRPC/WebSocket). ### Disable TLS Interception @@ -215,7 +222,7 @@ OPTIONS: Methods: get, post, put, delete, head, options, connect, trace, patch -c, --config Use configuration file --dry-run Log actions without blocking - --log-only Monitor without filtering + --request-log Append requests to log file (+ for allowed, - for blocked) --no-tls-intercept Disable HTTPS interception --interactive Interactive approval mode --weak Use weak mode (env vars only, no system isolation) diff --git a/src/main.rs b/src/main.rs index 831660b2..0fa825c9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,9 +3,10 @@ use clap::Parser; use httpjail::jail::{JailConfig, create_jail}; use httpjail::proxy::ProxyServer; use httpjail::rules::{Action, Rule, RuleEngine}; +use std::fs::OpenOptions; use std::os::unix::process::ExitStatusExt; -use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex}; use tracing::{debug, info, warn}; #[derive(Parser, Debug)] @@ -32,9 +33,9 @@ struct Args { #[arg(long = "dry-run")] dry_run: bool, - /// Monitor without filtering - #[arg(long = "log-only")] - log_only: bool, + /// Append requests to a log file + #[arg(long = "request-log", value_name = "FILE")] + request_log: Option, /// Disable HTTPS interception #[arg(long = "no-tls-intercept")] @@ -308,7 +309,18 @@ async fn main() -> Result<()> { // Build rules from command line arguments let rules = build_rules(&args)?; - let rule_engine = RuleEngine::new(rules, args.dry_run, args.log_only); + let request_log = if let Some(path) = &args.request_log { + Some(Arc::new(Mutex::new( + OpenOptions::new() + .create(true) + .append(true) + .open(path) + .context("Failed to open request log file")?, + ))) + } else { + None + }; + let rule_engine = RuleEngine::new(rules, args.dry_run, request_log); // Get ports from env vars (optional) let http_port = std::env::var("HTTPJAIL_HTTP_BIND") @@ -468,7 +480,7 @@ mod tests { Rule::new(Action::Deny, r".*").unwrap(), ]; - let engine = RuleEngine::new(rules, false, false); + let engine = RuleEngine::new(rules, false, None); // Test allow rule assert!(matches!( @@ -493,7 +505,7 @@ mod tests { fn test_dry_run_mode() { let rules = vec![Rule::new(Action::Deny, r".*").unwrap()]; - let engine = RuleEngine::new(rules, true, false); + let engine = RuleEngine::new(rules, true, None); // In dry-run mode, everything should be allowed assert!(matches!( @@ -502,19 +514,6 @@ mod tests { )); } - #[test] - fn test_log_only_mode() { - let rules = vec![Rule::new(Action::Deny, r".*").unwrap()]; - - let engine = RuleEngine::new(rules, false, true); - - // In log-only mode, everything should be allowed - assert!(matches!( - engine.evaluate(Method::POST, "https://example.com"), - Action::Allow - )); - } - #[test] fn test_build_rules_from_config_file() { use std::io::Write; @@ -531,7 +530,7 @@ mod tests { rules: vec![], config: Some(file.path().to_str().unwrap().to_string()), dry_run: false, - log_only: false, + request_log: None, no_tls_intercept: false, interactive: false, weak: false, diff --git a/src/proxy.rs b/src/proxy.rs index 01183ee0..ebfcb316 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -458,7 +458,7 @@ mod tests { Rule::new(Action::Deny, r".*").unwrap(), ]; - let rule_engine = RuleEngine::new(rules, false, false); + let rule_engine = RuleEngine::new(rules, false, None); let proxy = ProxyServer::new(Some(8080), Some(8443), rule_engine, None); assert_eq!(proxy.http_port, Some(8080)); @@ -469,7 +469,7 @@ mod tests { async fn test_proxy_server_auto_port() { let rules = vec![Rule::new(Action::Allow, r".*").unwrap()]; - let rule_engine = RuleEngine::new(rules, false, false); + let rule_engine = RuleEngine::new(rules, false, None); let mut proxy = ProxyServer::new(None, None, rule_engine, None); let (http_port, https_port) = proxy.start().await.unwrap(); diff --git a/src/proxy_tls.rs b/src/proxy_tls.rs index 928a29ed..06b6d258 100644 --- a/src/proxy_tls.rs +++ b/src/proxy_tls.rs @@ -593,7 +593,7 @@ mod tests { Rule::new(Action::Deny, r".*").unwrap(), ] }; - Arc::new(RuleEngine::new(rules, false, false)) + Arc::new(RuleEngine::new(rules, false, None)) } /// Create a TLS client config that trusts any certificate (for testing) diff --git a/src/rules.rs b/src/rules.rs index 24641650..8b1e9593 100644 --- a/src/rules.rs +++ b/src/rules.rs @@ -1,7 +1,11 @@ use anyhow::Result; +use chrono::{SecondsFormat, Utc}; use hyper::Method; use regex::Regex; use std::collections::HashSet; +use std::fs::File; +use std::io::Write; +use std::sync::{Arc, Mutex}; use tracing::{info, warn}; #[derive(Debug, Clone)] @@ -49,26 +53,25 @@ impl Rule { pub struct RuleEngine { pub rules: Vec, pub dry_run: bool, - pub log_only: bool, + pub request_log: Option>>, } impl RuleEngine { - pub fn new(rules: Vec, dry_run: bool, log_only: bool) -> Self { + pub fn new(rules: Vec, dry_run: bool, request_log: Option>>) -> Self { RuleEngine { rules, dry_run, - log_only, + request_log, } } pub fn evaluate(&self, method: Method, url: &str) -> Action { - if self.log_only { - info!("Request: {} {}", method, url); - return Action::Allow; - } + let mut action = Action::Deny; + let mut matched = false; for rule in &self.rules { if rule.matches(method.clone(), url) { + matched = true; match &rule.action { Action::Allow => { info!( @@ -77,9 +80,7 @@ impl RuleEngine { url, rule.pattern.as_str() ); - if !self.dry_run { - return Action::Allow; - } + action = Action::Allow; } Action::Deny => { warn!( @@ -88,27 +89,41 @@ impl RuleEngine { url, rule.pattern.as_str() ); - if !self.dry_run { - return Action::Deny; - } + action = Action::Deny; } } + if !self.dry_run { + break; + } } } - // Default deny if no rules match - warn!("DENY: {} {} (no matching rules)", method, url); - if self.dry_run { - Action::Allow - } else { - Action::Deny + if !matched { + warn!("DENY: {} {} (no matching rules)", method, url); + action = Action::Deny; } + + if let Some(log) = &self.request_log + && let Ok(mut file) = log.lock() + { + let timestamp = Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true); + let status = match &action { + Action::Allow => '+', + Action::Deny => '-', + }; + if let Err(e) = writeln!(file, "{} {} {} {}", timestamp, status, method, url) { + warn!("Failed to write to request log: {}", e); + } + } + + if self.dry_run { Action::Allow } else { action } } } #[cfg(test)] mod tests { use super::*; + use std::sync::{Arc, Mutex}; #[test] fn test_rule_matching() { @@ -138,7 +153,7 @@ mod tests { Rule::new(Action::Deny, r".*").unwrap(), ]; - let engine = RuleEngine::new(rules, false, false); + let engine = RuleEngine::new(rules, false, None); // Test allow rule assert!(matches!( @@ -168,7 +183,7 @@ mod tests { Rule::new(Action::Deny, r".*").unwrap(), ]; - let engine = RuleEngine::new(rules, false, false); + let engine = RuleEngine::new(rules, false, None); // GET should be allowed assert!(matches!( @@ -187,7 +202,7 @@ mod tests { fn test_dry_run_mode() { let rules = vec![Rule::new(Action::Deny, r".*").unwrap()]; - let engine = RuleEngine::new(rules, true, false); + let engine = RuleEngine::new(rules, true, None); // In dry-run mode, everything should be allowed assert!(matches!( @@ -197,15 +212,38 @@ mod tests { } #[test] - fn test_log_only_mode() { + fn test_request_logging() { + use std::fs::OpenOptions; + + let rules = vec![Rule::new(Action::Allow, r".*").unwrap()]; + let log_file = tempfile::NamedTempFile::new().unwrap(); + let file = OpenOptions::new() + .append(true) + .open(log_file.path()) + .unwrap(); + let engine = RuleEngine::new(rules, false, Some(Arc::new(Mutex::new(file)))); + + engine.evaluate(Method::GET, "https://example.com"); + + let contents = std::fs::read_to_string(log_file.path()).unwrap(); + assert!(contents.contains("+ GET https://example.com")); + } + + #[test] + fn test_request_logging_denied() { + use std::fs::OpenOptions; + let rules = vec![Rule::new(Action::Deny, r".*").unwrap()]; + let log_file = tempfile::NamedTempFile::new().unwrap(); + let file = OpenOptions::new() + .append(true) + .open(log_file.path()) + .unwrap(); + let engine = RuleEngine::new(rules, false, Some(Arc::new(Mutex::new(file)))); - let engine = RuleEngine::new(rules, false, true); + engine.evaluate(Method::GET, "https://blocked.com"); - // In log-only mode, everything should be allowed - assert!(matches!( - engine.evaluate(Method::POST, "https://example.com"), - Action::Allow - )); + let contents = std::fs::read_to_string(log_file.path()).unwrap(); + assert!(contents.contains("- GET https://blocked.com")); } } diff --git a/tests/platform_test_macro.rs b/tests/platform_test_macro.rs index 1500c897..5fd16849 100644 --- a/tests/platform_test_macro.rs +++ b/tests/platform_test_macro.rs @@ -28,8 +28,8 @@ macro_rules! platform_tests { #[test] #[::serial_test::serial] - fn test_jail_log_only_mode() { - system_integration::test_jail_log_only_mode::<$platform>(); + fn test_jail_request_log() { + system_integration::test_jail_request_log::<$platform>(); } #[test] diff --git a/tests/system_integration.rs b/tests/system_integration.rs index 6ec9bee4..9e3e0c2d 100644 --- a/tests/system_integration.rs +++ b/tests/system_integration.rs @@ -195,12 +195,19 @@ pub fn test_jail_method_specific_rules() { assert_eq!(stdout.trim(), "403", "POST request should be denied"); } -/// Test log-only mode -pub fn test_jail_log_only_mode() { +/// Test request logging +pub fn test_jail_request_log() { P::require_privileges(); + let log_file = tempfile::NamedTempFile::new().expect("Failed to create temp file"); + let log_path = log_file.path().to_str().unwrap().to_string(); + let mut cmd = httpjail_cmd(); - cmd.arg("--log-only").arg("--"); + cmd.arg("--request-log") + .arg(&log_path) + .arg("-r") + .arg("allow: .*") + .arg("--"); curl_http_status_args(&mut cmd, "http://example.com"); let output = cmd.output().expect("Failed to execute httpjail"); @@ -210,18 +217,25 @@ pub fn test_jail_log_only_mode() { if !stderr.is_empty() { eprintln!("[{}] stderr: {}", P::platform_name(), stderr); } - eprintln!("[{}] stdout: {}", P::platform_name(), stdout); - // In log-only mode, all requests should be allowed - // Due to proxy issues, we might get partial responses or timeouts - // Just verify that the request wasn't explicitly blocked (403) - assert!( - !stdout.contains("403") && !stderr.contains("403") && !stdout.contains("Request blocked"), - "Request should not be blocked in log-only mode. Got stdout: '{}', stderr: '{}', exit code: {:?}", - stdout.trim(), - stderr.trim(), - output.status.code() - ); + assert_eq!(stdout.trim(), "200", "GET request should be allowed"); + + // Run a denied request to ensure '-' is logged + let mut cmd = httpjail_cmd(); + cmd.arg("--request-log") + .arg(&log_path) + .arg("-r") + .arg("deny: .*") + .arg("--"); + curl_http_status_args(&mut cmd, "http://example.com"); + + let output = cmd.output().expect("Failed to execute httpjail"); + let stdout = String::from_utf8_lossy(&output.stdout); + assert_eq!(stdout.trim(), "403", "GET request should be denied"); + + let contents = std::fs::read_to_string(log_file.path()).expect("Failed to read request log"); + assert!(contents.contains("+ GET http://example.com")); + assert!(contents.contains("- GET http://example.com")); } /// Test dry-run mode From df72f635c52cdfbd7f09d0073bfcf1ec56002630 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Wed, 10 Sep 2025 14:21:00 -0500 Subject: [PATCH 2/3] Merge main into work --- README.md | 39 --------------------------------------- 1 file changed, 39 deletions(-) diff --git a/README.md b/README.md index 3b7aca69..04c5bc6c 100644 --- a/README.md +++ b/README.md @@ -211,45 +211,6 @@ Notes and limits: httpjail --no-tls-intercept --allow ".*" -- ./app ``` -## Command-Line Options - -``` -httpjail [OPTIONS] -- [ARGS] - -OPTIONS: - -r, --rule Add a rule (format: "action[-method]: pattern") - Actions: allow, deny - Methods: get, post, put, delete, head, options, connect, trace, patch - -c, --config Use configuration file - --dry-run Log actions without blocking - --request-log Append requests to log file (+ for allowed, - for blocked) - --no-tls-intercept Disable HTTPS interception - --interactive Interactive approval mode - --weak Use weak mode (env vars only, no system isolation) - --timeout Timeout for command execution - -v, --verbose Increase verbosity (-vvv for max) - -h, --help Print help - -V, --version Print version with commit hash - -RULE FORMAT: - Rules are specified with -r/--rule and use the format: - "action[-method]: pattern" - - Examples: - -r "allow: github\.com" # Allow all methods to github.com - -r "allow-get: api\..*" # Allow only GET requests to api.* - -r "deny-post: telemetry\..*" # Deny POST requests to telemetry.* - -r "deny: .*" # Deny everything (usually last rule) - - Rules are evaluated in the order specified. - -EXAMPLES: - httpjail -r "allow: github\.com" -r "deny: .*" -- git clone https://github.com/user/repo - httpjail --config rules.txt -- npm install - httpjail --dry-run -r "deny: telemetry" -r "allow: .*" -- ./application - httpjail --weak -r "allow: .*" -- npm test # Use environment variables only -``` - ## License This project is released into the public domain under the CC0 1.0 Universal license. See [LICENSE](LICENSE) for details. From 426e142ca62a35ed7f41fabf8652165330871ba6 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Wed, 10 Sep 2025 14:39:17 -0500 Subject: [PATCH 3/3] Remove dry-run flag that was incorrectly re-added during merge The dry-run flag was intentionally removed in a prior commit but was accidentally re-introduced during the merge conflict resolution. This commit removes it again, keeping only the request-log feature. --- src/main.rs | 22 ++-------------------- src/proxy.rs | 4 ++-- src/proxy_tls.rs | 2 +- src/rules.rs | 36 ++++++++---------------------------- 4 files changed, 13 insertions(+), 51 deletions(-) diff --git a/src/main.rs b/src/main.rs index b41597f7..12db7d39 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,10 +29,6 @@ struct Args { #[arg(short = 'c', long = "config", value_name = "FILE")] config: Option, - /// Log actions without blocking - #[arg(long = "dry-run")] - dry_run: bool, - /// Append requests to a log file #[arg(long = "request-log", value_name = "FILE")] request_log: Option, @@ -329,7 +325,7 @@ async fn main() -> Result<()> { } else { None }; - let rule_engine = RuleEngine::new(rules, args.dry_run, request_log); + let rule_engine = RuleEngine::new(rules, request_log); // Parse bind configuration from env vars // Supports both "port" and "ip:port" formats @@ -543,7 +539,7 @@ mod tests { Rule::new(Action::Deny, r".*").unwrap(), ]; - let engine = RuleEngine::new(rules, false, None); + let engine = RuleEngine::new(rules, None); // Test allow rule assert!(matches!( @@ -564,19 +560,6 @@ mod tests { )); } - #[test] - fn test_dry_run_mode() { - let rules = vec![Rule::new(Action::Deny, r".*").unwrap()]; - - let engine = RuleEngine::new(rules, true, None); - - // In dry-run mode, everything should be allowed - assert!(matches!( - engine.evaluate(Method::GET, "https://example.com"), - Action::Allow - )); - } - #[test] fn test_build_rules_from_config_file() { use std::io::Write; @@ -592,7 +575,6 @@ mod tests { let args = Args { rules: vec![], config: Some(file.path().to_str().unwrap().to_string()), - dry_run: false, request_log: None, interactive: false, weak: false, diff --git a/src/proxy.rs b/src/proxy.rs index 70797213..27b0d5fe 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -462,7 +462,7 @@ mod tests { Rule::new(Action::Deny, r".*").unwrap(), ]; - let rule_engine = RuleEngine::new(rules, false, None); + let rule_engine = RuleEngine::new(rules, None); let proxy = ProxyServer::new(Some(8080), Some(8443), rule_engine, None); assert_eq!(proxy.http_port, Some(8080)); @@ -473,7 +473,7 @@ mod tests { async fn test_proxy_server_auto_port() { let rules = vec![Rule::new(Action::Allow, r".*").unwrap()]; - let rule_engine = RuleEngine::new(rules, false, None); + let rule_engine = RuleEngine::new(rules, None); let mut proxy = ProxyServer::new(None, None, rule_engine, None); let (http_port, https_port) = proxy.start().await.unwrap(); diff --git a/src/proxy_tls.rs b/src/proxy_tls.rs index 06b6d258..90cb1380 100644 --- a/src/proxy_tls.rs +++ b/src/proxy_tls.rs @@ -593,7 +593,7 @@ mod tests { Rule::new(Action::Deny, r".*").unwrap(), ] }; - Arc::new(RuleEngine::new(rules, false, None)) + Arc::new(RuleEngine::new(rules, None)) } /// Create a TLS client config that trusts any certificate (for testing) diff --git a/src/rules.rs b/src/rules.rs index 8b1e9593..629cb559 100644 --- a/src/rules.rs +++ b/src/rules.rs @@ -52,17 +52,12 @@ impl Rule { #[derive(Clone)] pub struct RuleEngine { pub rules: Vec, - pub dry_run: bool, pub request_log: Option>>, } impl RuleEngine { - pub fn new(rules: Vec, dry_run: bool, request_log: Option>>) -> Self { - RuleEngine { - rules, - dry_run, - request_log, - } + pub fn new(rules: Vec, request_log: Option>>) -> Self { + RuleEngine { rules, request_log } } pub fn evaluate(&self, method: Method, url: &str) -> Action { @@ -92,9 +87,7 @@ impl RuleEngine { action = Action::Deny; } } - if !self.dry_run { - break; - } + break; } } @@ -116,7 +109,7 @@ impl RuleEngine { } } - if self.dry_run { Action::Allow } else { action } + action } } @@ -153,7 +146,7 @@ mod tests { Rule::new(Action::Deny, r".*").unwrap(), ]; - let engine = RuleEngine::new(rules, false, None); + let engine = RuleEngine::new(rules, None); // Test allow rule assert!(matches!( @@ -183,7 +176,7 @@ mod tests { Rule::new(Action::Deny, r".*").unwrap(), ]; - let engine = RuleEngine::new(rules, false, None); + let engine = RuleEngine::new(rules, None); // GET should be allowed assert!(matches!( @@ -198,19 +191,6 @@ mod tests { )); } - #[test] - fn test_dry_run_mode() { - let rules = vec![Rule::new(Action::Deny, r".*").unwrap()]; - - let engine = RuleEngine::new(rules, true, None); - - // In dry-run mode, everything should be allowed - assert!(matches!( - engine.evaluate(Method::GET, "https://example.com"), - Action::Allow - )); - } - #[test] fn test_request_logging() { use std::fs::OpenOptions; @@ -221,7 +201,7 @@ mod tests { .append(true) .open(log_file.path()) .unwrap(); - let engine = RuleEngine::new(rules, false, Some(Arc::new(Mutex::new(file)))); + let engine = RuleEngine::new(rules, Some(Arc::new(Mutex::new(file)))); engine.evaluate(Method::GET, "https://example.com"); @@ -239,7 +219,7 @@ mod tests { .append(true) .open(log_file.path()) .unwrap(); - let engine = RuleEngine::new(rules, false, Some(Arc::new(Mutex::new(file)))); + let engine = RuleEngine::new(rules, Some(Arc::new(Mutex::new(file)))); engine.evaluate(Method::GET, "https://blocked.com");