From a4b4e8454482f231fbd5cb91be8837c96e0d3a30 Mon Sep 17 00:00:00 2001 From: Eugene Prodan Date: Fri, 11 Jun 2021 22:14:43 +0300 Subject: [PATCH] feat: added CLI to manage ddos protection system --- README.MD | 42 ++++++- haproxy/domains_under_ddos.txt | 2 + haproxy/haproxy.cfg | 4 +- src/cli/ddos-cli | 197 +++++++++++++++++++++++++++++++++ src/cli/ddos_cli.sh | 27 ----- 5 files changed, 239 insertions(+), 33 deletions(-) create mode 100755 src/cli/ddos-cli delete mode 100755 src/cli/ddos_cli.sh diff --git a/README.MD b/README.MD index 540046a..969a2b9 100644 --- a/README.MD +++ b/README.MD @@ -1,19 +1,53 @@ ## HaProxy DDoS protection system PoC -If there is an unusual HTTP requests flow to a specific domain, the system detects it and triggers DDoS protection mode. -Each new client will be first forced to complete hCaptcha, before proceeding to the website. +The system provides functionality to protect certain (or all) resources on HaProxy from L7 DDoS attacks. +It works by requiring a user to have a specific cookie issued after successful captcha completion. If a user does not have the cookie, he gets redirected to a special captcha page. -##### How to test +It is by no means a cure for all ills, but should help you mitigate a moderate DDoS attack without disrupting the service. + +#### How it works + +![alternative text](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/mora9715/haproxy_ddos_protector/master/docs/interaction_diagram.txt) + +#### How to test - export hcaptcha sitekey and secret: ```bash export HCAPTCHA_SITEKEY=xxxXXxxx export HCAPTCHA_SECRET=xxxXXxxx ``` +They can be obtained after creating a free account on https://www.hcaptcha.com/ + - run docker compose: ```bash docker compose up ``` -- visit *http://127.0.0.1/captcha* \ No newline at end of file +- visit *http://127.0.0.1* + +For demostration purposes DDoS-protection mode was enabled by default. + + +#### CLI +The system comes with CLI. It can be used to manage global and per-domain protection: +```bash +Usage: ./ddos-cli [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: + ./ddos-cli global status Show status of global server ddos mode. + ./ddos-cli global enable Enable global ddos mode. + ./ddos-cli global disable Disable global ddos mode. + + Domain management: + ./ddos-cli domain list List all domains with ddos mode on. + ./ddos-cli domain status Get ddos mode status for a domain. + ./ddos-cli domain add Enable ddos mode for a domain. + ./ddos-cli domain del Disable ddos mode for a domain. +``` diff --git a/haproxy/domains_under_ddos.txt b/haproxy/domains_under_ddos.txt index e69de29..b735d48 100644 --- a/haproxy/domains_under_ddos.txt +++ b/haproxy/domains_under_ddos.txt @@ -0,0 +1,2 @@ +poge.com +domain.com diff --git a/haproxy/haproxy.cfg b/haproxy/haproxy.cfg index 84ab5b7..d1b9eee 100644 --- a/haproxy/haproxy.cfg +++ b/haproxy/haproxy.cfg @@ -19,8 +19,8 @@ frontend http-in acl captcha_passed var(txn.captcha_passed) -m bool acl on_captcha_url path -m beg /captcha - http-request lua.hcaptcha-redirect if !{ path -m beg /captcha } - http-request use-service lua.hcaptcha-view if { path /captcha } + http-request use-service lua.hcaptcha-view if on_captcha_url + http-request lua.hcaptcha-redirect if !on_captcha_url ddos_mode_enabled OR domain_under_ddos http-request redirect location /captcha?%[capture.req.uri] code 301 if !captcha_passed !on_captcha_url ddos_mode_enabled OR domain_under_ddos default_backend servers diff --git a/src/cli/ddos-cli b/src/cli/ddos-cli new file mode 100755 index 0000000..93aac52 --- /dev/null +++ b/src/cli/ddos-cli @@ -0,0 +1,197 @@ +#!/usr/bin/env bash + +HAPROXY_DDOS_DOMAINS_FILE="/usr/local/etc/haproxy/domains_under_ddos.txt" +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() { + if [[ ${1} ]]; then + local cmd="show acl #${1}" + else + local 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 +} + +_help() { +/bin/cat < [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 status Get ddos mode status for a domain. + $0 domain add Enable ddos mode for a domain. + $0 domain del Disable ddos 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=$(_h_show_acl | grep ${HAPROXY_DDOS_DOMAINS_FILE} | cut -d' ' -f1) + _h_show_acl ${domain_acl_id} | cut -d' ' -f2 +} + +_domain_status() { + local ddos_domains="$(_domain_list)" + local global_ddos_acl_id=$(_h_show_acl | grep ${HAPROXY_GLOBAL_ACL} | cut -d' ' -f1) + local 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 +} + +_domain_add() { + local ddos_domains="$(_domain_list)" + + if echo "${ddos_domains}" | grep -q "^${1}$"; then + echo "DDoS-protection mode is already enabled for ${1}" + exit 0 + fi + + local 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_del() { + local ddos_domains="$(_domain_list)" + + if ! echo "${ddos_domains}" | grep -q "^${1}$"; then + echo "DDoS-protection mode is already disabled for ${1}" + exit 0 + fi + + local 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=$(_h_show_acl | grep ${HAPROXY_GLOBAL_ACL} | cut -d' ' -f1) + local 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() { + local global_ddos_acl_id=$(_h_show_acl | grep ${HAPROXY_GLOBAL_ACL} | cut -d' ' -f1) + local 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 already enabled globally" + exit 0 + fi + + _h_add_acl ${global_ddos_acl_id} 0 &>/dev/null + _h_del_acl ${global_ddos_acl_id} 1 &>/dev/null + echo "DDoS-protection mode was enabled globally" +} + +_global_disable() { + local global_ddos_acl_id=$(_h_show_acl | grep ${HAPROXY_GLOBAL_ACL} | cut -d' ' -f1) + local global_ddos_status=$(_h_show_acl ${global_ddos_acl_id} | cut -d' ' -f2) + + if [[ ${global_ddos_status} -eq 1 ]]; then + echo "DDoS-protection mode is already disabled globally" + exit 0 + fi + + _h_add_acl ${global_ddos_acl_id} 1 &>/dev/null + _h_del_acl ${global_ddos_acl_id} 0 &>/dev/null + 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;; + status) + _ensure_domain_passed ${2} + _domain_status $2;; + add) + _ensure_domain_passed ${2} + _domain_add $2;; + del) + _ensure_domain_passed ${2} + _domain_del $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 diff --git a/src/cli/ddos_cli.sh b/src/cli/ddos_cli.sh deleted file mode 100755 index 1caa35f..0000000 --- a/src/cli/ddos_cli.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash - -_help() { -/bin/cat < [options] - -Show help screen and exit. - -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 status Get ddos mode status for a domain. - $0 domain add Enable ddos mode for a domain. - $0 domain del Disable ddos mode for a domain. -EOF -} -if ! [[ ${@} ]]; then - _help -fi \ No newline at end of file