====== haproxy ======
[[https://www.youtube.com/watch?v=6o0SWcBTwuQ|Introduction to haproxy]]
Reverse proxy и NLB 7-го и 4-го уровня.
==== Установка ====
sudo apt install --no-install-recommends software-properties-common
sudo add-apt-repository ppa:vbernat/haproxy-2.5 -y
sudo apt install haproxy=2.5.\*
https://www.haproxy.com/blog/how-to-install-haproxy-on-ubuntu/
==== Конфиг ====
Конфиг состоит из 4 основных частей:
- **global** - общие настройки
- **defaults** - значения по умолчанию для frontend и backend. Этих секций может быть несколько, и параметры будут применяться ко всем бэкендам и фронтендам после каждой.
- **frontend** - приём пользовательских запросов, прослушиваемый IP, проверка HTTP, выбор бэкенда
- **backend** - мониторинг серверов, балансировка, очередь
Есть ещё секция **listen** - её можно использовать как объединение frontend и backend. Обычно применяется для TCP. На примере HAProxy stats page:
frontend stats
bind *:8404
stats enable
stats uri /stats
stats refresh 10s
stats admin if { src 127.0.0.1 }
https://www.haproxy.com/blog/exploring-the-haproxy-stats-page/
Есть также секции:
- **resolvers** - настройка DNS (сервера тогда можно указывать как DNS-имя)
- **mailers** - configuring email notifications
- **peers** - синхронизация между узлами HAProxy.
**defaults** может быть в нескольких экземплярах - последующая сбрасывает настройки предыдущей. Положим, можно сначала задать настройки для TCP, потом сделать секции frontend и backend для TCP, а потом сделать defaults и всё остальное для HTTP.
**backend**\\
Самые популярные варианты балансировки:\\
balance roundrobin - просто циклический перебор\\
balance leastconn - выбор сервера с наименьшим числом соединений
cookie - засылается клиенту для сохранения настроек соединения - клиент продолжает общаться с тем же сервером до конца сессии.
default-server - можно перечислить опции для всех серверов, чтобы не писать одно и то же для каждого сервера
Простейший конфиг (frontend и backend)
frontend http
bind :80
acl test url_beg -i test
# или так:
# acl test path -i -m beg /test
use_backend test if test
# вариант записи в одну строку (anonymous or inline ACL)
# use_backend test if { path -i -m beg /test }
# редирект на Яндекс
acl yandex path -i -m beg /yandex
http-request redirect location https://yandex.ru if yandex
# во всех остальных случаях слать на root backend (типа else)
default_backend root
backend test
balance roundrobin
server web1 192.168.1.31:80
server web2 192.168.1.32:80
backend root
balance roundrobin
server web1 192.168.1.31:80
server web2 192.168.1.32:80
# Протестировать конфиг
haproxy -f /etc/haproxy/haproxy.cfg -c
HAProxy easily tells [[https://www.keepalived.org/manpage.html|keepalived]] about its state and copes very well with floating virtual IP addresses.
https://www.haproxy.com/blog/the-four-essential-sections-of-an-haproxy-configuration/\\
https://www.haproxy.com/blog/introduction-to-haproxy-acls/\\
http://www.haproxy.org/#docs
==== tune.ssl.default-dh-param ====
Предупреждение при проверке конфига, когда на фронтенде есть сертификат:
[WARNING] 012/103010 (81203) : parsing [/etc/haproxy/haproxy.cfg:38] : 'bind :443' :
unable to load default 1024 bits DH parameter for certificate '/etc/ssl/certs/company/cert.pem'.
, SSL library will use an automatically generated DH parameter.
[WARNING] 012/103010 (81203) : Setting tune.ssl.default-dh-param to 1024 by default, if your workload permits it you should set it to at least 2048. Please set a value >= 1024 to make this warning disappear.
Configuration file is valid
Решение:
sudo openssl dhparam -out /etc/haproxy/dhparams.pem 2048
sudo vi /etc/haproxy/haproxy.cfg
# Вставить в секцию global
ssl-dh-param-file /etc/haproxy/dhparams.pem
https://www.digitalocean.com/community/tutorials/haproxy-ssl-tls-warning-setting-tune-ssl-default-dh-param-to-1024-by-default
==== Конфиг для Exchange 2013 ====
OWA healthcheck page: https://mail.domain.ru/owa/healthcheck.htm
[[https://sysadminblogger.wordpress.com/2018/08/13/exchange-2013-2016-switching-from-zen-load-balancer-to-haproxy/|Exchange 2013/2016: Switching from Zen Load Balancer to HAProxy]]\\
[[https://discourse.haproxy.org/t/haproxy-and-outlook-2010-on-exchange-2016/4496/16|HAproxy and Outlook 2010 on Exchange 2016]] - Outlook не хотел подключаться без этих заголовков\\
[[https://www.haproxy.com/static/media/uploads/eng/resources/aloha_load_balancer_appnotes_0065_exchange_2013_deployment_guide_en.pdf|ALOHA Load-Balancer deployment guide for Microsoft Exchange 2013]] (PDF)\\
[[https://mangolassi.it/topic/17099/my-config-for-haproxy-and-exchange-2013|My config for HAPRoxy and Exchange 2013]]\\
[[https://www.haproxy.com/blog/microsoft-exchange-2013-load-balancing-with-haproxy/|Microsoft Exchange 2013 Load Balancing with HAProxy]]\\
[[https://bidhankhatri.com.np/system/haproxy-configuration-for-windows-exchange-server-2016-and-2019/|HAProxy configuration for Windows Exchange Server 2016/2019]]
++++ haproxy.cfg с одной из прошлых работ |
global
log 127.0.0.1 local0 info
maxconn 10000000
daemon
quiet
debug
tune.ssl.default-dh-param 2048
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 1600000ms
timeout client 1600000ms
timeout server 1600000ms
timeout check 1600000ms
stats enable
stats uri /stats
# в браузере вылезает окно с авторизацией перед входом
userlist basic-auth-list
user TEST insecure-password Pass123
frontend fe_http_all
bind *:80
mode http
maxconn 10000000
acl autodiscover url_beg /Autodiscover
acl autodiscover url_beg /autodiscover
# use_backend be_h_ex2013 if autodiscover
use_backend be_ex2013
frontend fe_https
mode http
maxconn 10000000
#bind *:443 ssl crt /etc/ssl/certs/final7.pem
bind *:443 ssl crt /etc/ssl/certs/exchange_certificate_and_key_nopassword.pem
acl Autodiscover url_beg /Autodiscover
acl autodiscover url_beg /autodiscover
acl bad_ip src 223.72.82.215 185.25.51.234 78.128.92.106 185.86.150.196 104.196.214.152
acl good_ip_ews src -f /etc/haproxy/good_ip_ews.lst
#acl legacy_host hdr_beg(host) -i legacy.
#acl mail_host hdr_beg(host) -i mail.
acl q_host hdr_beg(host) -i q.
#acl mapi url_beg /mapi
#acl rpc url_beg /rpc
#acl RPC url_beg /RPC
acl owa url_beg /owa
#acl eas url_beg /microsoft-server-activesync
#acl Eas url_beg /Microsoft-Server-Activesync
#acl ecp url_beg /ecp
acl ews url_beg /ews
acl EWS url_beg /EWS
acl calendar url_beg /owa/calendar/
acl Calendar url_beg /OWA/calendar/
use_backend be_ntpserv_owa if bad_ip
# use_backend be_secure_ex2013 if q_host owa
use_backend be_secure_ex2013 if q_host
use_backend be_ntpserv_owa if Calendar
use_backend be_ntpserv_owa if calendar
# use_backend be_cas01_ex2013 if owa
use_backend be_cas02_ex2013 if ews good_ip_ews # менять на активный be_cas0X_ex2013 for_change
use_backend be_cas02_ex2013 if EWS good_ip_ews # менять на активный be_cas0X_ex2013 for_change
use_backend be_ntpserv_owa if ews
use_backend be_ntpserv_owa if EWS
default_backend be_ex2013 # менять на активный be_cas0X_ex2013 for_change
#default_backend be_ex2007
frontend fe_imap
mode tcp
bind *:993
default_backend be_imap_ex2013
frontend fe_tls
mode tcp
bind *:143
default_backend be_tls_ex2013
#frontend fe_smtp
#mode tcp
#bind *:25
#default_backend be_smtp_ex2007
#backend be_ecp_ex2013
#maxconn 10000000
#mode http
#balance roundrobin
#server cas01 192.168.5.111:443 check
#server cas02 192.168.5.112:443 check
backend be_secure_ex2013
#acl q-auth http_auth(basic-auth-list)
#http-request auth realm cas01 unless q-auth
maxconn 10000000
mode http
balance roundrobin
server cas01 192.168.5.111:443 check ssl inter 15s verify required ca-file /usr/share/ca-certificates/certs/root2.pem maxconn 30000
server cas02 192.168.5.112:443 check ssl inter 15s verify required ca-file /usr/share/ca-certificates/certs/root2.pem maxconn 30000
backend be_ex2013
maxconn 10000000
mode http
balance roundrobin
server cas01 192.168.5.111:443 check ssl inter 15s verify required ca-file /usr/share/ca-certificates/certs/root2.pem maxconn 30000
server cas02 192.168.5.112:443 check ssl inter 15s verify required ca-file /usr/share/ca-certificates/certs/root2.pem maxconn 30000
backend be_cas01_ex2013
maxconn 10000000
mode http
balance roundrobin
server cas01 192.168.5.111:443 check ssl inter 15s verify required ca-file /usr/share/ca-certificates/certs/root2.pem maxconn 30000
backend be_cas02_ex2013
maxconn 10000000
mode http
balance roundrobin
server cas02 192.168.5.112:443 check ssl inter 15s verify required ca-file /usr/share/ca-certificates/certs/root2.pem maxconn 30000
backend be_ntpserv
maxconn 10000000
mode http
balance roundrobin
server ntpserv 192.168.5.184
backend be_ntpserv_owa
maxconn 10000000
mode http
balance roundrobin
server ntpserv88 192.168.5.184:88
backend be_imap_ex2013
mode tcp
balance roundrobin
server cas01 192.168.5.111:993
server cas02 192.168.5.112:993
backend be_tls_ex2013
mode tcp
balance roundrobin
server cas01 192.168.5.111:143
server cas02 192.168.5.112:143
#listenstats :7000
#stats enable
#stats uri /
#optionhttpclose
#stats auth admin:P@ssw0rd
++++
===== ACL =====
https://www.haproxy.com/blog/introduction-to-haproxy-acls/\\
https://www.youtube.com/watch?v=9ISPGye5MnU
ACLs - правила доступа, кто может получать доступ.
Примеры
# использовать бэкенд be_example, если заголовок host в запросе совпадает с example.com (-m[atch] dom[ain]), не учитывать регистр (-i).
use_backend be_example if { req.hdr(host) -i -m dom example.com }
# генерация имени бэкенда из запроса с помощью карты.
use_backend be_%[req.hdr(host),lower,map(/etc/haproxy/hosts.map)]
# редирект на https. unless = if !{}.
http-request redirect scheme https unless { ssl_fc }
# именованное правило
acl is_admin_range src 10.0.48.0/24
# запрет ходить на URL, начинающийся на /admin всем, кроме админского диапазона.
http-request deny if !is_admin_range { path_beg /admin/ }
# посылать на /login, если нет клиентского сертификата или через lua.
http-request redirect location /login unless { ssl_c_used } || { lua.is_auth_valid }
# не совсем acl, захват значения кол-ва запросов в логи, полезно для оценки и предварительных измерений (https://cbonte.github.io/haproxy-dconv/2.5/configuration.html#http-request%20capture).
http-request capture sc_http_req_rate(0) len 4
ACLs находятся в секциях фронтэнда (чаще всего), бекэнда или listen (совмещённый фронт и бэк, в основном используется для TCP). Выполняются обычно по порядку сверху вниз либо в порядке TCP connect -> TCP content -> HTTP request -> HTTP response. Нужно проверять конфигурацию и обращать внимание на предупреждения.
Линейные (inline) ACL состоит из действия (action), выборки (fetch) и необязательных конвертеров, флагов и т. д.
use_backend be_example if { req.hdr(host) -i -m dom example.com }
У именных (named) acl после имени идёт выборка
acl is_example req.hdr(host) -i -m dom example.com
use_backend be_example if is_example
==== Выборка (fetch) ====
Выборка получает данные, обычно из запроса или ответа. Полученная информация может быть передана конвертеру и затем посылается на сравнение (match). Некоторые выборки принимают аргументы, например, ''req.hdr(host)'', некоторые - нет ''ssl_fc_session_id''. Самые популярные выборки: [[https://cbonte.github.io/haproxy-dconv/2.5/configuration.html#7.3.6-url_param|url_param]], src (IP источника), req.hdr (заголовок источника), cook (выборка по имени кук, возвращает значение), rand (случайное число), nbsrv (возвращает кол-во активных серверов на указанном бэкенде), payload (тело запроса TCP, нужно для определения протокола, сравнение).
==== Конвертер ====
Конвертеры необязательны, идут за выборкой или за предыдущим конвертером, отделяются запятыми, например
# имя хоста в нижний регистр, взять первые 5 символов
req.hdr(host),lower,bytes(0,5)
Некоторые конвертеры также принимают аргументы. Популярные конвертеры: lower, bytes, hex, base64, map (лезет в карту "ключ-значение" и выдаёт значение), field (часть строки при заданном разделителе), mod (сравнение по модулю), regsub (регулярка).
==== Флаги ====
Флаги находятся между выборками/конвертерами и значениями.
use_backend be_example if { path -m beg /login }
Часто используются: -i (нечувствительность к регистру), -m (match), -f (смотреть в acl-файл), менее популярные: -n (запрет разрешения имени в DNS), -M (читать указанный файл как карту), -u (задать acl ID).
=== Сравнения ===
Варианты сравнения: beg, end, sub, dom, len, reg, found.
# reg
use_backend be_static if { path -m reg /login/[a-z]+/failed/.* }
# reg может сочетаться с -f
use_backend be_static if { path -m reg -f /etc/haproxy/static_patterns.acl }
# регулярные выражения ресурсоёмки, надо следить за производительностью
# found - если заголовка host нет, отклонить запрос
http-request deny unless { req.hdr(host) -m found }
=== Выборки со встроенным сравнением ===
Выборки со встроенным сравнением: ''path_beg = path -m beg'', path_end, path_reg, path_sub и т. д. Не все выборки имеют такие варианты. Часто используются beg, dir, dom, end, len, reg, sub.
=== Сравнения с числами ===
Сравнения с числами - eq, ge, gt, le, lt.
http-request deny if { sc_http_req_rate(0) gt 50 }
# диапазон разделён двоеточием, например 10:30
acl ssl req.ssl_ver 3:3.1
==== Значения ====
Значения должны быть статическими, т. е., не другими выборками/конвертерами/переменными.
# Строки, их может быть несколько
use_backend be_static if { path_beg /images /icons }
# ip или их диапазоны
http-request deny unless { src 192.168.0.0/24 127.0.0.0/8 }
# Двоичные данные
use_backend be_multiplayer if { payload(0,4) -m bin 3f021bca }
==== Действия ====
Собственно, для чего и затеваются ACLs. Одно из наиболее популярных - http-request и use_backend.
# редирект
http-request redirect scheme https code 301 unless { ssl_fc }
http-request redirect location /login unless { req.cook(sessionid) -m found }
# переделать путь на лету - не редирект, т. е., клиент не узнает, что бэкенд получает другой путь
http-request set-path /legacy%[path] if {req.hdr(host) -i old.example.com }
# добавить заголовок из stick table
http-request set-header x-pages-viewed %[sc_gpc0_rate(0)]
# использовать кэш
http-request cache-use static if {path_beg /static/ }
# отправлять на бэкенд be_example в соответствии с заголовком host
use_backend be_example if { req.hdr(host) -i -m dom www.example.com }
# динамически создавать имя бэкенда из заголовка в нижнем регистре
use_backend be_%[req.hdr(host),lower]
# дополнительно задействовать карту
use_backend be_%[req.hdr(host),lower,map_end(/etc/haproxy/hosts.map,default)]
Порядок срабатывания действий в соответствии с фазами соединения:
* tcp-request connection (на раннем этапе полезно отшибать DoS-атаки с кучей соединений, чтобы не тратить ресурсы на их обработку)
* tcp-request content (используемый протокол, режим)
* http-request (например, добавить заголовки, изменить путь перед передачей запроса клиента на бэкенд)
* http-response (например, добавить заголовки перед отправкой ответа бэкенда клиенту)
''use_backend'' может быть использован на любом этапе и останавливает обработку.\\
''tcp-request accept'' или ''http-request accept'' останавливают обработку на своих фазах.
==== Переменные ====
Не требуются, если только не нужно делать чего-то сложного. Некоторые случаи, когда они используются:
* Пути или заголовки из запроса на этапе ответа
* В выборках/lua, которые принимают значения переменных (например, concat)
* Задание записи в карте с sessionid cookie на основe заголовка ответа: http-request set-var(txn.sessionid) req.cook(sessionid)
use_backend %[req.cook(sessionid),map(/etc/haproxy/sessions.map)] if { req.cook(sessionid),map(/etc/haproxy/sessions.map) -m found }
http-response set-map(/etc/haproxy/sessions.map) var(txn.sessionid)
res.hdr(x-sessionid-backend) if { res.hdr(x-sessionid-backend) -m found }
Как их использовать
* В http-request/tcp-request content/http-response - ''set-var([scope].[name]) value''
* Scope может быть txn (самый частый случай), proc, sess, req, res
* Name может состоять из чисел, букв, точек и подчёркиваний.
* Во всех случаях переменная будет иметь имя **txn.name**, никогда как просто **name**
* Значение переменной извлекается в выборке как ''var(txn.name)''
* В LUA - ''%%txn:set_var("txn.name","value")%%'' и ''%%txn:get_var("txn.name")%%''
==== Карты и ACL-файлы ====
ACL-файлы - это просто список для сравнения, а карта при совпадении значения при сравнении возвращает другое значение из второй колонки.
* Все они являются файлами на диске, который читаются на этапе запуска
* ACL-файлы имеют одну колонку, например, ''192.168.1.0/24'', карты - две: ''192.168.1.0/24 admin_net''
* Все они могут быть прочитаны и изменены через runtime API:
* ''add acl'', ''del acl'', ''show acl''
* ''add map'', ''del map'', ''show map'', ''set map''
* Все они могут быть изменены через модуль ''lb_update'' (в версии enterprise)
=== ACL-файлы ===
* Одна строка на образец
* Образец может быть IP-адресом (диапазоном), строкой или регуляркой
* Может быть использован с режимами сравнения ''-m beg'', ''-m end'', ''-m reg''
* Используется с помощью ключа ''-f''http-request deny if { path_beg /admin/ } !{ src -f /etc/haproxy/admins.acl }
== Действия и ACL-файлы ==
* http-request add-acl
* http-response add-acl
* http-request add-acl
* http-response add-acl
Таким образом, можно сделать так, чтобы запросы с какого-нибудь сервера автоматически правили ACL-файлы, например, внося туда IP-адреса для блокировки.
http-request add-acl(/etc/haproxy/block.acl) %[url_param(ip)] if { src 10.0.0.0/8 } url_param(action) add
http-request add-acl(/etc/haproxy/block.acl) %[url_param(ip)] if { src 10.0.0.0/8 } url_param(action) remove
=== Карты ===
* Используются через "конвертер карт"
* Подбирает образец, например ''src,map(/etc/haproxy/ip_types.map''
* Возвращает результат поиска
* Сравнивать можно практически любым способом - beg, dir, dom, end, len, reg, sub
* Действия http-request/response содержат варианты set-map, add-map, del-map
* Используются для:
* Сопоставления имён хостов бэкендам
* Хранения лимитов для ключей
===== Карты (maps) =====
https://www.haproxy.com/blog/introduction-to-haproxy-maps/\\
https://www.youtube.com/watch?v=M0-08Hrn86E
Нужны для
* Направления трафика на бэкенды
* GeoIP
* API key
* Лимитов для доменов/путей
Карта выглядит примерно так:
# key value
example.com be_example
haproxy.com be_haproxy
test.ru be_test
frontend fe_main
bind :443 ssl crt /etc/ssl/certs/main/
# Взять хост из заголовка, перевести в нижний регистр, искать значение в карте.
# Если значения нет, использовать значение по умолчанию (здесь: be_static)
use_backend %[req.hdr(host),lower,map_dom(/etc/haproxy/maps/hosts.map,be_static)]
==== map() ====
* На вход в конвертер карты подаётся образец, который нужно найти, например, ''src,map(/etc/haproxy/ips.map)''. Конвертер он потому, что, в отличие от обычной выборки, он значение даёт не сам, а ищет его в стороннем файле.
* Обязательным значением является путь к файлу карты.
* Необязательный аргумент - значение по умолчанию.
* Возвращает значение из второй колонки, если найдено совпадение строки в первой.
* Варианты сравнения - str, beg, sub, dir, dom, end, reg, regm, int, ip
==== Обновление карты ====
Так как haproxy читает файлы только во время инициализации, нужен метод для обновления карт без перезапуска haproxy.
Все методы не обновляют самих файлов карт на диске! Чтобы обновлять сами файлы, нужно отдельно запускать show map и дамп в файл (например, в кроне).
Первый метод - модуль LB-Update (только для версии enterprise). Его легко настраивать, он хорошо работает в кластерах.
dynamic-update
update id /etc/hapee-1.8/maps/sample.map url http://10.0.0.1/sample.map delay 300s
Второй метод - через сокет (см. https://www.haproxy.com/blog/dynamic-configuration-haproxy-runtime-api/).
echo "show map /etc/haproxy/domains.map" |socat stdio /var/run/haproxy/admin.sock
echo "clear map /etc/haproxy/domains.map" |socat stdio /var/run/haproxy/admin.sock
echo "del map /etc/haproxy/domains.map example.com" |socat stdio /var/run/haproxy/admin.sock
# add map не проверяет, есть ли добавляемое уже в карте, есть риск получить дубль,
# поэтому надо сочетать это в скрипте с show map, чтобы проверить наличие до добавления
echo "add map /etc/haproxy/domains.map example.com be_example" |socat stdio /var/run/haproxy/admin.sock
echo "set map /etc/haproxy/domains.map example.com be_example" |socat stdio /var/run/haproxy/admin.sock
При использовании nbproc у каждого процесса haproxy свой взгляд на файлы карт. В отличие от модуля lb-update здесь у каждого процесса свой сокет, и нужно обновлять карты для каждого процесса. С версии 1.9 используется усовершенствованный nbthread, позволяющий работать без потерь производительности при нескольких процессах haproxy.\\
https://www.haproxy.com/blog/multithreading-in-haproxy/
Третий - через ACL (неприменимо, если используется nbproc)
frontend fe_main
bind :80
# при запросе определённого url из определённой сети
acl in_network src 192.168.122.0/24
acl is_map_add path_beg /map/add
# задать запись в карте (в памяти)
http-request set-map(/etc/haproxy/maps/hosts.map) %[url_param(domain)] %[url_param(backend)] if is_map_add in_network
# не передавать этот запрос на бэкенд
http-request deny deny_status 200 if is_map_add in_network
# использовать бэкенд с картой при всех прочих запросах
use_backend %[req.hdr(host),lower,map(/etc/haproxy/maps/hosts.map)]
==== Примеры ====
=== Paths rate limit ===
Карта:
/api/routeA 40
/api/routeB 20
frontend api_gateway
bind :80
default_backend api_servers
# Set up stick table to track request rates
stick-table type binary len 8 size 1m expire 10s store http_req_rate(10s)
# Track client by base32+src (Host header + URL path + src IP)
http-request track-sc0 base32+src
# Check map file to get rate limit for path
http-request set-var(req.rate_limit) path,map_beg(/etc/haproxy/maps/rates.map)
# Client's request rate is tracked
http-request set-var(req.request_rate) base32+src,table_http_req_rate(api_gateway)
# Subtract the current request rate from the limit
# If less than zero, set rate_abuse to true
acl rate_abuse var(req.rate_limit),sub(req.request_rate) lt 0
# Deny if rate abuse
http-request deny deny_status 429 if rate_abuse
=== Чёрный список ===
Пример файла ACL.
acl is_admin_ip src 192.168.122.0/24 127.0.0.0/8
# добавляем-удаляем значения с админских диапазонов
http-request add-acl(/etc/haproxy/blacklist.acl) %[url_param(ip)] if is_admin_ip { path_beg /blacklist/add }
http-request del-acl(/etc/haproxy/blacklist.acl) %[url_param(ip)] if is_admin_ip { path_beg /blacklist/del }
# не пробрасывать никуда, если правится чёрный список
http-request deny deny_status 200 if is_admin_ip { path_beg /blacklist/ }
# блочить, собственно, значения из списка
http-request deny if ( src -f /etc/haproxy/blacklist.acl }
===== Stick tables =====
https://www.haproxy.com/blog/introduction-to-haproxy-stick-tables/\\
https://www.youtube.com/watch?v=syPkNnOXS8k
Хранилище "ключ-значение". Ключом может быть IP, Integer, String, Binary.\\
Популярные значения - conn_cur, conn_rate, http_req_rate, http_err_rate, server_id.
Используются для
* Привязки клиента к серверу
* Session cookies
* SSL session ID
* Исходный IP
* Лимитирования
* GET/POST floods
* Login brute force
* Web scraping
* API key usage
* Статистики/Логирования
Таблица может быть **только одна** в секции frontend/backend/listen.\\
Данные в неё добавляются с помощью действия ''track-sc0'' в командах tcp-request connect, tcp-request content, http-request, http-response.
В примере используется пустой бэкенд с таблицей, на который идёт ссылка как на таблицу из фронтенда.
backend st_src_global
stick-table type ip size 1m expire 10s store http_req_rate(10s)
frontend fe_main
bind *:80
http-request track-sc0 src table st_src_global
http-request deny if { sc_http_req_rate(0) gt 10 }
==== Multiprocessing ====
* Если используется nbproc (версия 1.8 и старее), то у каждого процесса будет свой набор таблиц.
* Если используется nbthread (версия 1.9 и новее), то все процессы используют один и тот же набор таблиц.
==== Синтаксис ====
backend st_ip
# stick-table тип <ключ> размер <кол-во строк> истекает <время> хранить <значение,значение,значение>
stick-table type ip size 1m expire 10s store http_req_rate(10s),conn_cur,gpc0
* За типами string и binary идёт параметр len (длина) - это число байт для захвата, если входное значение больше, оно урезается.
* Размер - кол-во строк в таблице (здесь 1 млн), нужен, чтобы не забить всю память. Из практических соображений не нужно задавать слишком много значений, т. к. может понадобиться сделать ещё несколько таблиц. На одну строку нужно 50 байт + размер ключа + размер значений.
* Истечение срока хранения - максимальное время, прошедшее с момента добавления, обновления или сравнения записи в таблице, после истечения запись удаляется. В случае с таблицей, хранящей частоту запросов (rate), срок хранения должен равняться периоду изменения частоты запросов, чтобы срок хранения и окончание периода измерения совпадали.
==== Хранение данных ====
http-request track-sc0 src table st_ip
* sc0 - sticky counter 0
* src - выборка, ключ для отслеживания
* Частота запросов/сканирование: src, base32 (IP + URL), req.hdr(Authorization)
* Статистика: req.hdr(host), ssl_fc_protocol, path
* Привязка: src, req.cook(sessionid)
* table - какую таблицу использовать. Этот параметр не нужен, если таблица находится в той же секции.
В рамках одной сессии нужен уникальный номер sc для каждой записи.
Здесь нужны разные номера, т. к. они активны для каждой сессии:
http-request track-sc0 src table st_src
http-request track-sc1 base32 table st_base32
А здесь - нет, т. к. они никогда не пересекаются:
http-request track-sc0 src st_src_get if METH_GET
http-request track-sc0 src st_src_post if METH_POST
* Номер задаётся флагом компиляции ''MAX_SESS_STKCTR'', в HAPEE 12 номеров.
* Номер sc используется потом в аргументах выборки, например, ''track-sc0'' -> ''st_http_req_rate(0)''
==== Использование значений ====
1) С помощью выборки. Выборка, ссылающаяся на счётчик (sc), чаще всего имеет префикс sc_, и соответствующий номер как аргумент.
http-request deny if { sc_http_req_rate(0) gt 3 }
Популярные выборки: sc_http_req_rate, sc_conn_cur, sc_conn_rate.
2) С помощью конвертера, если не задействованы счётчики. На входе - ключ, аргумент конвертера - имя таблицы.
http-request deny if { src,table_http_req_rate(st_src) gt 3 }
Применяется для таблиц, если используется peers.
==== Просмотр содержимого ====
С помощью API-запросов. В версии Enterprise есть веб-интерфейс.
echo "show table st_src_global" |socat stdio /var/haproxy/admin.sock
# Обновлять каждые 5 секунд
watch -n 5 'echo "show table st_src_global" |socat stdio /var/run/haproxy/admin.sock'
==== Примеры ====
Постоянное подключение к одному из серверов (привязка) + peers
# Протокол peers позволяет передавать данные из таблицы на все сервера-партнёры
peers mypeers
# Имя партнёра должно совпадать с его hostname, либо с заданным в секции global его конфигурации haproxy
peer haproxy1 192.168.1.11:10000
peer haproxy2 192.168.1.12:10000
backend mybackend
mode tcp
balance roundrobin
# Здесь нет указания значений, т. к. server ID добавляется автоматически
stick-table type ip size 20k peers mypeers
# К чему привязываться при закреплении пользователя к серверу
stick on src
server srv1 192.168.1.101:80
server srv2 192.168.1.102:80
Анти-флуд
backend st_src_global
stick-table type ip size 1m expire 10s store http_req_rate(10s)
frontend fe_main
bind *:80
http-request track-sc0 src table st_src_global
http-request deny if { sc_http_req_rate(0) gt 10 }
Web scraping - в этом примере блокируются те IP, которые зашли на более чем 15 разных URL за последние 24 часа.
backend per_ip_and_url_rates
stick-table type binary len 8 size 1m expire 24h store http_req_rate(24h)
backend per_ip_rates
stick-table type ip size 1m expire 24h store gpc0,gpc0_rate(30s)
frontend fe_main
bind *:80
http-request track-sc0 src table per_ip_rates
http-request track-sc1 url32+src table per_ip_and_url_rates unless { path_end .css .js .png .jpeg .gif }
acl exceeds_limit sc_gpc0_rate(0) gt 15
# Увеличить gpc0, если http_req_rate = 1 (он равен единице, когда кто-то запрашивает url первый раз)
# Если url запрашивается не в первый раз, то http_req_rate будет больше 1, и это не сработает
http-request sc-inc-gpc0(0) if { sc_http_req_rate(1) eq 1 } !exceeds_limit
http-request deny if exceeds_limit
default_backend web_servers
===== DDoS Attack and Bot Protection =====
https://www.haproxy.com/blog/application-layer-ddos-attack-protection-with-haproxy/\\
https://www.youtube.com/watch?v=-WcGDlX1liY\\
https://www.haproxy.com/blog/four-examples-of-haproxy-rate-limiting/
===== Логи =====
https://www.youtube.com/watch?v=hZ9DQKyMce4
# Дата сервер процесс[PID]: IP-адрес:порт [время запроса] фронтенд бекэнд/сервер тай/мин/ги/зап/роса код_статуса размер_запроса куки(- -) состояние_при_завершении(----)
Mar 10 11:13:42 vmls-haproxy1 haproxy[919886]: 77.232.165.18:2437 [10/Mar/2022:11:13:42.511] fe_web~ be_waf/vmls-waf 0/0/0/15/15 404 399 - - ---- 219/219/83/17/0 0/0 "GET https://www.domain.ru/common/img/uploaded/articles/cdhem/6-1.jpg HTTP/2.0"
Состояние при завершении, расшифровка: https://cbonte.github.io/haproxy-dconv/2.5/configuration.html#8.5
===== Проверки =====
https://www.haproxy.com/blog/how-to-enable-health-checks-in-haproxy/
===== Кластер =====
Active-Passive: 1 виртуальный IP.
{{:service:pasted:20220212-121707.png?600}}
Active-Active: 2 виртуальных IP, на входе DNS Round Robin.
{{:service:pasted:20220212-121718.png?600}}
==== Синхронизация конфигурации ====
Сначала нужно [[service:ssh#haproxy|настроить ssh]].
Синхронизация между двумя нодами, rsync работает в режиме archive и update, т. е., обновления существующих файлов.\\
Выдаёт статистику, которая парсится по слову ''transferred:'' и потом удаляется всё, кроме числового значения.\\
Помимо каталога с конфигурацией, синхронизируется каталог с сертификатами. Так как сертификаты выпускает только одна нода,\\
строку с переменной ''certs'' на ней нужно закомментировать. Чтобы не было ошибки при сложении в ''all'', подставляется 0 как значение по умолчанию.\\
Если были скопированы какие-то файлы, HAProxy перечитывает конфигурацию.
#!/bin/bash
rsyncConfig() {
int=`rsync -au --stats $1 $2 |grep transferred:`
echo ${int##*:}
}
config=`rsyncConfig vmls-haproxy1:/etc/haproxy/ /etc/haproxy`
certs=`rsyncConfig vmls-haproxy1:/etc/ssl/certs/companyname/ /etc/ssl/certs/companyname`
all=$(( ${config:-0} + ${certs:-0} ))
if [[ $all -ne 0 ]]
then
echo "$all files transferred, reloading HAProxy..."
systemctl reload haproxy
echo "HAProxy has been reloaded"
else
echo "$all files transferred, no need to do anything"
fi
Добавить в cron
echo -e "\n# Sync HAProxy config\n*/5 *\t* * *\troot\t/scripts/sync-config.sh" >> /etc/crontab
===== Видео =====
[[https://www.youtube.com/watch?v=CHdT3XA3JP8|HAProxy 2.6 - обзор новых функций]]