Hi,
So I’ve only just started out with HA proxy and my requirements aren’t anything too heavy but I’m still running into a few issues.
Goal
I have a single IP that I use port forwarding on to allow me to host many web servers behind an NGINX reverse proxy. I now have to get an RDS Gateway functioning behind that proxy. A week of tinkering has been only partially successful until I realised that this solution won’t work. I’ll not go into the details here.
Solution
I came accross this: https://www.haproxy.com/documentation/haproxy/deployment-guides/remote-desktop/rdp-gateway and thought this would be a viable (though a little messy), solution. The thought being that HA Proxy will be the first hop, listening on 80 and 443, examining the headers of inbound connections. If it sees rds.mydomain.com, it forwards the connection to the RDS Gateway. If it doesn’t match, it forwards to my NGINX reverse proxy. Having HA Proxy terminate the SSL connection is also attractive to me, especially when you consider things like this: https://betanews.com/2020/01/27/windows-remote-desktop-gateway-rce-exploit. So, thats the basics of what I’m aiming for.
For my initial testing, I have used most of the config found in the link above with some of my own additions. HA Proxy is listening on 81 and 443 (although only 81 is currently accessible while I nail this part of my problem down), it should check the headers, see that they are bound for webserver.mydomain.com, skip the config for rds.mydomain.com and forward to my test webserver.
Problem
Punching in http://webserver.mydomain.com:81 hits the HA Proxy server and forwards me to http://webserver.mydomain.com:81/RDWeb. Obviously the acl checking is not firing as I expected although I’m not totally clear why.
Code
You’ll have to excuse the heavily commented code. It’s for my benefit as I learn all the syntax (and possibly will help others point out where I’m getting confused).
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
stats timeout 30s
user haproxy
group haproxy
daemon
# Default SSL material locations
ca-base /etc/ssl/certs
crt-base /etc/ssl/private
# See: https://ssl-config.mozilla.org/#server=haproxy&server-version=2.0.3&config=intermediate
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
defaults
log global
# Define this connection type as HTTP. Defining this parameter allows for further parameters to be defined.
mode http
# Define additional options. Descriptions to follow.
option httplog
option dontlognull
option http-keep-alive
timeout http-request 10s
timeout queue 1m
timeout connect 10s
timeout client 10s
timeout server 1m
timeout http-keep-alive 10s
timeout check 10s
maxconn 1000
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 408 /etc/haproxy/errors/408.http
errorfile 500 /etc/haproxy/errors/500.http
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http
errorfile 504 /etc/haproxy/errors/504.http
# Define one universal frontend to capture both HTTP & HTTPS traffic.
frontend mydomain.com
bind *:81
bind *:443
# Define an acl called "acl_rds" that looks for mydomain.com in the request header of the inbound connection.
acl acl_rds hdr(host) -i rds.mydomain.com
# Capture the host name from the request header for later use. Max length of header can be no more than 32
# characters in length. We can only capure request and response headers in seperate streams. Request captures
# on inbound traffic and responses on outbound traffic.
capture request header Host len 32
# Redirect request to /RDWeb if user tries to access the root (/).
http-request redirect location /RDWeb/ if { path -i / /RDWeb }
# Define an acl called "path_rdweb" that checks the path of inbound requests for "/RDWeb/".
acl path_rdweb path_beg -i /RDWeb/
# Deny the request unless the requested URL matches the acl defined above (path_rdweb). This prevents users
# trying any sub-folders at rds.mydomain.com other than /RDWeb.
http-request deny unless path_rdweb
# Use the backend named "backend_rds" if you find the acl defined as "acl_rds".
use_backend backend_rds if acl_rds
# If the incoming connection does not match rds.mydomain.com, send the connection to the nginx proxy server.
default_backend backend_nginx
backend backend_rds
# Use the balacing algorithm "leastconn". This algorithm is best suited for long connections rather than
# website connections.
balance leastconn
# This is how ha-proxy will perform a health-check on the RDS server. It will check to see if the /RDWeb
# path is accessible. As soon as it becomes inaccessible, the server in the pool (doesn't apply in this
# specific config), is seen as being down. ha-proxy will continue checking this URL and the moment it
# comes back up again, it the backend server will be added back into the pool.
option httpchk GET /RDWeb
# Insert a cookie called RDWEB. Append a “Cache-Control: nocache” to the cookie since this type of
# traffic is supposed to be personnal and we don’t want any shared cache on the internet to cache it.
cookie RDPWEB insert nocache
# Override the default options for the default server - THIS MIGHT NOT BE NEEDED - POSSIBLE REMOVAL
default-server inter 3s rise 2 fall 3
# Define the first (and only in this specific config), backend server.
server rds_gw 192.168.1.30:443 maxconn 1000 weight 10 ssl verify none check cookie rds_gw
backend backend_nginx
mode http
balance roundrobin
server srv1 192.168.1.29:80
Hopefully, someone can point out my errors. Once I get the redirect working, I can move onto testing the RDS portion.
Thanks for any suggestions you have.