Linux Firewall (Part 5) : Reverse Proxy
Intro
In this post, we will setup HAProxy on our firewall. We can use it for some simple services running on our DMZ servers.
Install
We will install it from RHEL/Fedora repository
dnf install haproxyConfigure
Backup the default configuration if needed
cp /etc/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg.oldEdit or create a new configuration file
nano /etc/haproxy/haproxy.cfgGlobal
Create a global section in config file. We will specify maximum connections, user and group HAProxy will run as, and some crypto settings.
global
chroot /var/lib/haproxy
pidfile /var/run/haproxy.pid
maxconn 4000
user haproxy
group haproxy
daemon
# one process and 4 threads
nbproc 1
nbthread 4
# prefer client certs and min TLSv1.2
ssl-default-bind-options prefer-client-ciphers ssl-min-ver TLSv1.2
# utilize system-wide crypto-policies
ssl-default-bind-ciphers PROFILE=SYSTEM
ssl-default-server-ciphers PROFILE=SYSTEM
resolvers localdns
nameserver unbound 127.0.0.1:53Defaults
Add some defaults that all the listen and backend blocks will use if not designated in their block
defaults
mode http
log global
option httplog
option dontlognull
option http-server-close
option forwardfor except 127.0.0.0/8
option redispatch
retries 3
timeout http-request 10s
timeout http-keep-alive 10s
timeout queue 30s
timeout connect 30s
timeout client 30s
timeout server 30s
timeout check 10s
maxconn 3000HTTP
Create a block for handing HTTP traffic (port 80). We want to redirect all HTTP requests to HTTPS.
# Frontend: frontend_80 (http traffic on port 80)
frontend frontend_80
bind 0.0.0.0:80
mode http
option http-keep-alive
option forwardfor
timeout client 30s
# Redirect HTTP to HTTPS
http-request redirect scheme https code 302HTTPS
Create a block for handling HTTPS traffic (port 443). This is going to be a bigger block.
We will enable TLS with the certificates we installed in the previous post
# Frontend: frontend_443 (https traffic on port 443)
frontend frontend_443
bind 0.0.0.0:443 ssl crt /etc/haproxy/example.com.pem
mode http
option http-keep-alive
option forwardfor
timeout client 30sEnable stats endpoint at /haproxy/stats
stats enable
stats uri /haproxy/stats
stats refresh 10s[!TIP]
For better security use a different URI for stats page.
Set some headers that will be used by our applications behind the reverse proxy
http-request set-header X-Forwarded-Proto https
http-request set-header X-Forwarded-Protocol https
http-request set-header X-Client-Port %[src_port]
http-request set-header X-Real-IP %[src]
http-request set-header HTTP_X_FORWARDED_PROTO https
http-request set-header HTTP_X_FORWARDED_HOST %[hdr(host)]
http-request set-header HTTP_X_FORWARDED_PORT %[dst_port]
http-request set-header HTTP_X_FORWARDED_SSL on
http-request set-header Host %[hdr(host)]We will create two different conditions to route traffic to backends. The first one will match the host and if found in our map file, will redirect to the specified backend. The second condition will execute if the first one is not found and will redirect based on the URL path. This is useful for hiding services behind random/obfuscated paths like example.com/<random_secret_string1>/mysecretapplication/login
use_backend %[req.hdr(host),lower,map(/etc/haproxy/hosts.map)] if { req.hdr(host),lower,map(/etc/haproxy/hosts.map) -m found }
use_backend %[base,lower,map_beg(/etc/haproxy/api.map)] if { base,lower,map_beg(/etc/haproxy/api.map) -m found }That’s the end of our HTTPS block. Let’s look at the hosts and api map files we need.
The hosts.map file maps subdomains to backend names.
/etc/haproxy/hosts.map
app1.example.com app1
app2.example.com app2
ha.example.com homeassistantThe api.map file maps URL paths to backend names.
/etc/haproxy/api.map
api.example.com/somerandomstring/secretapp/ secretappNow the request app1.example.com/login will use app1 backend and api.example.com/somerandomstring/secretapp/login will use secretapp backend.
Backends
Let’s add the backends now. Each backend will have one or more upstream severs and we can pick which method we want to use for load balancing. We will also add health checks and the method for health checks
Add backend for app1
backend app1
option httpchk
mode http
balance source
timeout connect 30s
timeout server 30s
http-reuse safe
server app1_aa aa.example.com:1000 resolvers localdns check inter 10s fall 5 rise 2Add backend for ha. httpchk sends HTTP OPTIONS request. We can send a GET request for health check by adding http-check with GET method.
backend homeassistant
option httpchk
http-check send meth GET uri /manifest.json
mode http
balance source
timeout connect 30s
timeout server 30s
http-reuse safe
server ha_aa aa.example.com:1001 resolvers localdns check inter 10s fall 5 rise 2Add backend for secret API. Nothing special here, it’s just like a regular backend
backend secretapp
option httpchk
mode http
balance source
timeout connect 30s
timeout server 30s
http-reuse safe
server sec_aa aa.example.com:1005 resolvers localdns check inter 10s fall 5 rise 2Run
By default, SELinux only allow HAProxy to connect to a small subset of upstream ports. If we try to connect to other ports, HAProxy health checks will get errors
Layer4 connection problem, info: "General socket error (Permission denied)"The SELinux audit log will show avc: denied { name_connect } with scontext=system_u:system_r:haproxy_t:s0 tclass=tcp_socket message.
We can fix this by allowing HAProxy to connect to any port. Set SELinux boolean
setsebool -P haproxy_connect_any 1We can now enable and run it
systemctl enable --now haproxyEverything should be working now. Verify that all hosts are up and running using the stats page at /haproxy/stats.
Thank you for reading. Check out the other parts in the series below.