How to share the same port for VPN and HTTP
Author: Nikos Mavrogiannopoulos
One of the advantages of ocserv is that is an HTTPS-based protocol and it is often used over 443 to allow bypassing certain firewalls. However the 443 TCP port is typically used by an HTTP server on a system. This section will describe how to collocate ocserv with a web server.
SSL termination on ocserv with haproxy
A method to collocate ocserv and an HTTPS server on port 443, is by using the server name indication (SNI) present on the first SSL/TLS ClientHello message, and forwarding traffic according to the name present.
In the example below we assume that the web server and ocserv have to be setup to use an alternative port, e.g., ocserv uses 4443, and the web server uses 4444. We also assume that the web server responds to www.example.com, while the vpn server, to vpn.example.com. An example configuration of haproxy that will redirect the traffic to the appropriate server is shown below.
frontend www-https
bind 0.0.0.0:443
mode tcp
tcp-request inspect-delay 5s
timeout client 300s
default_backend bk_ssl_default
backend bk_ssl_default
mode tcp
acl vpn-app req_ssl_sni -i vpn.example.com
acl web-app req_ssl_sni -i www.example.com
use-server server-vpn if vpn-app
use-server server-web if web-app
use-server server-vpn if !vpn-app !web-app
timeout server 300s
option ssl-hello-chk
server server-vpn 127.0.0.1:4443 send-proxy-v2
server server-web 127.0.0.1:4444 check
In order for ocserv to obtain information on the incoming session, we have enabled the proxy protocol in haproxy's configuration (with the send-proxy-v2 option). That requires ocserv's configuration to contain the following:
listen-proxy-proto = true
Another point to keep in mind are the connection timeouts. Many reverse
proxies, especially haproxy in particular, will have some sort of a
connection timeout. This timeout setting in reverse proxies must be aligned
to the ocserv configuration keepalive
so that the main TCP/TLS
connection that is established between a client/ocserv, doesn't die out
due to the reverse-proxy detecting a no-activity timeout.
The config on the haproxy side are called out in the timeout server <time>
directive, as well as the timeout client <time>
directive.
Corresponding to the above, what may need to be tweaked in the ocserv.config
is the keepalive
directive. Note, for optimal performance, the
keepalive number in ocserv config must be LESS THAN the reverse-proxy
connection timeouts
keepalive = 290
If you are hosting multiple websites on your webserver this alternative configuration may be more suitable for you as it does not require you to have each hostname fully specified (tested on haproxy 2.2 LTS):
defaults
timeout connect 10s
timeout client 1m
timeout server 1m
frontend www-https
bind 0.0.0.0:443
mode tcp
tcp-request inspect-delay 5s
tcp-request content accept if { req.ssl_hello_type 1 }
use_backend bk_vpn if { req_ssl_sni vpn.example.nl }
use_backend bk_ssl_default if { req_ssl_sni -i -m end .nl }
use_backend bk_ssl_default if { req_ssl_sni -i -m end .com }
use_backend bk_ssl_default if { req_ssl_sni -i -m end .eu }
# In case of missing SNI information fallback to the VPN backend.
# This is needed for older openconnect versions (as present in Ubuntu 18.04 LTS)
default_backend bk_vpn
backend bk_vpn
mode tcp
option ssl-hello-chk
server server-vpn 127.0.0.1:4443 send-proxy-v2
backend bk_ssl_default
mode tcp
option ssl-hello-chk
server server-web 127.0.0.1:4444 check
DTLS and reverse proxies
Some reverse proxies, e.g. haproxy, will NOT proxy out UDP connections. Generally, this is NOT a blocker on many implementations, but there are a few things that need to be accounted for.
DTLS, if required to be implemented on the VPN, requires ocserv to be able to listen to a UDP port where the DTLS communication happens.
This will often lead to a conflict if the ocserv configuration does not account for the reverse proxy NOT being able to proxy out UDP. In such cases while for the TCP channel, the communication would typically go
VPNClient->HAProxy->OCServ
... the UDP channel communication needs to be
VPNClient->OCServ
This can be accomplished by having ocserv LISTEN for UDP on a DIFFERENT
host IP, by implementing the udp-listen-host
directive in the config
listen-host=127.0.0.1
udp-listen-host=192.168.x.x