Introduction
proxysaur is a network application debugging proxy. Currently it supports TCP, HTTP and HTTP(s). There are plans to extend it to other protocols, such as Postgres and Redis, but these aren't implemented yet.
You can use proxysaur to observe, record, replay, and rewrite network requests in real time. This is useful when you're doing web application development and need to see what requests are being performed.
Design Goals
Proxysaur is designed to be extensible with WebAssembly. All of the core logic is written in Rust, but request and response handling are completely delegated to compiled WASI modules.
Proxysaur is designed to be shareable. Once you have a proxy configuration which works, you can share the configuration and any compiled WASM modules with your teammates. You could use this, for example, to provide separate configurations for frontend and backend developers on your team. You could also use proxysaur as a mock network layer in a test suite.
Design Anti-Goals
Proxysaur is not meant to be used in production, at least not in its current form.
Proxysaur is configurable through a CLI interface and a configuration file, and is not meant to be a full-fledged UI application like Fiddler or Charles Proxy. That doesn't mean that one can't be built on top of Proxysaur.
Releases
Proxysaur is currently in the pre-alpha phase of development. The API, CLI, and configuration interfaces could change at anytime.
Getting Help
If you have questions or feedback, the best way to get help is to open an issue.
Before Getting Started
Before you get started, you'll need to install openssl
. Currently, proxysaur
is known to work with the latest 1.1.1 release.
The following sections provide more details on getting up and running:
- installation goes over how to install proxysaur on different OSes
- configuration documents how to configure proxysaur
Installation
Releases
There are prebuilt releases for macOS and Linux available on the releases page.
Keep in mind you will need a working installation of OpenSSL 1.1.1 which can be installed via homebrew or on Debian-based Linux distributions with apt-get install
.
Building from source
Alternatively, you can build proxysaur from source.
You'll need to have Rust installed, and then you can run:
$ git clone https://github.com/pmalmgren/proxysaur.git
$ cd proxysaur
$ cargo build --release
$ cp target/release/proxysaur /usr/local/bin # or somewhere in your PATH
Configuration
Proxysaur uses a toml configuration file to store configuration settings.
ca_path
stores a reference to the root certificate authority, which is needed if you want proxysaur to proxy and terminate TLS connections.
Proxysaur can have one or more proxy configurations. These tell the server which ports to listen on, what application protocol to expect on those connections, and how to handle those connections.
Initialization
To generate a configuration file, use the command:
$ proxysaur init [optional-path]
It will create a proxysaur.toml
file in the current directory. Alternatively, you can pass in a path.
You can then launch the proxy server with the command:
$ proxysaur
Or, if you specified a configuration file path:
$ proxysaur --config-path /path/to/config
Certificate Authority Configuration
Proxysaur comes with a root certificate authority generator. This is necessary if you want to configure it as a man-in-the-middle forwarding proxy for HTTPS.
$ proxysaur generate-ca
By default, this will place the certificate authority in the XDG data directory on Linux, and the application support directory on macOS.
Proxy Configuration
To add a proxy configuration, use the subcommand add-proxy
. This example will create a proxy which will listen on port 9000 and forward the requests to www.google.com:
$ proxysaur add-proxy
Enter host: localhost
Enter port: 9000
Enter protocol [http|httpforward|tcp]: http
Use tls [true/false]: false
Upstream address: www.google.com
Upstream port: 443
Use custom wasi [true/false]? false
Enter host/Enter port
This prompt is the address and port that proxysaur will bind to when listening for incoming connections.
Enter protocols
Currently proxysaur supports three different protocols:
http
, a reverse proxy protocol which simply forwards along requests to an upstream serverhttpforward
, which allows proxysaur to be configured as a system-wide http proxytcp
, a reverse tcp proxy which forwards along tcp requests to an upstream server
Use tls
If true
, the server will terminate connections using TLS and the configured certificate authority.
Upstream address/port
Where to forward the requests.
These configuration settings aren't applicable to the httpforward
protocol.
Use custom wasi
If set to true
, you will be asked to enter the path to a custom WASI module that handles requests for the protocol you specified.
HTTP Proxying
Proxysaur supports two modes for proxying HTTP requests: HTTP forwarding mode, and reverse proxy mode.
HTTP forwarding mode allows proxysaur to be used as a system-wide proxy.
Reverse proxy mode allows proxysaur to terminate HTTP(S) traffic for a single host.
HTTP(S) Forward Proxy Configuration
Proxysaur ships with a forward proxy WASI module which is highly configurable, and allows the following things:
- Request rewriting and redirecting
- Serving responses directly from the filesystem
- Response rewriting and recording
Unless specified otherwise, this module is activated by default when the httpforward
protocol is specified.
The remaining documentation will cover how to use httpforward
mode to observe and manipulate HTTP traffic.
Quick start
To support getting started quickly with HTTP debugging, proxysaur
comes with a command to launch a debugging proxy:
$ proxysaur http
Using configuration file: "/home/me/.config/proxysaur/proxysaur.toml"
Using existing CA dir: "/home/me/.local/share/proxysaur"
Root Certificate: "/home/me/.local/share/proxysaur/myca.crt"
To trust this certificate, run:
sudo cp "/home/me/.local/share/proxysaur/myca.crt" /usr/local/share/ca-certificates/extra
sudo update-ca-certificates
To use in a browser, read more here: https://proxysaur.us/ca#trusting-the-root-certificate-in-your-browser
Proxy configuration path: "/home/me/.config/proxysaur/config.yml"
Visit this URL to make sure everything is working: https://proxysaur.us/test
Proxy HttpForward listening on address: http://localhost:9999
This will initialize a root certificate authority, initialize a configuration file, and start the HTTP proxy with a default configuration.
Copy and paste the steps to trust the root certificate, and then run the command:
$ curl -x "http://localhost:9999" "https://proxysaur.us/test"
<!DOCTYPE html>
<html>
<body>
<h1>Proxysaur has eaten your website.</h1>
<p>If this works, it means your proxy is correctly configured. Congratulations!</p>
<p>To get started, head over to the <a href="/http_configuration.html#configure-the-forward-proxy">configuration docs page.</a></p>
</body>
</html>
Go ahead and edit the file defined in the "proxy configuration path," in this case "/home/me/.config/proxysaur/config.yml" to look like this:
hosts:
google.com:
scheme: https
response_rewrites:
- when:
- path:
exact: /test
rewrite:
replace_with: |
<!DOCTYPE html>
<html>
<body>
<h1>Proxysaur has eaten your website.</h1>
</body>
</html>
proxysaur.us:
scheme: https
response_rewrites:
- when:
- path:
exact: /test
rewrite:
replace_with: |
<!DOCTYPE html>
<html>
<body>
<h1>Proxysaur has eaten your website.</h1>
<p>If this works, it means your proxy is correctly configured. Congratulations!</p>
<p>To get started, head over to the <a href="/http_configuration.html#configure-the-forward-proxy">configuration docs page.</a></p>
</body>
</html>
You should see the proxy live reload. Afterwards, run the command curl -x "http://localhost:9999" "https://proxysaur.us/test"
to verify that your changes were picked up.
Next Steps
Certificate Authority Setup
Before we're able to intercept HTTPS requests, we need a certificate authority which is trusted by the system making the requests. When HTTP requests come in, proxysaur uses the certificate authority to generate a certificate signing request (or CSR) and generate certificates for the domain. And because the certificates are signed by a trusted certificate authority, browsers and other HTTPS clients will see them as valid without displaying warnings.
Security Concerns
Because the risk of an attacker getting ahold of the root certificate poses a grave security threat, proxysaur-generated root certificate authorities expire after 1 day.
In the future, using something like Linux keyrings or Apple's Secure Enclave may be implemented so that keys aren't stored in plaintext on disk.
Generating a Certificate Authority
Proxysaur has a built in subcommand called generate-ca
. It uses openssl to generate a key and certificate file. If you don't give a path to the subcommand, it will place them in the platform-specific data directory, for example in $XDG_DATA_DIRS
on Linux.
It will output the location of the CA to stdout
, and if you run it multiple times it won't overwrite anything in the existing directory. If you pass in -f
it will clear out all of the certificates.
$ proxysaur generate-ca
/home/me/.local/share/proxysaur
$ ls -lah /home/me/.local/share/proxysaur
total 28K
drwxrwxr-x 2 me me 4.0K Apr 28 11:29 .
drwxrwxr-x 45 me me 4.0K Apr 27 08:53 ..
-rw-rw-r-- 1 me me 204 Apr 28 11:29 config
-rwxrwxrwx 1 me me 326 Apr 28 11:29 generateca.sh
-rw-rw-r-- 1 me me 1.3K Apr 28 11:29 myca.crt
-rw------- 1 me me 1.7K Apr 28 11:29 myca.key
-rw-rw-r-- 1 me me 1.3K Apr 28 11:29 myca.pem
Trusting the Root Certificate in your Browser
Firefox
- Go to
about:preferences
in the URL bar - Search for "certificates" and click on "View Certificates"
- Click on "Authorities" and then "Import"
- Select the root CA
Chrome
- Go to
chrome://settings/certificates
in the URL bar - Click on "Authorities" and then "Import"
- Select the root CA
Trusting the Root Certificate on your OS
Linux
On Linux, you can manually trust the root certificate by copying it to your /usr/local/share/ca-certificates/extra
directory:
$ CA_LOC=$(proxysaur generate-ca)
$ sudo cp $CA_LOC/myca.crt /usr/local/share/ca-certificates/extra
$ sudo update-ca-certificates
macOS
On macOS, you can either trust the root CA by double-clicking it and adding it to your login keychain. You'll have to select "Always Trust" after adding it to the keychain.
You can also try with the following command:
$ CA_LOC=$(proxysaur generate-ca)
$ security add-trusted-cert -d -r trustRoot -k $HOME/Library/Keychains/login.keychain $CA_LOC/myca.crt
iOS
You'll have to send the certificate to yourself via AirDrop, and then follow along with the Apple Support docs.
Configuration
If you haven't already, run proxysaur init
to create a new proxysaur.toml
file in the current directory.
Make sure that your certificate authority is initialized and created.
Add a proxy with HTTP forward protocol
To setup a forwarding HTTP proxy, use the proxysaur command add-proxy
:
$ proxysaur add-proxy
Enter host: localhost
Enter port: 9000
Enter protocol [http|httpforward|tcp]: httpforward
Use tls [true/false]: false
Use custom wasi [true/false]? false
Enter proxy configuration path: proxy.yaml
This will generate the following configuration in proxysaur.toml
:
[[proxy]]
port = 9000
protocol = 'httpforward'
tls = false
address = 'localhost'
upstream_address = ''
upstream_port = 9999
To check that this works, start proxysaur and then run the following command:
$ curl -x "http://localhost:9000" "http://neverssl.com"
...
You should see requests and responses in the proxysaur logs:
$ proxysaur
INFO protocols::http::proxy: Received request req=Request { ... }
INFO protocols::http::proxy: New request. new_request=Request {...}
INFO protocols::http::proxy: New response. new_response=Response { ...}
INFO protocols::http::proxy: Finished tunneling.
INFO protocols::http::proxy: HTTP proxy result.
Configure the forward proxy
To intercept and rewrite requests, we need to add a proxy configuration.
Create a file called proxy.yaml
and add the following:
hosts:
google.com:
scheme: https
response_rewrites:
- when:
- path:
exact: /
rewrite:
replace_with: |
<!DOCTYPE html>
<html>
<body>
<h1>Proxysaur has eaten your website.</h1>
</body>
</html>
Now start up proxysaur, and in a separate terminal run:
$ curl -x "http://localhost:9000" "https://google.com/"
<!DOCTYPE html>
<html>
<body>
<h1>Proxysaur has eaten your website.</h1>
</body>
</html>%
Configuration format
The configuration file has a top-level array of hosts, each of which has its own configuration block with the following properties:
scheme
- can either behttp
orhttps
response_rewrites
- an array of rewrites which can change the HTTP response, covered in HTTP response rewritingrequest_rewrites
- an array of rewrites which can change the HTTP request, covered in HTTP request rewritingredirect
- a rule which can redirect requests to different hosts, covered in HTTP request redirection
Each rewrite or redirect has a when
clause, which can match on either a header or the path. The match value for the path can be an exact match, a contains match which will match when the provided substring is found in the path, or a regular expression.
Exact match example
hosts:
html.duckduckgo.com:
scheme: https
request_rewrites:
- when:
- path:
exact: /path
rewrite:
match:
header_name:
exact: origin
header_value:
contains: ""
new_header_name: $0
new_header_value: "duckduckgo.com"
Contains match example
hosts:
html.duckduckgo.com:
scheme: https
request_rewrites:
- when:
- path:
contains: /api/v1
rewrite:
match:
header_name:
exact: origin
header_value:
contains: ""
new_header_name: $0
new_header_value: "duckduckgo.com"
Regular expression match example
hosts:
html.duckduckgo.com:
scheme: https
request_rewrites:
- when:
- path:
regex: /api/v1/(.*)/books
rewrite:
match:
header_name:
exact: origin
header_value:
contains: ""
new_header_name: $0
new_header_value: "duckduckgo.com"
Observing HTTP Requests
For now, HTTP requests can be observed in the CLI output.
In the future, an open telemetry collector might be added, with the option to view the output either in a tool like Jaeger or in a separate web UI.
Redirecting HTTP Requests
HTTP requests can be redirected to another URL with the following configuration block:
hosts:
google.com:
scheme: https
redirect:
to:
url:
url: https://duckduckgo.com/about
replace_path_and_query: false
when:
- path:
exact: /about
The parameter replace_path_and_query
will overwrite the new redirected URL. For example:
hosts:
google.com:
scheme: https
redirect:
to:
url:
url: https://proxysaur.us/
replace_path_and_query: true
when:
- path:
regex: /.*
In the above configuration block, every request directed to google.com will be redirected to proxysaur.us, with the path component being completely overwritten.
Redirecting file requests
Note: This currently doesn't work, but is valid configuration
You can also use proxysaur to serve static files with the file redirect block:
hosts:
test2.com:
scheme: https
redirect:
to:
file:
path: /usr/local/www
root_index: true
replace_path: true
file_suffix: .html
content_type: text/html; charset=UTF-8
This will redirect requests from https://test2.com/
to the local file system. Requests to /
will be served from /usr/local/www/index.html
etc. This can be useful when overwriting requests to specific paths with pre-recorded responses.
Rewriting HTTP Requests
You can configure proxysaur to overwrite the HTTP headers and body of a request.
Rewriting HTTP headers
Header rewrites have two parts: a match on the name or the value, and the new header name and/or value. Parts of the matched headers can be used in the old. For exact and contains matches, $0
will expand to the entire matched value. So for example, this configuration will rewrite the access-control-allow-origin
header to reuse the same name, but with the new value *
.
hosts:
test.com:
scheme: https
request_rewrites:
- when:
- path:
exact: /
rewrite:
match:
header_name:
exact: access-control-allow-origin
header_value:
# a blank value means anything will be matched
contains: ""
new_header_name: $0
new_header_value: "*"
Regular expressions can be used to do more complex rewrites with capture groups, which can be named or anonymous. In the below example we'll capture the bearer token and convert it to a basic token:
hosts:
test.com:
scheme: https
request_rewrites:
- when:
- path:
exact: /
rewrite:
match:
header_name:
exact: authorization
header_value:
regex: Bearer (?P<token>[0-9A-Za-z]+)
new_header_name: $0
new_header_value: "Basic $token"
Rewriting the HTTP request body
There isn't any matching involved in an HTTP request body rewrite. The only thing you can do is overwrite the entire request body.
hosts:
html.duckduckgo.com:
scheme: https
request_rewrites:
- when:
- path:
contains: /api
rewrite:
replace_with: |
{ "payload": "a new body" }
The proxy will calculate the size of the new body and set the Content-Length
header to the appropriate size for you.
Rewriting HTTP Responses
You can configure proxysaur to overwrite the HTTP headers, body, and status code of a response. The header and body rewrites are the same as for HTTP requests.
Status code rewrites
This example rewrites the status code from 200 to 500 for https://test.com/
:
hosts:
test.com:
scheme: https
response_rewrites:
- when:
- path:
exact: /
rewrite:
status:
exact: "200"
new_status: "500"
Customization with WASI
Compiled WASM modules, specifically compiled with WASI, can be used to extend proxysaur. Currently, bindings exist only for Rust. The API exposed by these bindings is not stable and subject to change.
A good reference point is the http-forward-proxy
, which is the proxy that is documented for intercepting and rewriting requests via the YAML configuration file.
Writing a Rust proxysaur module
Cargo.toml
Add the following dependency:
proxysaur-bindings = { git = "https://github.com/pmalmgren/proxysaur" }
Configuration
The configuration parameter in the toml
file called proxy_configuration_path
will be read in and the raw bytes are accessible with the config
bindings:
use proxysaur_bindings::config; fn main() { let config_data: Vec<u8> = proxysaur_config::get_config_data(); ... }
If the data is invalid, you can use the set_invalid_data
function which allows you to set a String
error message:
use proxysaur_bindings::config; fn main() { let config_data: Vec<u8> = proxysaur_config::get_config_data(); let config: Config = serde::from_bytes(&config_data).map_err(|err| { let msg = format!("Error serializing data: {}", err); config::set_invalid_data(msg); err }).unwrap(); }
HTTP pre-requests
Before proxysaur proxies a request, it checks to see if it should intercept the request or just forward the data between the client and the server. It does this by running the module, and checking to see what the module set the proxy mode to. A value of Intercept
means that it will proceed to call methods to manipulate the request and the response.
For example, this WASI module will proxy requests only to localhost:9234
:
use proxysaur_bindings::http::pre_request::{self, HttpPreRequest, ProxyMode}; fn main() { let request: HttpPreRequest = pre_request::http_request_get(); if request.authority == "localhost:9234" { pre_request::http_set_proxy_mode(ProxyMode::Intercept); } }
HTTP Requests
After proxysaur received a request from the client, it runs a WASI module and checks the data set by the module. In the request module, you can set anything you want on the request. For example, this one changes the request body to some random data for test.com
:
use proxysaur_bindings::http::request::{self, HttpRequest}; fn main() { let request: HttpRequest = request::http_request_get().expect("should get the request"); if request.host == "test.com" { request::http_request_set_body("haha!".as_bytes()).expect("should set the body"); } }
HTTP Responses
After proxysaur receives a response from the server, it runs a WASI module to check the response data set by the module.
use proxysaur_bindings::http::response::{self, HttpResponse}; fn main() { let response: HttpResponse = response::http_response_get().expect("should get the response"); if response.status == 200 { response::http_response_set_status(500).expect("should set the status"); response::http_response_set_body("broken!".as_bytes()).expect("should set the body"); } }