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: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,18 @@ webpki-roots = "0.26"
lru = "0.12"
rand = "0.8"
anyhow = "1.0"
base64 = "0.22"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "chrono"] }
chrono = "0.4"
dirs = "6.0.0"
hyper-rustls = "0.27.7"
tower-service = "0.3"
tls-parser = "0.12.2"
camino = "1.1.11"
filetime = "0.2"
ctrlc = "3.4"
percent-encoding = "2.3"
url = "2.5"
v8 = "129"
serde = { version = "1.0", features = ["derive"] }
Expand Down
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Or download a pre-built binary from the [releases page](https://github.com/coder
- 🌐 **HTTP/HTTPS interception** - Transparent proxy with TLS certificate injection
- 🛡️ **DNS exfiltration protection** - Prevents data leakage through DNS queries
- 🔧 **Multiple evaluation approaches** - JS expressions or custom programs
- 🏢 **Upstream proxy support** - Chain httpjail's egress through a corporate proxy
- 🖥️ **Cross-platform** - Native support for Linux and macOS

## Quick Start
Expand Down Expand Up @@ -61,8 +62,28 @@ httpjail --server --js "true"

# Run Docker containers with network isolation (Linux only)
httpjail --js "r.host === 'api.github.com'" --docker-run -- --rm alpine:latest wget -qO- https://api.github.com

# Route httpjail's own egress through an upstream (corporate) proxy
httpjail --upstream-proxy http://proxy.corp:3128 --js "true" -- curl https://api.github.com
# Credentials and HTTPS proxies are supported: http://user:pass@proxy.corp:3128, https://proxy.corp:8443
# May also be set via the HTTPJAIL_UPSTREAM_PROXY environment variable
```

### Upstream (corporate) proxy

When httpjail itself runs in an environment with no direct internet access, use
`--upstream-proxy <URL>` (or the `HTTPJAIL_UPSTREAM_PROXY` environment variable)
to route httpjail's outbound requests through an upstream proxy. Rule evaluation
still happens locally on the intercepted traffic; only the re-originated request
is forwarded through the proxy.

- `http://`, `https://` and bare `host:port` (http assumed) forms are accepted.
- Basic authentication is supported via `http://user:pass@host:port`.
- HTTPS destinations are reached via a `CONNECT` tunnel through the proxy, while
plain HTTP destinations are forwarded in absolute-form.
- This is independent of the `HTTP_PROXY`/`HTTPS_PROXY` variables that httpjail
sets *inside* the jail to point sandboxed processes at itself.

## Documentation

Docs are stored in the `docs/` directory and served
Expand All @@ -82,6 +103,7 @@ Table of Contents:
- [TLS Interception](https://coder.github.io/httpjail/advanced/tls-interception.html)
- [DNS Exfiltration](https://coder.github.io/httpjail/advanced/dns-exfiltration.html)
- [Server Mode](https://coder.github.io/httpjail/advanced/server-mode.html)
- [Upstream Proxy](https://coder.github.io/httpjail/advanced/upstream-proxy.html)

## License

Expand Down
1 change: 1 addition & 0 deletions docs/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
- [TLS Interception](./advanced/tls-interception.md)
- [DNS Exfiltration](./advanced/dns-exfiltration.md)
- [Server Mode](./advanced/server-mode.md)
- [Upstream Proxy](./advanced/upstream-proxy.md)

---

Expand Down
61 changes: 61 additions & 0 deletions docs/advanced/upstream-proxy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Upstream Proxy

By default httpjail contacts destination servers directly. When httpjail itself
runs in an environment that has no direct internet access — for example behind a
corporate proxy — you can route httpjail's own outbound requests through an
upstream proxy with `--upstream-proxy` (or the `HTTPJAIL_UPSTREAM_PROXY`
environment variable).

Rule evaluation still happens locally on the intercepted traffic. Only the
request that httpjail re-originates towards the real destination is forwarded
through the upstream proxy.

```bash
# Route httpjail's egress through a corporate proxy
httpjail --upstream-proxy http://proxy.corp:3128 --js "true" -- curl https://api.github.com

# With Basic authentication
httpjail --upstream-proxy http://user:pass@proxy.corp:3128 --js "true" -- ./my-app

# Through an HTTPS proxy
httpjail --upstream-proxy https://proxy.corp:8443 --js "true" -- ./my-app

# Via the environment variable (equivalent to --upstream-proxy)
HTTPJAIL_UPSTREAM_PROXY=http://proxy.corp:3128 httpjail --js "true" -- ./my-app
```

## Accepted formats

| Form | Example | Notes |
| --- | --- | --- |
| `http://host:port` | `http://proxy.corp:3128` | Plain HTTP proxy |
| `https://host:port` | `https://proxy.corp:8443` | Connection to the proxy is wrapped in TLS |
| `host:port` | `proxy.corp:3128` | Bare authority, `http` scheme assumed |
| With credentials | `http://user:pass@proxy.corp:3128` | Sends `Proxy-Authorization: Basic ...` |

The command-line flag takes precedence over the environment variable. Credentials
are never written to the logs.

## How it works

- **HTTPS destinations** are reached by issuing a `CONNECT` to the upstream
proxy to obtain a raw TCP tunnel; httpjail then performs the destination TLS
handshake over that tunnel. TLS is validated against Mozilla's webpki roots
plus the httpjail CA, exactly as for a direct connection.
- **Plain HTTP destinations** are forwarded to the proxy in absolute-form, with
the `Proxy-Authorization` header attached when credentials are configured.
- Only connection setup (TCP connect, optional TLS to the proxy, and the
`CONNECT` exchange) is bounded by a timeout. The established tunnel carries no
timeout, so long-running connections such as WebSocket and gRPC keep working.

## Relationship to `HTTP_PROXY` / `HTTPS_PROXY`

This feature is independent of the `HTTP_PROXY` and `HTTPS_PROXY` variables that
httpjail sets *inside* the jail to point sandboxed processes at httpjail itself.

```
[ jailed process ] --HTTP_PROXY/HTTPS_PROXY--> [ httpjail ] --upstream-proxy--> [ corporate proxy ] --> internet
```

The jailed process always talks to httpjail; `--upstream-proxy` only affects the
hop from httpjail to the outside world.
13 changes: 9 additions & 4 deletions docs/guide/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,15 @@ These are automatically set in the jailed process:

These affect httpjail's behavior:

| Variable | Description | Example |
| ------------------ | -------------------------- | -------------------------------- |
| `RUST_LOG` | Logging level | `debug`, `info`, `warn`, `error` |
| `HTTPJAIL_CA_CERT` | Custom CA certificate path | `/etc/pki/custom-ca.pem` |
| Variable | Description | Example |
| ------------------------ | -------------------------------------- | -------------------------------- |
| `RUST_LOG` | Logging level | `debug`, `info`, `warn`, `error` |
| `HTTPJAIL_CA_CERT` | Custom CA certificate path | `/etc/pki/custom-ca.pem` |
| `HTTPJAIL_UPSTREAM_PROXY`| Upstream proxy for httpjail's egress | `http://proxy.corp:3128` |

The `--upstream-proxy` command-line flag takes precedence over
`HTTPJAIL_UPSTREAM_PROXY`. See [Upstream Proxy](../advanced/upstream-proxy.md)
for details.

## Platform-Specific Configuration

Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ pub mod proxy_tls;
pub mod rules;
pub mod sys_resource;
pub mod tls;
pub mod upstream;

pub mod test_utils;
58 changes: 56 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use httpjail::rules::shell::ShellRuleEngine;
use httpjail::rules::v8_js::V8JsRuleEngine;
use httpjail::rules::{Action, RuleEngine};
use hyper::Method;
use std::fmt;
use std::fs::OpenOptions;
use std::os::unix::process::ExitStatusExt;
use std::sync::atomic::{AtomicBool, Ordering};
Expand Down Expand Up @@ -40,7 +41,7 @@ enum Command {
},
}

#[derive(Parser, Debug)]
#[derive(Parser)]
struct RunArgs {
/// Use shell script for evaluating requests
/// The script receives environment variables:
Expand Down Expand Up @@ -85,6 +86,13 @@ struct RunArgs {
#[arg(long = "request-log", value_name = "FILE")]
request_log: Option<String>,

/// Route httpjail's own upstream requests through an upstream (corporate) proxy.
/// Accepts http://host:port, https://host:port, or host:port (http assumed),
/// optionally with credentials: http://user:pass@host:port.
/// Falls back to the HTTPJAIL_UPSTREAM_PROXY environment variable.
#[arg(long = "upstream-proxy", value_name = "URL")]
upstream_proxy: Option<String>,

/// Use weak mode (environment variables only, no system isolation)
#[arg(long = "weak")]
weak: bool,
Expand Down Expand Up @@ -139,6 +147,32 @@ struct RunArgs {
exec_command: Vec<String>,
}

impl fmt::Debug for RunArgs {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let upstream_proxy = self
.upstream_proxy
.as_deref()
.map(httpjail::upstream::redact_proxy_spec);
f.debug_struct("RunArgs")
.field("sh", &self.sh)
.field("proc", &self.proc)
.field("js", &self.js)
.field("js_file", &self.js_file)
.field("request_log", &self.request_log)
.field("upstream_proxy", &upstream_proxy)
.field("weak", &self.weak)
.field("verbose", &self.verbose)
.field("timeout", &self.timeout)
.field("no_jail_cleanup", &self.no_jail_cleanup)
.field("cleanup", &self.cleanup)
.field("server", &self.server)
.field("test", &self.test)
.field("docker_run", &self.docker_run)
.field("exec_command", &self.exec_command)
.finish()
}
}

fn setup_logging(verbosity: u8) {
use tracing_subscriber::fmt::time::FormatTime;

Expand Down Expand Up @@ -590,7 +624,27 @@ async fn main() -> Result<()> {
}
};

let mut proxy = ProxyServer::new(http_bind, https_bind, rule_engine);
// Resolve the optional upstream (corporate) proxy from the flag or env var.
// This is independent of the HTTP_PROXY/HTTPS_PROXY variables httpjail sets
// *inside* the jail to point sandboxed processes at itself.
let upstream_proxy_spec = args
.run_args
.upstream_proxy
.clone()
.or_else(|| std::env::var("HTTPJAIL_UPSTREAM_PROXY").ok());
let upstream_proxy = match upstream_proxy_spec {
Some(spec) => {
let proxy = httpjail::upstream::UpstreamProxy::parse(&spec)
.with_context(|| format!("Failed to parse upstream proxy: {}", spec))?;
// Avoid logging the spec verbatim as it may contain credentials.
info!("Routing httpjail upstream requests through the configured upstream proxy");
Some(proxy)
}
None => None,
};

let mut proxy =
ProxyServer::new_with_upstream_proxy(http_bind, https_bind, rule_engine, upstream_proxy);

// Start proxy in background if running as server; otherwise start with random ports
let (actual_http_port, actual_https_port) = proxy.start().await?;
Expand Down
Loading