readme fix, improve, remove old shit cli and interaction diagram

This commit is contained in:
Thomas Lynch
2022-01-02 16:52:45 +11:00
parent 52da926ed2
commit b63daef8e1
3 changed files with 31 additions and 362 deletions

View File

@ -1,25 +1,22 @@
## HaProxy DDoS protection system PoC ## HAProxy DDoS protection system
A fork and further development of a proof of concept from https://github.com/mora9715/haproxy_ddos_protector, a haproxy configuration and lua scripts allowing a holding page where users solve a captcha (think cloudflare CDN). A fork and further development of a proof of concept from https://github.com/mora9715/haproxy_ddos_protector, a HAProxy configuration and lua scripts allowing a challenge-response page where users solve a captcha and/or proof-of-work.
Intended to stop bots, spam, probably some forms of ddos, etc. Intended to stop bots, spam, ddos, etc.
Some issues fixed and various improvements: Will soon™ be accompanied by a control panel allowing to manage clusters of servers with this installed. Allowing you to add/remove/edit domains, protection rules, blocked ips, backend server IPs, etc during runtime.
- Add a proof-of-work element to the bot-check page as an optional weaker but more user-friendly mode Improvements in this fork:
- Add more options to CLI for nocaptcha
- Add examples and support for .onion/tor using the haproxy PROXY protocol to provide some kind of "ip" discrimination of tor users (circuit identifiers)
- Add serving javascript files directly from haproxy with http-request return, so no extra backend is needed
- Improved the appearance of the challenge page
- Fix a lot of bugs
- Fix resolving domain of hcaptcha, no longer uses a hack
- Fix multiple security issues that could result in bypassing the captcha
- Fix challenge cookies lasting forever, they are now limited by a bucket duration on server side
- Ability to set values for domains (or domain+path!) to select off, pow, or captcha and override for paths over domains
#### Screenshot - Add a proof-of-work element, instead of only captcha.
- Add examples and support for .onion/tor using the HAProxy PROXY protocol to provide some kind of "ip" discrimination of tor users (circuit identifiers).
![captcha](img/captcha.png "captcha mode (pow done asynchronously in background)") - Use HAProxy http-request return to improve performance/caching for the challenge page, without an extra backend http server.
![nocaptcha](img/nocaptcha.png "no captcha mode") - Improved the appearance of the challenge page.
- Remove dns resolution hack, use proper backend address.
- Fix multiple security issues that could result in bypassing the captcha.
- Add a bucket duration for cookie validity, so valid cookies don't last forever.
- Ability to enable globally, per-domain or per-domain+path, with paths taking priority.
- Include dataplaneapi, to sync map files to disk if edited during runtime.
- Many bugfixes.
#### How to test #### How to test
@ -29,17 +26,22 @@ Add some env vars to docker-compose file:
- HCAPTCHA_SECRET - your hcaptcha secret key - HCAPTCHA_SECRET - your hcaptcha secret key
- CAPTCHA_COOKIE_SECRET - random string, a salt for captcha cookies - CAPTCHA_COOKIE_SECRET - random string, a salt for captcha cookies
- POW_COOKIE_SECRET - different random string, a salt for pow cookies - POW_COOKIE_SECRET - different random string, a salt for pow cookies
- RAY_ID - string to identify the haproxy node by - RAY_ID - string to identify the HAProxy node by
- BUCKET_DURATION - how long between bucket changes, invalidating cookies - BUCKET_DURATION - how long between bucket changes, invalidating cookies
- BACKEND_NAME - name of backend to build from hosts.map - BACKEND_NAME - name of backend to build from hosts.map
- SERVER_PREFIX - prefix of server names i.e. <prefix>n where n is the number, in server-template - SERVER_PREFIX - prefix of server names used in server-template
Add a domain name + backend IP to `haproxy/hosts.map` like:
```plain
localhost 127.0.0.1:81
```
Run docker compose: Run docker compose:
```bash ```bash
docker compose up docker compose up
``` ```
- visit *http://127.0.0.1* Visit http://localhost
DDoS-protection mode is enabled by default. DDoS-protection mode is enabled by default.
@ -47,35 +49,15 @@ DDoS-protection mode is enabled by default.
Before installing the tool, ensure that HAProxy is built with Lua support (in debian package and ubuntu recommended PPA, it is.) Before installing the tool, ensure that HAProxy is built with Lua support (in debian package and ubuntu recommended PPA, it is.)
- Copy haproxy config and make sure that `lua-load` directive contains absolute path to [register.lua](src/scripts/register.lua) - Copy [haproxy.cfg](haproxy/haproxy.cfg) to /etc/haproxy
- Edit the `lua-load` directive to be the absolute path to [register.lua](src/scripts/register.lua)
- Edit the paths of sha1.js and worker.js in the `http-request return` directive to the absolut path to the respective files in the haproxy/js folder
- Copy [dataplaneapi.hcl](haproxy/dataplaneapi.hcl) to /etc/haproxy
- Copy or link [scripts](src/scripts) to /etc/haproxy/scripts - Copy or link [scripts](src/scripts) to /etc/haproxy/scripts
- Copy or link [libs](src/libs) to /etc/haproxy/libs (or a path where Lua looks for modules). - Copy or link [libs](src/libs) to /etc/haproxy/libs (or a path where Lua looks for modules).
- Create `/etc/haproxy/ddos.map` for domains or paths with protection mode enabled - Copy the map files from the haproxy folder to /etc/haproxy
#### CLI #### Screenshot
The system comes with CLI. It can be used to manage protection global/per-domain and control nocaptcha mode.
Ensure that stat socket is configured in HaProxy for CLI support.
```bash ![captcha](img/captcha.png "captcha mode (pow done asynchronously in background)")
Usage: ddos-cli <command> [options] ![nocaptcha](img/nocaptcha.png "no captcha mode")
Command line interface to manage per-domain and global DDoS protection.
optional arguments:
-h, --help Show this help message and exit.
Commands:
Global management:
src/cli/ddos-cli global status Show status of global server ddos mode.
src/cli/ddos-cli global enable Enable global ddos mode.
src/cli/ddos-cli global disable Disable global ddos mode.
Domain management:
src/cli/ddos-cli domain list List all domains with ddos mode on.
src/cli/ddos-cli domain nocaptcha List all domains with nocaptcha mode on.
src/cli/ddos-cli domain status <domain> Get ddos mode status for a domain.
src/cli/ddos-cli domain enable <domain> Enable ddos mode for a domain.
src/cli/ddos-cli domain disable <domain> Disable ddos mode for a domain.
src/cli/ddos-cli domain mode <domain> Toggle nocaptcha mode for a domain.
```

View File

@ -1,32 +0,0 @@
@startuml
actor Browser as user
participant Proxy as proxy
participant "Captcha Provider" as captcha
participant "Backend" as backend
user -> proxy: Request /resource?foo=bar
activate proxy
proxy -> user: Redirect /{captcha_url}
deactivate proxy
user -> captcha: Submit challenge
activate captcha
captcha -> user: Pass challenge result hash
deactivate captcha
user -> proxy: Submit captcha form
activate proxy
proxy -> captcha: Validate challenge results
activate captcha
captcha -> proxy: Pass validation results
deactivate captcha
proxy -> user: Redirect /resource?foo=bar
deactivate proxy
user -> proxy: Request /resource?foo=bar
activate proxy
proxy -> backend: Request /resource?foo=bar
activate backend
backend -> proxy: Serve /resource?foo=bar
deactivate backend
proxy -> user: Serve /resource?foo=bar
deactivate proxy
@enduml

View File

@ -1,281 +0,0 @@
#!/usr/bin/env bash
HAPROXY_DDOS_DOMAINS_FILE="/etc/haproxy/ddos.map"
HAPROXY_NOCAPTCHA_DOMAINS_FILE="/etc/haproxy/no_captcha.map"
HAPROXY_GLOBAL_ACL="hdr_cnt"
HAPROXY_SOCKET="/var/run/haproxy.sock"
SOCAT="$(which socat)"
DOMAIN_REGEX='(?=^.{5,254}$)(^(?:(?!\d+\.)[a-zA-Z0-9_\-]{1,63}\.?)+(?:[a-zA-Z]{2,})$)'
_h_show_acl() {
local cmd
if [[ ${1} ]]; then
cmd="show acl #${1}"
else
cmd="show acl"
fi
echo "${cmd}" | ${SOCAT} ${HAPROXY_SOCKET} stdio
}
_h_add_acl() {
echo "add acl #${1} ${2}" | ${SOCAT} ${HAPROXY_SOCKET} stdio
}
_h_del_acl() {
echo "del acl #${1} ${2}" | ${SOCAT} ${HAPROXY_SOCKET} stdio
}
_h_show_map() {
local cmd
if [[ ${1} ]]; then
cmd="show map #${1}"
else
cmd="show map"
fi
echo "${cmd}" | ${SOCAT} ${HAPROXY_SOCKET} stdio
}
_h_add_map() {
echo "add map #${1} ${2} ${2}" | ${SOCAT} ${HAPROXY_SOCKET} stdio
}
_h_del_map() {
echo "del map #${1} ${2}" | ${SOCAT} ${HAPROXY_SOCKET} stdio
}
_help() {
/bin/cat <<EOF
Usage: $0 <command> [options]
Command line interface to manage per-domain and global DDoS protection.
optional arguments:
-h, --help Show this help message and exit.
Commands:
Global management:
$0 global status Show status of global server ddos mode.
$0 global enable Enable global ddos mode.
$0 global disable Disable global ddos mode.
Domain management:
$0 domain list List all domains with ddos mode on.
$0 domain nocaptcha List all domains with nocaptcha mode on.
$0 domain status <domain> Get ddos mode status for a domain.
$0 domain enable <domain> Enable ddos mode for a domain.
$0 domain disable <domain> Disable ddos mode for a domain.
$0 domain mode <domain> Toggle nocaptcha mode for a domain.
EOF
}
_ensure_domain_passed() {
if ! [[ ${1} ]]; then
echo "Error: 'domain' argument is required for this action"
_help
exit 1
elif ! echo "${1}" | grep -qP "${DOMAIN_REGEX}"; then
echo "Error: '${1}' is not a valid domain"
_help
exit 1
fi
}
_domain_list() {
local domain_acl_id
domain_acl_id=$(_h_show_acl | grep ${HAPROXY_DDOS_DOMAINS_FILE} | cut -d' ' -f1)
_h_show_acl "${domain_acl_id}" | cut -d' ' -f2
}
_nocaptcha_list() {
local nocaptcha_map_id
nocaptcha_map_id=$(_h_show_map | grep ${HAPROXY_NOCAPTCHA_DOMAINS_FILE} | cut -d' ' -f1)
_h_show_map "${nocaptcha_map_id}" | cut -d' ' -f2
}
_domain_status() {
local ddos_domains
local nocaptcha_domains
local global_ddos_acl_id
local global_ddos_status
ddos_domains="$(_domain_list)"
nocaptcha_domains="$(_nocaptcha_list)"
global_ddos_acl_id=$(_h_show_acl | grep ${HAPROXY_GLOBAL_ACL} | cut -d' ' -f1)
global_ddos_status=$(_h_show_acl "${global_ddos_acl_id}" | cut -d' ' -f2)
if echo "${ddos_domains}" | grep -q "^${1}$"; then
echo "DDoS-protection mode is enabled for ${1}"
else
echo "DDoS-protection mode is disabled for ${1}"
if [[ ${global_ddos_status} -eq 0 ]]; then
echo "ATTENTION: DDoS-protection mode is enabled globally"
fi
fi
if echo "${nocaptcha_domains}" | grep -q "^${1}$"; then
echo "Nocaptcha mode is enabled for ${1}"
fi
}
_domain_enable() {
local ddos_domains
local domain_acl_id
ddos_domains="$(_domain_list)"
if echo "${ddos_domains}" | grep -q "^${1}$"; then
echo "DDoS-protection mode is already enabled for ${1}"
exit 0
fi
domain_acl_id=$(_h_show_acl | grep ${HAPROXY_DDOS_DOMAINS_FILE} | cut -d' ' -f1)
_h_add_acl "${domain_acl_id}" "${1}" &>/dev/null
if ! grep -q "^${1}$" ${HAPROXY_DDOS_DOMAINS_FILE}; then
echo "${1}" >> ${HAPROXY_DDOS_DOMAINS_FILE}
fi
echo "DDoS-protection mode was enabled for ${1}"
}
_domain_changemode() {
local nocaptcha_domains
local domain_map_id
nocaptcha_domains="$(_nocaptcha_list)"
domain_map_id=$(_h_show_acl | grep ${HAPROXY_NOCAPTCHA_DOMAINS_FILE} | cut -d' ' -f1)
if echo "${nocaptcha_domains}" | grep -q "^${1}$"; then
_h_del_map "${domain_acl_id}" "${1}" &>/dev/null
if grep -q "^${1}$" ${HAPROXY_NOCAPTCHA_DOMAINS_FILE}; then
sed -i "/^${1}$/d" ${HAPROXY_NOCAPTCHA_DOMAINS_FILE}
fi
echo "Nocaptcha mode was disabled for ${1}"
exit 0
fi
_h_add_map "${domain_acl_id}" "${1}" &>/dev/null
if ! grep -q "^${1}$" ${HAPROXY_NOCAPTCHA_DOMAINS_FILE}; then
echo "${1}" >> ${HAPROXY_NOCAPTCHA_DOMAINS_FILE}
fi
echo "Nocaptcha mode was enabled for ${1}"
}
_domain_disable() {
local ddos_domains
local domain_acl_id
ddos_domains="$(_domain_list)"
if ! echo "${ddos_domains}" | grep -q "^${1}$"; then
echo "DDoS-protection mode is already disabled for ${1}"
exit 0
fi
domain_acl_id=$(_h_show_acl | grep ${HAPROXY_DDOS_DOMAINS_FILE} | cut -d' ' -f1)
_h_del_acl "${domain_acl_id}" "${1}" &>/dev/null
if grep -q "^${1}$" ${HAPROXY_DDOS_DOMAINS_FILE}; then
sed -i "/^${1}$/d" ${HAPROXY_DDOS_DOMAINS_FILE}
fi
echo "DDoS-protection mode was disabled for ${1}"
}
_global_status() {
local global_ddos_acl_id
local global_ddos_status
global_ddos_acl_id=$(_h_show_acl | grep ${HAPROXY_GLOBAL_ACL} | head -1 | cut -d' ' -f1)
global_ddos_status=$(_h_show_acl "${global_ddos_acl_id}" | cut -d' ' -f2)
if [[ ${global_ddos_status} -eq 0 ]]; then
echo "DDoS-protection mode is enabled globally"
else
echo "DDoS-protection mode is disabled globally"
fi
}
_global_enable() {
declare -a global_ddos_acl_ids
local global_ddos_status
global_ddos_acl_ids=($(_h_show_acl | grep ${HAPROXY_GLOBAL_ACL} | cut -d' ' -f1))
global_ddos_status=$(_h_show_acl "${global_ddos_acl_ids[0]}" | cut -d' ' -f2)
if [[ ${global_ddos_status} -eq 0 ]]; then
echo "DDoS-protection mode is already enabled globally"
exit 0
fi
for id in "${global_ddos_acl_ids[@]}"; do
_h_add_acl "${id}" 0 &>/dev/null
_h_del_acl "${id}" 1 &>/dev/null
done
echo "DDoS-protection mode was enabled globally"
}
_global_disable() {
declare -a global_ddos_acl_ids
local global_ddos_status
global_ddos_acl_ids=($(_h_show_acl | grep ${HAPROXY_GLOBAL_ACL} | cut -d' ' -f1))
global_ddos_status=$(_h_show_acl "${global_ddos_acl_ids[0]}" | cut -d' ' -f2)
if [[ ${global_ddos_status} -eq 1 ]]; then
echo "DDoS-protection mode is already disabled globally"
exit 0
fi
for id in "${global_ddos_acl_ids[@]}"; do
_h_add_acl "${id}" 1 &>/dev/null
_h_del_acl "${id}" 0 &>/dev/null
done
echo "DDoS-protection mode was disabled globally"
}
_handle_global_management() {
case ${1} in
status) _global_status;;
enable) _global_enable;;
disable) _global_disable;;
*) _help; exit 1;;
esac
}
_handle_domain_management() {
case ${1} in
list) _domain_list;;
nocaptcha) _nocaptcha_list;;
status)
_ensure_domain_passed "${2}"
_domain_status "${2}";;
enable)
_ensure_domain_passed "${2}"
_domain_enable "${2}";;
disable)
_ensure_domain_passed "${2}"
_domain_disable "${2}";;
mode)
_ensure_domain_passed "${2}"
_domain_changemode "${2}";;
*) _help; exit 1;;
esac
}
if ! [[ "${*}" ]]; then
_help
exit 1
fi
for i in "${@}"; do
case ${i} in
-h|--help) _help; exit 0;;
domain) MODE=DOMAIN; shift; break;;
global) MODE=GLOBAL; shift; break;;
*) _help; exit 1;;
esac
done
case ${MODE} in
DOMAIN) _handle_domain_management "${@}";;
GLOBAL) _handle_global_management "${@}";;
esac