Skip to content

Latest commit

 

History

History
201 lines (183 loc) · 8.51 KB

ExampleWAF.md

File metadata and controls

201 lines (183 loc) · 8.51 KB

Basic WAF Example

There are still some basic WAF features to be implemented.

NOTE: The feature-set this role provides does not come lose to the one available in HAProxy Enterprise by default.

Fingerprinting Docs for detailed information on how you might want to track clients.

Config

haproxy:
  waf:
    script_kiddy:
      disable: ['.zip']  # disable specific entries from the script-kiddy filters
      exclude:  # exclude by path sub-string (at runtime)
        - '.well_known/'

  frontends:
    fe_web:
      bind: ['[::]:80 v4v6']

      security:
        headers: true
        fingerprint_ssl: true
        restrict_methods: true
        allow_only_methods: ['HEAD', 'GET', 'POST']
        # deny_dangerous_methods: true
        block_script_bots: true
        block_bad_crawler_bots: true
        flag_bots: true
        block_script_kiddies: true

      routes:
        be_test:
          domains: ['app.test.ansibleguy.net']

      default_backend: 'be_fallback'

  backends:
    be_test:
      security:
        # NOTE: it could make sense for your environment to apply some filters on an app-basis
        #   but these are optional
        restrict_methods: true
        allow_only_methods: ['HEAD', 'GET', 'POST']
        # deny_dangerous_methods: true
        block_script_bots: true
        block_bad_crawler_bots: true

      servers:
        - 'srv-1 192.168.10.11:80'
        - 'srv-2 192.168.10.12:80'

    be_fallback:
      lines: 'http-request redirect code 302 location https://github.com/ansibleguy'

Result

root@test-ag-haproxy-waf:/# cat /etc/haproxy/haproxy.cfg 
> # Ansible managed: Do NOT edit this file manually!
> # ansibleguy.infra_haproxy
> 
> global
>     daemon
>     user haproxy
>     group haproxy
>
>     tune.ssl.capture-buffer-size 96
> 
>     log /dev/log    local0
>     log /dev/log    local1 notice
>     chroot /var/lib/haproxy
>     stats socket /run/haproxy/admin.sock mode 660 level admin
>     stats timeout 30s
>     ca-base /etc/ssl/certs
>     crt-base /etc/ssl/private
>     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
>     mode http
>     option httplog
>     option dontlognull
>     timeout connect 5000
>     timeout client 50000
>     timeout server 50000
>     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

root@test-ag-haproxy-waf:/# cat /etc/haproxy/conf.d/frontend.cfg 
> # Ansible managed: Do NOT edit this file manually!
> # ansibleguy.infra_haproxy
>  
> frontend fe_web
>     mode http
>     bind [::]:80 v4v6
> 
>  frontend fe_web
>     mode http
>     bind [::]:80 v4v6
> 
>     http-request deny status 405 default-errorfiles if !{ method HEAD GET POST }
> 
>     # block well-known script-bots
>     http-request deny status 425 default-errorfiles if { req.fhdr(User-Agent) -m sub -i -f /etc/haproxy/lst/waf-badbot-ua-sub.lst }
>     # block well-known bad-crawler-bots
>     http-request deny status 425 default-errorfiles if { req.fhdr(User-Agent) -m str -i -f /etc/haproxy/lst/waf-crawler-ua-full.lst }
>     http-request deny status 425 default-errorfiles if { req.fhdr(User-Agent) -m sub -i -f /etc/haproxy/lst/waf-crawler-ua-sub.lst }
>     # block script-kiddy requests
>     http-request deny status 425 default-errorfiles if { path -m beg -i -f /etc/haproxy/lst/waf-script-kiddy-path-beg.lst }
>     http-request deny status 425 default-errorfiles if { path -m end -i -f /etc/haproxy/lst/waf-script-kiddy-path-end.lst }
>     http-request deny status 425 default-errorfiles if { path -m sub -i -f /etc/haproxy/lst/waf-script-kiddy-path-sub.lst }
>     # FLAG BOTS
>     ## flag bots by common user-agent substrings
>     http-request set-var(txn.bot) int(1) if !{ var(txn.bot) -m found } { req.fhdr(User-Agent) -m sub -i -f /etc/haproxy/lst/waf-bot-ua-sub.lst }
> 
>     ## unusual if action has no referrer; could produce false-positives in some special cases
>     http-request set-var(txn.bot) int(1) if !{ var(txn.bot) -m found } !{ method GET HEAD } !{ req.hdr(Referer) -m found }
>     ## browsers set these ones usually
>     http-request set-var(txn.bot) int(1) if !{ var(txn.bot) -m found } !{ req.hdr(Accept-Language) -m found }
>     http-request set-var(txn.bot) int(1) if !{ var(txn.bot) -m found } !{ req.fhdr(User-Agent) -m found }
> 
>     http-request set-var(txn.bot) int(0) if !{ var(txn.bot) -m found }
>     http-request capture var(txn.bot) len 1
> 
>     # Security headers
>     http-response add-header Strict-Transport-Security "max-age=16000000; includeSubDomains; preload;" if !{ res.hdr(Strict-Transport-Security) -m found }
>     http-response add-header X-Frame-Options "SAMEORIGIN" if !{ res.hdr(X-Frame-Options) -m found }
>     http-response add-header X-Content-Type-Options "nosniff" if !{ res.hdr(X-Content-Type-Options) -m found }
>     http-response add-header X-Permitted-Cross-Domain-Policies "none" if !{ res.hdr(X-Permitted-Cross-Domain-Policies) -m found }
>     http-response add-header X-XSS-Protection "1; mode=block" if !{ res.hdr(X-XSS-Protection) -m found }
>     # SSL fingerprint
>     http-request lua.fingerprint_ja3n
>     http-request capture var(txn.fingerprint_ssl) len 32
> 
>     http-request capture req.fhdr(User-Agent) len 200
> 
>     # BACKEND be_test
>     acl be_test_filter_domains req.hdr(host) -m str -i app.test.ansibleguy.net
>     use_backend be_test if be_test_filter_domains
> 
>     default_backend be_fallback

root@test-ag-haproxy-waf:/# cat /etc/haproxy/conf.d/backend.cfg 
> # Ansible managed: Do NOT edit this file manually!
> # ansibleguy.infra_haproxy
> 
> backend be_test
>     mode http
> 
>     http-request deny status 405 default-errorfiles if !{ method HEAD GET POST }
> 
>     # block well-known script-bots
>     http-request deny status 425 default-errorfiles if { req.fhdr(User-Agent) -m sub -i curl wget Apache-HttpClient nmap Metasploit headless cypress go-http-client zgrab python httpx httpcore aiohttp httputil urllib GuzzleHttp phpcrawl Zend_Http_Client Wordpress Symfony-HttpClient cpp-httplib java perl axios ruby }
>     # block well-known bad-crawler-bots
>     http-request deny status 425 default-errorfiles if { req.fhdr(User-Agent) -m sub -i spider test-bot tiny-bot fidget-spinner-bot download scrapy }
> 
>     server srv-1 192.168.10.11:80 check
>     server srv-2 192.168.10.12:80 check
> 
> 
> backend be_fallback
>     mode http
> 
>     http-request redirect code 302 location https://github.com/ansibleguy
>     

root@test-ag-haproxy-waf:/# systemctl status haproxy.service
> * haproxy.service - HAProxy Load Balancer
>      Loaded: loaded (/lib/systemd/system/haproxy.service; enabled; preset: enabled)
>     Drop-In: /etc/systemd/system/haproxy.service.d
>              `-override.conf
>      Active: active (running) since Sat 2024-05-04 16:24:54 UTC; 4min 11s ago
>        Docs: man:haproxy(1)
>              file:/usr/share/doc/haproxy/configuration.txt.gz
>              https://www.haproxy.com/documentation/haproxy-configuration-manual/latest/
>              https://github.com/ansibleguy/infra_haproxy
>     Process: 4574 ExecStartPre=/usr/sbin/haproxy -c -f $CONFIG -f /etc/haproxy/conf.d/ (code=exited, status=0/SUCCESS)
>     Process: 4635 ExecReload=/usr/sbin/haproxy -c -f $CONFIG -f /etc/haproxy/conf.d/ (code=exited, status=0/SUCCESS)
>     Process: 4637 ExecReload=/bin/kill -USR2 $MAINPID (code=exited, status=0/SUCCESS)
>    Main PID: 4576 (haproxy)
>      Status: "Ready."
>       Tasks: 7 (limit: 1783)
>      Memory: 132.2M
>         CPU: 297ms
>      CGroup: /system.slice/haproxy.service
>              |-4576 /usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -f /etc/haproxy/conf.d/ -p /run/haproxy.pid -S /run/haproxy-master.sock
>              `-4639 /usr/sbin/haproxy -sf 4578 -x sockpair@4 -Ws -f /etc/haproxy/haproxy.cfg -f /etc/haproxy/conf.d/ -p /run/haproxy.pid -S /run/haproxy-master.sock