Skip to content

HAProxy Setup

klzgrad edited this page Nov 5, 2022 · 5 revisions

Configure Let's Encrypt

As root, install acme.sh:

git clone https://github.com/Neilpang/acme.sh.git
cd ./acme.sh
./acme.sh --install
. ~/.bashrc

Issue ECC and RSA certificates for example.org (assuming an HTTP server running at /var/www/html):

site=example.org
acme.sh -k ec-256 -d $site -d www.$site --issue -w /var/www/html
acme.sh -k 2048 -d $site -d www.$site --issue -w /var/www/html

Install in bundled format as required by HAProxy for supporting dual certificates:

mkdir -p /etc/haproxy/certs
acme.sh --install-cert --ecc -d $site --key-file /tmp/$site.key --fullchain-file /tmp/$site.crt --reloadcmd "cat /tmp/$site.* >/etc/haproxy/certs/$site.pem.ecdsa; rm /tmp/$site.*; service haproxy restart"
acme.sh --install-cert -d $site --key-file /tmp/$site.key --fullchain-file /tmp/$site.crt --reloadcmd "cat /tmp/$site.* >/etc/haproxy/certs/$site.pem.rsa; rm /tmp/$site.*; service haproxy restart"

Here acme.sh also automatically installs a crontab for certificate renewal.

Set up a forward proxy

apt install tinyproxy.

Edit /etc/tinyproxy/tinyproxy.conf:

User tinyproxy
Group tinyproxy
PidFile "/run/tinyproxy/tinyproxy.pid"
MaxClients 100
MinSpareServers 5
MaxSpareServers 20
StartServers 10
Port 8888

Listen 127.0.0.1
LogFile "/dev/null"
DisableViaHeader Yes

You can also use NaiveProxy as the forward proxy: nohup ./naive --listen=http://127.0.0.1:8080 &. Although as a proxy server it only works together with Naive client, not directly with the browser (only HTTPS works).

Set up HAProxy

apt install haproxy on server. (Make sure it is 1.8.15+ or 1.9+.)

Append the following to /etc/haproxy/haproxy.cfg:

userlist users
        user name insecure-password pass

frontend haproxy_tls
        bind :443 ssl crt /etc/haproxy/certs/ alpn h2,http/1.1
        option http-use-proxy-header
        acl login base_dom login-key.test
        acl auth_ok http_auth(users)
        http-request auth if login !auth_ok
        http-request redirect location https://google.com if login auth_ok
        use_backend proxy if auth_ok
        default_backend masquerade

backend proxy
        http-request del-header proxy-authorization
        server proxy 127.0.0.1:8888

backend masquerade
        server nginx 127.0.0.1:80

Without credentials the proxy disguises as a regular HTTP server, rejecting proxy requests.

If your browser can't preset credentials, visit https://login-key.test to login with name and pass. (http://login-key.test doesn't work!)

name, pass, and login-key.test should be changed to something hard to guess, e.g. login-<random-digits>.test.

Android Wifi proxy support (optional)

HTTPS proxying on Android needs a URL for a PAC file. Add the following to the top of frontend haproxy_tls section after the bind clause:

        acl pac path /proxy-key.pac
        errorfile 200 /etc/haproxy/errors/proxy.pac.http
        http-request deny deny_status 200 if pac

proxy-key.pac should be changed to something hard to guess.

The content of /etc/haproxy/errors/proxy.pac.http should look like (double new lines at the end):

HTTP/1.0 200 OK
Cache-Control: no-cache
Connection: close
Content-Type: text/plain

function FindProxyForURL(url, host) {
  if (shExpMatch(host, "localhost")) return "DIRECT";
  if (shExpMatch(host, "192.168.*")) return "DIRECT";
  if (shExpMatch(host, "127.0.*")) return "DIRECT";
  return "HTTPS example.org";
}

Performance

Check https://github.com/klzgrad/naiveproxy/wiki/Parameter-Tuning.

Also, consider deploying a caching DNS server to speed up DNS resolution.

Rationale

The overall rationale is to select the most commonly used pieces of software/configurations so there are less distinguishable features to detect.

HAProxy is one of the commonly used traffic front-ends here for removing traffic signatures. Nginx is more popular but it hardly supports HTTP CONNECT tunnels and even less so for HTTP/2 proxying. Apache Traffic Server seems to have a lot of bugs; I didn't manage to make it work. Nghttpx doesn't do application-layer routing.

https://github.com/cloudflare/sslconfig is used as the TLS cipher list here because it seems quite the standard. TLS cipher selection can be very non-trivial and can leak a lot of entropy, see https://ssllabs.com/. There is also this https://mozilla.github.io/server-side-tls/ssl-config-generator/. The two main considerations here are to use elliptic curve cryptos for signatures which are faster than RSA, and to prioritize ChaPoly for encryption which can be energy efficient on mobile devices (if AES is not CPU-accelerated, otherwise it's not that much). The cipher list used here is modified from Cloudflare's list because [algo1|algo2] ("bracketed equal-preference") is specific to BoringSSL and not supported in OpenSSL built with HAProxy. This is no longer the case as Debian provides a modern cipher list by default via https://ssl-config.mozilla.org/#server=haproxy&server-version=2.0.3&config=intermediate.

Let's Encrypt is used for TLS certificates. Let's Encrypt may seem to have issued a lot of certificates but many of those are issued for malware and phising sites too, which means using Let's Encrypt doesn't really exempt your domain names from scrutiny. In fact, for the purpose of reducing traffic features it's probably no better than creating TLS certificates with your own self-signed root CA (different from self-signed certificates). Here we use Let's Encrypt because it's easier to setup, without having to manipulate the user's root CA store. We use acme.sh to issue both RSA and ECC certificates because the dual certificate setup is common (the business reason is usually to improve browser compatibility). certbot doesn't support ECC certificates yet. acme.sh's reloadcmd may look unwieldy because HAProxy has some specific requirements for dual certificate files and acme.sh's HAProxy hook doesn't do that yet.

Tinyproxy is used instead of Squid only because I didn't figure out how to set up transparent proxying with Squid.