HAProxy community

ACL with multiple condition

I am running HAproxy for my Exchange 2019 Servers.
Everything is working as expected so far. This is my config:

#---------------------------------------------------------------------
# Global settings
#---------------------------------------------------------------------

global
	log 127.0.0.1 local0 debug
	chroot /var/lib/haproxy
	stats socket /var/lib/haproxy/stats mode 660 level admin
	stats timeout 30s
	user haproxy
	group haproxy
	daemon
	
	# Default SSL material locations
	ca-base /etc/ssl/certs
	crt-base /etc/ssl/private
	
	# Default ciphers to use on SSL-enabled listening sockets.
	# For more information, see ciphers(1SSL). This list is from:
	# https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
	# https://wiki.mozilla.org/Security/Server_Side_TLS#Recommended-configurations
	ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS
	ssl-default-bind-options no-sslv3
	#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:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256
	#ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets no-sslv3
	tune.ssl.default-dh-param 2048

#---------------------------------------------------------------------
# Defaults section
#---------------------------------------------------------------------

# Regarding timeout client and timeout server: 
# https://discourse.haproxy.org/t/high-number-of-connection-resets-during-transfers-exchange-2013/1158/4

defaults
	log global
	mode http
	option dontlognull
	option http-keep-alive
	option prefer-last-server
	no option httpclose
	no option http-server-close
	no option forceclose
	no option http-tunnel
	balance leastconn
	default-server inter 3s rise 2 fall 3
	timeout client 600s
	timeout http-request 10s
	timeout connect 4s
	timeout server 60s 
	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


#-------------------------------------------------------
# Stats section
#-------------------------------------------------------

listen stats 
	bind *:444 ssl crt /etc/ssl/private/cert.pem
	stats enable
	stats refresh 30s
	stats show-node
	stats auth admin:network21
	stats uri /
	stats admin if TRUE	# Administration allowed
	stats show-legends


#---------------------------------------------------------------------
# Main Front-Ends that proxy to the Back-Ends
#---------------------------------------------------------------------

frontend fe_default
	bind *:80 name http
	bind *:443 name https ssl crt /etc/ssl/private/cert.pem
	capture request header Host len 32
	capture request header User-Agent len 64
	capture response header Content-Length len 10
	maxconn 10000
	acl ssl_connection ssl_fc
	acl letsencrypt path_beg /.well-known/acme-challenge/
	acl path_autodiscover path_beg -i /Autodiscover/Autodiscover.xml
	acl path_activesync path_beg -i /Microsoft-Server-ActiveSync
	acl path_ews path_beg -i /ews/
	acl path_owa path_beg -i /owa/
	acl path_oa path_beg -i /rpc/rpcproxy.dll
	acl path_ecp path_beg -i /ecp/
	acl path_ps path_beg -i /powershell/
	acl path_oab path_beg -i /oab/
	acl path_mapi path_beg -i /mapi/
	acl path_check path_end -i HealthCheck.htm
	http-request redirect scheme https code 302 unless ssl_connection
	http-request redirect scheme https code 301 if !{ ssl_fc }
	http-request deny if path_check
	use_backend be_letsencrypt if letsencrypt
	use_backend be_exchange_https_autodiscover if path_autodiscover
	use_backend be_exchange_https_activesync if path_activesync
	use_backend be_exchange_https_ews if path_ews
	use_backend be_exchange_https_owa if path_owa
	use_backend be_exchange_https_oa if path_oa
	use_backend be_exchange_https_ecp if path_ecp
	use_backend be_exchange_https_ps if path_ps
	use_backend be_exchange_https_oab if path_oab
	use_backend be_exchange_https_mapi if path_mapi
	default_backend be_exchange_https_default

frontend fe_smtp
	mode tcp
	bind *:25 name smtp
	maxconn 100
	default_backend be_smtp

#---------------------------------------------------------------------
# Back-Ends
#---------------------------------------------------------------------

backend be_letsencrypt
	server letsencrypt 127.0.0.1:8888
	
backend be_exchange_https_activesync
	option httpchk GET /Microsoft-Server-ActiveSync/HealthCheck.htm
	http-check expect string 200\ OK
	server ex1 10.32.0.51:443 ssl verify none maxconn 10000 weight 10 check
	server ex2 10.32.0.52:443 ssl verify none maxconn 10000 weight 10 check

backend be_exchange_https_autodiscover
	option httpchk GET /Autodiscover/HealthCheck.htm
	http-check expect string 200\ OK
	server ex1 10.32.0.51:443 ssl verify none maxconn 10000 weight 10 check
	server ex2 10.32.0.52:443 ssl verify none maxconn 10000 weight 10 check
	
backend be_exchange_https_ecp
	option httpchk GET /ECP/HealthCheck.htm
	http-check expect string 200\ OK
	server ex1 10.32.0.51:443 ssl verify none maxconn 10000 weight 10 check
	server ex2 10.32.0.52:443 ssl verify none maxconn 10000 weight 10 check
	
backend be_exchange_https_ews
	option httpchk GET /EWS/HealthCheck.htm
	http-check expect string 200\ OK
	server ex1 10.32.0.51:443 ssl verify none maxconn 10000 weight 10 check
	server ex2 10.32.0.52:443 ssl verify none maxconn 10000 weight 10 check
	
backend be_exchange_https_mapi
	option httpchk GET /mapi/HealthCheck.htm
	http-check expect string 200\ OK
	timeout server 600s
	server ex1 10.32.0.51:443 ssl verify none maxconn 10000 weight 10 check
	server ex2 10.32.0.52:443 ssl verify none maxconn 10000 weight 10 check
	
backend be_exchange_https_oab
	option httpchk GET /OAB/HealthCheck.htm
	http-check expect string 200\ OK
	server ex1 10.32.0.51:443 ssl verify none maxconn 10000 weight 10 check
	server ex2 10.32.0.52:443 ssl verify none maxconn 10000 weight 10 check
	
backend be_exchange_https_oa
	option httpchk GET /RPC/HealthCheck.htm
	http-check expect string 200\ OK
	timeout server 600s
	server ex1 10.32.0.51:443 ssl verify none maxconn 10000 weight 10 check
	server ex2 10.32.0.52:443 ssl verify none maxconn 10000 weight 10 check
	
backend be_exchange_https_owa
	option httpchk GET /owa/HealthCheck.htm
	http-check expect string 200\ OK
	server ex1 10.32.0.51:443 ssl verify none maxconn 10000 weight 10 check
	server ex2 10.32.0.52:443 ssl verify none maxconn 10000 weight 10 check
	
backend be_exchange_https_ps
	server ex1 10.32.0.51:443 ssl verify none maxconn 10000 weight 10 check
	server ex2 10.32.0.52:443 ssl verify none maxconn 10000 weight 10 check
	
backend be_exchange_https_default
	timeout server 60s
	server ex1 10.32.0.51:443 ssl verify none maxconn 10000 weight 10 check
	server ex2 10.32.0.52:443 ssl verify none maxconn 10000 weight 10 check
	
backend be_smtp
	mode tcp
	option smtpchk
	server ex1 10.32.0.51:25 maxconn 100 weight 10 check
	server ex2 10.32.0.52:25 maxconn 100 weight 10 check

Now I want to only allow access to the backends be_exchange_https_ecp and be_exchange_https_ps when the request is coming from 10.0.0.0/8.
I have tried all different methods I could find, for example
use_backend be_exchange_https_ps if path_ecp { src 10.0.0.0/8 } or
http-request deny if path_ecp { src 10.0.0.0/8 } or other non-inline variants but none of them seems to work.

Is there something else wrong with my configuration preventing the ACL from working?
Any help is greatly appreciated! :slight_smile:

EDIT:
The HAPrxy is behind a NAT if that makes any difference.

This statement:

use_backend be_exchange_https_ps if path_ecp { src 10.0.0.0/8 }

Means that be_exchange_https_ps is used when path_ecp is true and the request comes from 10.0.0.0/8, otherwise other use_backend directives or the default_backend is used.

The statement:

http-request deny if path_ecp { src 10.0.0.0/8 }

means that when the acl path_ecp is true and when your request comes from your LAN (10.0.0.0/8), it will be denied. You probably want the opposite, so you need to negate the second condition:

http-request deny if path_ecp ! { src 10.0.0.0/8 }

You may want to double check that haproxy really sees the request coming from your LAN IPs. If you access the public IP with NAT hairpinning, your NAT gateway may also rewrite the source IP to your public IP.

I have confirmed through pktstat -n that the source ip of the requests is the Firewalls IP.
So I have added the line http-request deny if path_ecp { src 10.32.1.247 10.32.1.248 10.32.1.249 } under the line http-request deny if path_check.
These are the two IPs of the two firewalls and the virtual IP, that is passed over to whoever is master atm.

Unfortunately it still does not work.
I also tried http-request deny if path_ecp { src 10.32.1.28 } (Thats the IP of my workstation) but I can still browse to the /ecp/ directory.

As I said, you need to NEGATE the source condition. It’s not like you want to block you own LAN and allow everything else on the Internet to connect, but vice versa.

http-request deny if path_ecp ! { src 10.32.1.247 10.32.1.248 10.32.1.249 }

You just said you see the Firewall IPs and not your workstation IP appear on haproxy, so why would haproxy deny the request when the source IP does not match?

It doesn’t matter if I negate the stetement or not - It remains accessible from everywhere. As soon as I add another condition the statement doesn’t seem to work anymore.

Provide the log of the request that passes while using those non-working deny statements.

See this post for more information about logging configuration: