Skip to content

mirainc/funes

Repository files navigation

funes

A forward proxy with caching.

"I was struck by the thought that every word I spoke, every expression of my face or motion of my hand would endure in his implacable memory..."

Uses: https://github.com/chobits/ngx_http_proxy_connect_module/tree/96ae4e06381f821218f368ad0ba964f87cbe0266 (code copied to ngx_http_proxy_connect_module). Code is copied instead of using submodule as Github doesn't support including submodule code in releases.

Dependencies

  • faketime
  • openssl (tested with version 1.0.2g)

Start

funes listens on ports 80, 443, and 3128. By default it will cache all GET requests with a TTL of 5s, with certain content types having longer expirations (see conf/nginx.conf.server). It will serve stale responses indefinitely if there is no network connection.

Build for your local architecture

make

This will compile Nginx and place all required files in the build directory.

Inside the build directory, bash run.sh to start the application.

There are several helper scripts that are placed in in the build directory. These can be run manually or added to crontabs.

  • logtruncate.sh
    • Truncates all logs from the Funes log directory.
  • clear_cert_disk_cache.sh <AGE_MIN, default 1440> <CACHE_DIR, default /data/funes/cert_cache>
    • Clears any generated SSL cert files older than AGE_MIN from CACHE_DIR.

make package can be run to zip the build directory to package/funes.zip. The zip can be extracted and run elsewhere.

Build options

The following build options are available using environment variables:

# Disable the transparent proxy on port 80/443
DISABLE_TRANSPARENT_PROXY

# Disallow all connections except those from 127.0.0.1/24
RESTRICT_LOCAL

# Disallow all connections except from 127.0.0.1/24 and Docker IPs (172.18, 172.19, 172.21 prefixes)
# Cannot be combined with RESTRICT_LOCAL
RESTRICT_LOCAL_DOCKER

# Disable dynamic SSL cert generation, instead passing the root cert on each response
DISABLE_DYNAMIC_CERTS

Usage example:

DISABLE_TRANSPARENT_PROXY=1 DISABLE_DYNAMIC_CERTS=1 make

DISABLE_TRANSPARENT_PROXY=1 RESTRICT_LOCAL=1 make

DISABLE_TRANSPARENT_PROXY=1 RESTRICT_LOCAL_DOCKER=1 make

Build for Docker

docker build -f Dockerfile.build .
docker run -d -p 3128:3128 <created_image_id>

Development

make dev-build 		# Builds the docker container
make dev        	# Starts container and runs Funes.

will start a local development instance in Docker.

If you have not made any changes between runs of the Docker container, you can run make dev again to avoid having to rebuild Openresty.

After making changes, run make dev-build again to rebuild the container and pull in changes.

SSL Certificates

For HTTPS requests, funes will generate certificates signed by its root CA certificate using OpenSSL. This is similar to other HTTPS proxies such as mitmproxy. The root CA cert key used may be specified via ROOT_CA_CERT and ROOT_CA_KEY env vars when starting funes, or is autogenerated and printed to stdout. If the root CA cert is added to the client's local certificate store, certificate checking may be left on for the client.

The root CA cert key used may be specified via ROOT_CA_CERT and ROOT_CA_KEY env vars when starting funes

If run with default settings, you will see the generated root CA cert in Docker stdout, e.g.:

-----BEGIN CERTIFICATE-----
MIIDzzCCAregAwIBAgIJAL0LMN2/s8xBMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNV
...
...
-----END CERTIFICATE-----

You may copy everything in this block into a file with a .crt extension and add it to your local system's certificates. For example, on Debian/Ubuntu you can run:

sudo cp <COPIED_CERT_FILE>.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates

Or on Mac: https://www.eduhk.hk/ocio/content/faq-how-add-root-certificate-mac-os-x

Testing Changes

Run the full test suite:

make test

Due to Codeship limitations, it's not possible to run the full test suite in Codeship because you cannot disable the network adapter.

Full test suite should be run with RUN_CONTEXT=local after changes to test offline functionality.

Customizing expiration rules

In conf/nginx.conf.server, expiration rules can be set for URI ($uri_expiry), host ($host_expiry), and content type ($content_type_expiry). By default there are only content type expiry rules defined. Refer to the examples in this file for usage patterns.

Configuring browser proxy

Chrome

Start chrome with the following flag:

--proxy-server="https=127.0.0.1:3128;http=127.0.0.1:3128"

Electron

Wrap your call to <electron_window>.loadURL following this example:

mainWindow.webContents.session.setProxy({proxyRules:"https=192.168.99.100:3128;http=192.168.99.100:3128"}, function () {
      mainWindow.loadURL(url.format({
        pathname: path.join(__dirname, 'index.html'),
        protocol: 'file:',
        slashes: true
      }))
  });

Architecture

funes is meant to be used as a caching proxy for browser clients that need to function seamlessly if offline.

When Chrome is configured to use a proxy <proxy_host>:<proxy_port>, it does the following

  1. Sends HTTP requests to <proxy_host>:<proxy_port> instead of <external_host>:80
  • This request will contain a header Host: <external_host
  1. Send a CONNECT request to <proxy_host>:<proxy_port>, which opens a tunnel to the proxy. Send the original request through the tunnel.

funes responds to these requests:

  1. Send the request to <external_host>:80. Cache the result and return.
  2. Open a tunnel to <funes_host>:443, which then receives the original HTTPS request. Send the request to <external_host>:443. Cache the result and return.

See https://en.wikipedia.org/wiki/HTTP_tunnel#HTTP_CONNECT_tunneling for more information on how tunneling works.

By default, the proxy server will cache everything with a TTL of 5s. If the resource cannot be refreshed after the TTL, it will continue serving the stale cached copy.

Implementation

The main proxy server uses Nginx, and the Nginx Proxy Connect module to implement HTTP CONNECT method handling (since Nginx does not natively support the CONNECT method).

To redirect all HTTPS requests received by the tunnel back to Nginx, a local dnsmasq instance is started that responds to all DNS requests with 127.0.0.1. This local dnsmasq is configured as the DNS resolver for the proxy. Although dnsmasq works well for this purpose, we now use the proxy_connect_address directive provided by the Nginx Proxy Connect module, which redirects all proxied traffic to the specified address.

Generating cacert.pem

The cacert.pem file needs to be periodically updated to contain the latest public certificate authorities. If not you may eventually see SSL failures for some domains that result in the following error:

upstream SSL certificate verify error: (20:unable to get local issuer certificate)

This can happen if the cert of the upstream server was created with a CA that does not exist in cacert.pem.

To update cacert.pem run the following command:

perl scripts/mk-ca-bundle.pl

Copy the resulting ca-bundle.crt file to certs/cacert.pem.