OpenVPN server behind the load balancer

In some of environments there it is required to run the HTTPS and OpenVPN server on the port 443. For servers with a few assigned public IP addresses it is much easier then with the server with only one public IP address. There are some methods to use both OpenVPN and HTTPS traffic on the same port. In this article you will find information how to set up the HAProxy load balancer to listen HTTPS and OpenVPN traffic.

Listen on the port 443

First, prepare the frontend to listen on the port 443:

frontend httpsin
    bind :443
    bind :::443
    mode tcp
    tcp-request inspect-delay 5s
    tcp-request content accept if { req.ssl_hello_type 1 }
    tcp-request content accept if HTTP
    use_backend vmopenvpn if !{ req.ssl_sni -m found }
    default_backend respin-tls

This fronted is listening on the port 443 both on IPv4 and IPv6. Next the load balancer is inspecting the received package. When packet SSL SNI (Server Name Indication) is not found then the traffic is redirected to the backend with the OpenVPN server, in other case the traffic is redirected to another backend which creates a virtual server  with HTTPS packets.

Backend – respin-tls

The next step is the backend which is a kind of the virtual server which handles the packets with HTTPS traffic.

backend respin-tls
    mode tcp
    option http-server-close
    server loopback-for-tls abns@haproxy-tls-term send-proxy-v2

Listening HTTPS traffic

When the new frontend for gathering the whole traffic from the port 443, the backend for respin-tls and the OpenVPN server are ready, then it is time for creating the frontend for listening on the haproxy-tls-term server.

frontend https
    bind abns@haproxy-tls-term accept-proxy ssl crt /etc/haproxy/cert/
    option forwardfor except header X-Forwarded-For
    option forwardfor header X-Real-IP
    http-request set-header X-Real-IP %[src]
    http-request set-header X-Forwarded-For %[src]
    http-request set-header X-Forwarded-Port %[dst_port]
    http-request add-header X-Forwarded-Proto https if { ssl_fc }
    http-request add-header X-Forwarded-Proto http if !{ ssl_fc }
    use_backend vmfoobar if { hdr(host) -i }
    default_backend defhttpsbackend

This frontend is listening on the haproxy-tls-term only. To avoid duplicating the section with listening on the port 80 (HTTP) it is possible to add more then one bind into the frontend, for example:

bind :80
bind :::80
bind abns@haproxy-tls-term accept-proxy ssl crt /etc/haproxy/cert/

When all is done, reload the HAProxy service. If there is no error in the configuration file, the VPN and the HTTPS traffic should work properly.

Transparent proxy

When connecting to the OpenVPN server is working properly and the whole HTTPS traffic also works, it is time for some improvements. In the virtual machine with the OpenVPN server you can find some logs in the system logs like:

Mar 8 18:29:21 vpn ovpn-server[103]: Control Channel: TLSv1.2, cipher TLSv1/SSLv3 ECDHE-RSA-AES256-GCM-SHA384, 2048 bit RSA

In this case we know that the traffic to the OpenVPN is coming from the node (the node has address on which the virtual machines are working. For system administrators this information is useless but there is a solution for that – transparent proxy in HAProxy.

How to set up transparent proxy?

First, make sure that the installed version of HAProxy in your system supports transparent proxy. You can check it with the command

[root@server ~]# haproxy -vv | grep -i transparent
Built with transparent proxy support using: IP_TRANSPARENT IPV6_TRANSPARENT IP_FREEBIND

If HAProxy was not build with the transparent proxy support, please consider the update or build the HAProxy by yourself.

Sysctl and iptables

Add these lines into /etc/sysctl.d/transparentproxy.conf:

net.ipv4.ip_forward = 1
net.ipv4.ip_nonlocal_bind = 1

Reload the system variables with the command

sysctl --system

Set up the firewall – in this case add the following lines into /etc/network/interfaces under the vmbr0 interface:

post-up iptables -t mangle -N DIVERT
post-up iptables -t mangle -A PREROUTING -p tcp -m socket -j DIVERT
post-up iptables -t mangle -A DIVERT -j MARK --set-mark 1
post-up iptables -t mangle -A DIVERT -j ACCEPT
post-up ip rule add fwmark 1 lookup 100
post-up ip route add local dev lo table 100
post-down iptables -t mangle -D PREROUTING -p tcp -m socket -j DIVERT
post-down iptables -t mangle -D DIVERT -j MARK --set-mark 1
post-down iptables -t mangle -D DIVERT -j ACCEPT
post-down ip rule del fwmark 1 lookup 100
post-down ip route del local dev lo table 100

Or use iptables-persistent.

Reload the networking service.

Configuring HAProxy

In the HAProxy change and add some lines. In the fronted which is listening on the port 443 add the keyword transparent after bind:

frontend httpsin
    bind :443 transparent
    bind :::443 transparent

In the backend which should use the transparent proxy add the line source usesrc clientip like in the backend below:

backend vmopenvpn
    mode tcp
    timeout server 2h
    source usesrc clientip
    server vm100 check

After that reload the HAProxy service and watch on the syslog on the virtual machine with the OpenVPN server:

Mar 8 18:48:03 vpn ovpn-server[103]: Control Channel: TLSv1.2, cipher TLSv1/SSLv3 ECDHE-RSA-AES256-GCM-SHA384, 2048 bit

Now you can see the real IP of the client who wants to connect the the OpenVPN server.