HAProxy configuration and lua scripts implementing a challenge-response page where visitors solve a captcha and/or proof-of-work (cpu intensive) task. Intended to stop bots, spam, ddos, etc.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

132 lines
6.1 KiB

global
daemon
maxconn 256
log stdout format raw local0 debug
lua-load /etc/haproxy/scripts/register-servers.lua
lua-load-per-thread /etc/haproxy/scripts/register-bot-check.lua
stats socket /var/run/haproxy.sock mode 666 level admin
stats socket 127.0.0.1:1999 level admin
httpclient.ssl.verify none
# Allow larger buffer size for return-file of argon scripts
tune.bufsize 51200
defaults
log global
mode http
option httplog
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
timeout tarpit 5000ms
program api
command dataplaneapi -f /etc/haproxy/dataplaneapi.hcl --update-map-files
no option start-on-reload
frontend stats-frontend
bind 127.0.0.1:2000
option tcplog
mode tcp
acl white_list src 127.0.0.1
tcp-request connection reject unless white_list
default_backend stats-backend
backend stats-backend
mode tcp
server stats-localhost 127.0.0.1:1999
frontend http-in
# Clearnet http (you'll have to figure out https yourself)
bind *:80
# Or instead, for Tor, to use circuit IDs as "IP":
#bind 127.0.0.1:80 accept-proxy
#option forwardfor
# drop requests with invalid host header
acl is_existing_vhost hdr(host),lower,map_str(/etc/haproxy/map/hosts.map) -m found
http-request silent-drop unless is_existing_vhost
# debug information at /.basedflare/cgi/trace
http-request return status 200 content-type "text/plain; charset=utf-8" lf-file /etc/haproxy/template/trace.txt if { path /.basedflare/cgi/trace }
# acl for blocked IPs/subnets
acl blocked_ip_or_subnet src,map_ip(/etc/haproxy/map/blocked.map) -m found
http-request deny deny_status 403 if blocked_ip_or_subnet
# ratelimit (and for tor, kill circuit) on POST bot-check. legitimate users shouldn't hit this.
http-request track-sc0 src table bot_check_post_throttle if { path /.basedflare/bot-check } { method POST }
http-request lua.kill-tor-circuit if { sc_http_req_rate(0) gt 1 }
http-request tarpit if { sc_http_req_rate(0) gt 1 }
# acl for lua check whitelisted IPs/subnets and some excluded paths
acl is_excluded src,map_ip(/etc/haproxy/map/whitelist.map) -m found
acl is_excluded path /favicon.ico #add more
# acl ORs for when ddos_mode_enabled
acl ddos_mode_enabled_override str("true"),map(/etc/haproxy/map/ddos_global.map) -m found
acl ddos_mode_enabled hdr(host),lower,map(/etc/haproxy/map/ddos.map) -m bool
acl ddos_mode_enabled base,map(/etc/haproxy/map/ddos.map) -m bool
# serve challenge page scripts directly from haproxy
http-request return file /etc/haproxy/js/argon2.js status 200 content-type "application/javascript; charset=utf-8" hdr "cache-control" "public, max-age=300" if { path /.basedflare/js/argon2.js }
http-request return file /etc/haproxy/js/challenge.js status 200 content-type "application/javascript; charset=utf-8" hdr "cache-control" "public, max-age=300" if { path /.basedflare/js/challenge.js }
http-request return file /etc/haproxy/js/worker.js status 200 content-type "application/javascript; charset=utf-8" hdr "cache-control" "public, max-age=300" if { path /.basedflare/js/worker.js }
# acl for domains in maintenance mode to return maintenance page (after challenge page htp-request return rules, for the footerlogo)
acl maintenance_mode hdr(host),lower,map_str(/etc/haproxy/map/maintenance.map) -m found
http-request return lf-file /etc/haproxy/template/maintenance.html status 200 content-type "text/html; charset=utf-8" hdr "cache-control" "private, max-age=30" if maintenance_mode
# map if you want a domain to be a redirect from the edge (302 for now)
http-request redirect location https://%[hdr(host),map(/etc/haproxy/map/rewrite.map)]%[capture.req.uri] code 302 if { hdr(host),map(/etc/haproxy/map/rewrite.map) -i -m found }
# create acl for bools updated by lua
acl captcha_passed var(txn.captcha_passed) -m bool
acl pow_passed var(txn.pow_passed) -m bool
acl validate_captcha var(txn.validate_captcha) -m bool
acl validate_pow var(txn.validate_pow) -m bool
# check pow/captcha and show page if necessary
acl on_bot_check path /.basedflare/bot-check
http-request use-service lua.bot-check if on_bot_check !is_excluded
# challenge decisions, checking, and redirecting to /bot-check
http-request lua.decide-checks-necessary if !is_excluded !on_bot_check ddos_mode_enabled
http-request lua.captcha-check if !is_excluded !on_bot_check validate_captcha
http-request lua.pow-check if !is_excluded !on_bot_check validate_pow OR !is_excluded !on_bot_check ddos_mode_enabled_override
http-request redirect location /.basedflare/bot-check?%[capture.req.uri] code 302 if validate_captcha !captcha_passed !on_bot_check ddos_mode_enabled !is_excluded OR validate_pow !pow_passed !on_bot_check ddos_mode_enabled !is_excluded OR !pow_passed ddos_mode_enabled_override !on_bot_check !is_excluded
# X-Cache-Status header (may be sent in some non-cache responses because NOSRV can happen for other reasons, but should always be present in responses served by cache-use)
http-response set-header X-Cache-Status HIT if !{ srv_id -m found }
http-response set-header X-Cache-Status MISS if { srv_id -m found }
# simple example cache for files
http-request set-var(txn.path) path
acl can_cache var(txn.path) -i -m end .png .jpg .jpeg .jpe .ico .webmanifest .xml .apng .bmp .webp .pjpeg .jfif .gif .mp4 .webm .mov .mkv .svg .m4a .aac .flac .mp3 .ogg .wav .opus .txt .pdf .sid
http-request cache-use basic_cache if can_cache
http-response cache-store basic_cache if can_cache
default_backend servers
cache basic_cache
total-max-size 2500
max-object-size 31457280
max-age 86400
backend servers
# optional (recommended) ssl, requires CA cert installed on proxy and signeed cert on backends, you can also use "ssl verify none" but ssl can then be trivially mitm'd
# default-server ssl verify required ca-file ca-certificates.crt sni req.hdr(Host)
# use server based on hostname
use-server %[req.hdr(host),lower,map(/etc/haproxy/map/backends.map)] if TRUE
backend bot_check_post_throttle
stick-table type ipv6 size 100k expire 60s store http_req_rate(60s)
backend hcaptcha
mode http
server hcaptcha hcaptcha.com:443
backend recaptcha
mode http
server recaptcha www.google.com:443