Using Cloudflare with HAProxy

A while ago I switched to using Cloudflare for my domain names DNS. The main reason I did this was for dynamic DNS since I had a dynamic IP on my home Internet connection. I then looked into what else I could use Cloudflare for and over time have taken advantage of more of their free options.

I was looking at setting up HAProxy anyway because I have a server that I use to play with all kinds of web services. I have a Icinga2 instance for monitoring, a Bookstack setup for taking notes, a Home Assistant install, and more. To make these easily accessible externally I wanted to use HAProxy with SNI. While looking into this I discovered Cloudflare Origin CA and use it in the following instructions.

Requirements

To follow this setup completely you will need:

Cloudflare Setup

After you have followed through the Cloudflare Getting Started guide

You will then need to configure Cloudflare’s Universal SSL by following the guide, https://support.cloudflare.com/hc/en-us/articles/115000479507 to create a Cloudflare Origin Certificate to install onto your router.

Once you have the certificate and key, you can combine them together to create a .pem file. You will then copy this .pem to somewhere on your router. I’ve placed mine under /etc/ssl/cloudflare/domain.com.pem

Also remember to backup this file somewhere secure as in that location it won’t be saved during an upgrade of OpenWrt/LEDE.

HAProxy Configuration

This is the configuration I have used but I am not sure if it is the best way to go about it. I would love some feedback about whether there is a better way to manage it.

This file is located under /etc/haproxy.cfg

# Global parameters
defaults
    # Slowloris protection
    timeout http-request 5s
    timeout connect 5s
    timeout client 30s
    timeout server 30s
    timeout http-keep-alive 4s
    # Close the backend connection
    option http-server-close

global
    log 10.0.0.10 local0
    maxconn 32000
    ulimit-n 65535
    uid 0
    gid 0
    daemon
    nosplice
    tune.ssl.default-dh-param 2048
    ssl-default-bind-ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDH
    ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
    ssl-default-server-ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:EC
    ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets

listen local_health_check
    bind :60000
    mode health

# Frontend for SNI Passthrough
frontend frontend_snipt
    bind *:443
    mode tcp
    log global
    option forwardfor header X-Forwarded-For
    tcp-request inspect-delay 5s
    tcp-request content accept if { req_ssl_hello_type 1 }
# This will decide which backend to pass the traffic to
# based on the FQDN
    use_backend backend_snipt_1 if { req_ssl_sni -i 1.domain.com }
    use_backend backend_snipt_2 if { req_ssl_sni -i 2.domain.com }
    use_backend backend_snipt_3 if { req_ssl_sni -i 3.domain.com }
    use_backend backend_snipt_4 if { req_ssl_sni -i 4.domain.com }
    use_backend backend_snipt_5 if { req_ssl_sni -i 5.domain.com }
    default_backend backend_1

# Backend for SNI Passthrough
backend backend_snipt_1
    mode tcp
    server localhost 127.0.0.1:7000 check

backend backend_snipt_2
    mode tcp
    server localhost 127.0.0.1:7001 check

backend backend_snipt_3
    mode tcp
    server localhost 127.0.0.1:7002 check

backend backend_snipt_4
    mode tcp
    server localhost 127.0.0.1:7003 check

backend backend_snipt_5
    mode tcp
    server localhost 127.0.0.1:7004 check

# Normal frontend
frontend frontend_1
    bind *:7000 ssl strict-sni crt /etc/ssl/cloudflare/domain.com.pem
    mode http
    use_backend backend_1

frontend frontend_2
    bind *:7001 ssl strict-sni crt /etc/ssl/cloudflare/domain.com.pem
    mode http
    use_backend backend_2

frontend frontend_3
    bind *:7002 ssl strict-sni crt /etc/ssl/cloudflare/domain.com.pem
    mode http
    use_backend backend_3

frontend frontend_4
    bind *:7003 ssl strict-sni crt /etc/ssl/cloudflare/domain.com.pem
    mode http
    use_backend backend_4

frontend frontend_5
    bind *:7004 ssl strict-sni crt /etc/ssl/cloudflare/domain.com.pem
    mode tcp
    option clitcpka
    timeout client 3h
    timeout server 3h
    use_backend backend_5

# Normal backend                          
backend backend_1
    mode http
    server server01 10.0.0.10:80 check

backend backend_2
    mode http
    server server01 10.0.0.10:8080 check

backend backend_3
    mode http
    server server02 10.0.0.254:80 check

backend backend_4
    mode http
    server server01 10.0.0.10:8081 check

backend backend_5
    mode http
    server server01 10.0.0.10:8082 check

With this you can also make the LuCI interface available externally and know that the traffic will all be encrypted. This setup offloads all the TLS encryption.

You will need to also go to System > Startup in LuCI and start the haproxy service.

Open Port via LuCI

The last thing you need to make this all work is to open port 443 on the router. Browse to Network > Firewall > Traffic Rules and under the section Open ports on router: add a rule for TCP for port 443. If you want to make that more secure you can make that rule only accept connections from Cloudflare’s IP Ranges.

Advertisements

Accessing your modem from OpenWRT Router

In this setup the modem’s IP address will be 192.168.20.254/24 and the OpenWRT router’s address will be 192.168.10.254/24

Go to Network section and open Interfaces
Down the bottom of the Interface section click on Add new interface…

01 Create InterfaceName the interface e.g. modem
Select the radio button next to the wan interface in the Cover the following interface section
Click Submit
The configuration page for the new interface will now load

02 Interface Config

Enter in the address as 192.168.20.253 and netmask as 255.255.255.0
Go to the Firewall Settings tab and make sure the new interface is assigned to the wan zone

03 Zone Config

Click Save & Apply

Click on the Network  menu and select Interfaces

06 Connect Interface

Click Connect next to the modem interface
You should now be able to access your modem’s web console from a device on your LAN