Так же будет переосмысление предыдущих статей на тему маршрутизации VLESS+TLS VLESS-REALITY
Данная статья так же будет объединением предыдущих статей про маршрутизацию REALITY методом steal-oneself(укради себя) и переосмысляет их. Первые три можно прочитать тут 1 2 3. Перейдем на unix socket взамен использования портов, установим панель 3X-UI на сервер без докера от пользователя xray, подключим подписку. Так же дам пример как через Haproxy развернуть сертификат для ocserv или любого другого сервиса для сквозной маршрутизации.
Версии используемого ПО Ubuntu 24.04 LTS и Haproxy 2.8 из репозитория, конфиг подготовлен для этой версии, на версиях выше может потребоваться доработка, версия XCA 2.8, на 2.9 идентично.
В первой части статьи установим Haproxy настроим выдачу сертификатов без перезапуска Haproxy через acme.sh, настроим базовую маршрутизацию, базово подготовим Apache2 для работы за обратным прокси, подготовим УЦ, настроим mTLS, и встроенный экспортер Prometheus в Haproxy.
Часть 1. Базовый конфиг и сертификаты LE
Работаю от root командой sudo su
Обновим список пакетов и обновим систему:apt update && apt upgrade -y
Установим необходимое ПО:apt install -y haproxy htop socat netcat-traditional apache2
Поменяем стандартный 80 порт apache2 на 8080 и отключим модули SSL:
Отрываем конфиг nano /etc/apache2/ports.conf
и приводим к виду как на примере ниже
Listen 8080
#<IfModule ssl_module>
# Listen 443
#</IfModule>
#<IfModule mod_gnutls.c>
# Listen 443
#</IfModule>
Перезапускаем apache2 systemctl restart apache2
Установим acme.sh и подготовим директорию для сертификатов.
Создадим пользователя acme:adduser --system --disabled-password --disabled-login --home /var/lib/acme --quiet --force-badname --group acme
Добавим его в группу haproxy:adduser acme haproxy
Создадим директорию для файлов acme:mkdir /usr/local/share/acme.sh/
Перейдем в tmp загрузим acme.sh перейдем в директорию:cd /tmp/ && git clone https://github.com/acmesh-official/acme.sh.git && cd acme.sh/
Установим acme.sh без cron в созданную ранее директорию:./acme.sh --install --no-cron --no-profile --home /usr/local/share/acme.sh
Сделаем симлинк для удобства: ln -s /usr/local/share/acme.sh/acme.sh /usr/local/bin/
Изменим права на директорию acme.sh: chmod 755 /usr/local/share/acme.sh/
Создадим директорию для хранения сертификатов:mkdir /etc/haproxy/certs
Изменим владельца:chown haproxy:haproxy /etc/haproxy/certs
Выставим права:chmod 770 /etc/haproxy/certs
Теперь создадим аккаунт в acme.sh, далее все действия выполняются от пользователя acme:sudo -u acme -s
acme.sh --register-account --server letsencrypt -m example@mail.com
Изменим УЦ для выдачи по умолчанию ZeroSSL не работает с RU:acme.sh --set-default-ca --server letsencrypt
Выполним acme.sh --update-account
Полученный ACCOUNT_THUMBPRINT нам потребуется далее для настройки Haproxy.
Завершим работу с пользователем командой exit
Приступим к подготовке конфигурации Haproxy
Сотрем имеющуюся конфигурацию командой > /etc/haproxy/haproxy.cfg
Откроем файл nano /etc/haproxy/haproxy.cfg
Настроим секцию Global, в замен "*ACCOUNT_THUMBPRINT*" вставьте полученный ранее отпечаток на стадии acme.sh --update-account, уровень безопасности выберите сами, для совместимости я использую tls1.2 на фронтенде отдельно можно установить tls1.3
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /var/run/haproxy/admin.sock level admin mode 660
setenv ACCOUNT_THUMBPRINT '*ACCOUNT_THUMBPRINT*' # поменять на полученный из acme
stats timeout 30s
user haproxy
group haproxy
daemon
# https://ssl-config.mozilla.org/
# Улучшенная безопастность и совместимость со старыми браузерами ( Intermediate ) Supports Firefox 27, Android 4.4.2, Chrome 31, Edge, IE 11 on Windows 7, Java 8u31, OpenSSL 1.0.1, Opera 20, Safari 9
ssl-default-bind-curves X25519:prime256v1:secp384r1
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:DHE-RSA-CHACHA20-POLY1305
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-bind-options prefer-client-ciphers ssl-min-ver TLSv1.2 no-tls-tickets
ssl-default-server-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:DHE-RSA-CHACHA20-POLY1305
ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-server-options ssl-min-ver TLSv1.2 no-tls-tickets
# TLS 1.3 современная безопасность без отката к TLS 1.2 ( Modern ) Supports Firefox 63, Android 10.0, Chrome 70, Edge 75, Java 11, OpenSSL 1.1.1, Opera 57, Safari 12.1
# ssl-default-bind-curves X25519:prime256v1:secp384r1
# ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
# ssl-default-bind-options prefer-client-ciphers ssl-min-ver TLSv1.3 no-tls-tickets
# ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
# ssl-default-server-options ssl-min-ver TLSv1.3 no-tls-tickets
ssl-dh-param-file /etc/haproxy/dh4096.pem # openssl dhparam -out /etc/haproxy/dh4096.pem 4096
# tune.ssl.default-dh-param 2048
# Тюнинг http/2
tune.h2.initial-window-size 536870912 # Увеличиваем начальный размер окна для входящих и исходящих соединений.
tune.h2.fe.max-concurrent-streams 512 # Установим кол-во одновременных потоков на входящие соединений.
tune.h2.be.max-concurrent-streams 512 # Установим кол-во одновременных потоков на исходящие соединений.
Сгенерируем DH командой: openssl dhparam -out /etc/haproxy/dh4096.pem
Настроим секции defaults и resolvers для разрешения доменных имен на backend'е
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 40s
timeout client 1m
timeout server 1m
timeout tunnel 1h
timeout http-request 30s
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
resolvers dnsserver
nameserver ns1 127.0.0.1:53
parse-resolv-conf
resolve_retries 3
timeout resolve 1s
timeout retry 1s
hold other 30s
hold refused 30s
hold nx 30s
hold timeout 30s
hold valid 10s
hold obsolete 30s
Настроим http фронтенд (порт 80)
frontend f_http
bind *:80
bind [::]:80
mode http
# отклоним невалидные юзер агенты
http-request reject if { req.hdr(user-agent) -m len le 32 }
http-request reject if { req.hdr(user-agent) -m sub evil }
# ответим на запрос ACME
http-request return status 200 content-type text/plain lf-string "%[path,field(-1,/)].${ACCOUNT_THUMBPRINT}\n" if { path_beg '/.well-known/acme-challenge/'}
http-request deny if { path -m sub /. } # запрет доступа к скрытым файлам
http-request redirect scheme https code 301 unless { ssl_fc }
Теперь настроим TCP фронтенд на 443 порту для SSL Passthrough, а так же сделаем доступным SSH на 443 порту.
frontend f_tcp
bind *:443 # ipv4 SSL Passthrough
bind [::]:443 # ipv6 SSL Passthrough
mode tcp
option tcplog
tcp-request inspect-delay 3s
# Ограничим частоту запросов
stick-table type ip size 1m expire 10s store conn_cur
tcp-request session track-sc0 src
tcp-request session reject if { sc_conn_cur(0) gt 240 }
tcp-request content capture req.ssl_sni len 10
tcp-request content accept if { req_ssl_hello_type 1 } or !{ req_ssl_hello_type 1 }
# Правила сопостовления
use_backend tcp-ssh if !{ req.ssl_hello_type 1 } { payload(0,7) -m bin 5353482d322e30 } or !{ req.ssl_hello_type 1 } { req.len 0 } #use_backend tcp-ssh if !{ req.ssl_hello_type 1 } { req.len 0 }
use_backend tcp_to_https if { req.ssl_sni -i one.example.ru }
Сразу же добавим https фронтед
frontend f_https #
bind abns@frontendhttps.sock accept-proxy ssl crt /etc/haproxy/certs/ alpn h2,http/1.1 strict-sni tfo
http-request reject if { req.hdr(user-agent) -m sub evil }
http-request deny if { path -m sub /. } # запрет доступа к скрытым файлам
http-response set-header X-Content-Type-Options nosniff
http-response set-header X-XSS-Protection 1;mode=block
http-response set-header X-Frame-Options SAMEORIGIN
http-after-response set-header Strict-Transport-Security "max-age=16000000; includeSubDomains;" # Устанавливаем HSTS
stick-table type ip size 100k expire 5m store http_req_rate(10s)
http-request track-sc0 src
http-request deny deny_status 429 if { sc_http_req_rate(0) gt 300 }
http-request return status 200 content-type text/plain lf-string "Status 200 OK! Your IP %[src]." if { path /https-status-443 }
Справка *_sni
req.ssl_sni - анализирует заголовок SNI для маршрутизации без SSL-терминации трафика и сквозной передачи зашифрованного трафика на бекенд.
ssl_fc_sni - Проводит SSL-терминацию трафика для передачи на http бекенды.
Теперь настало время для backend секции
backend tcp-ssh # Бекенд для SSH
mode tcp
option http-keep-alive
timeout http-keep-alive 30s
server ssh 127.0.0.1:22 check port 22
backend tcp_to_https #
mode tcp
server s1 abns@frontendhttps.sock send-proxy-v2-ssl-cn tfo check
retry-on conn-failure empty-response response-timeout
Проверим конфигурацию haproxy haproxy -f /etc/haproxy/haproxy.cfg -c
Если ответ Configuration file is valid перезапустим командой systemctl restart haproxy
На этом базовая конфигурация окончена, можем приступить к выдаче сертификатов перед началом развертывания оставшихся сервисов и настройкой ограничений.
Начнем получать сертификаты, переключимся на пользователя acme:sudo -u acme -s
Создадим первый запрос на сертификат для домена one:acme.sh --issue -d one.example.ru --stateless --ecc
Произведем развертывания сертификата через deploy-hook выполним:
DEPLOY_HAPROXY_HOT_UPDATE=yes \
DEPLOY_HAPROXY_STATS_SOCKET=/var/run/haproxy/admin.sock \
DEPLOY_HAPROXY_PEM_PATH=/etc/haproxy/certs \
acme.sh --deploy -d one.example.ru --deploy-hook haproxy
Таким методом развертываете необходимое кол-во сертификатов, теперь можно установить задачу в crontab на обновление сертификатов командой acme.sh --install-cronjob
.
Проверить сертификат можно командой:echo "show ssl cert /etc/haproxy/certs/one.example.ru" | nc -U /var/run/haproxy/admin.sock | grep Status
Если при выполнении вы видите 'Status: Used' значит все прошло успешно, теперь можно перейти по адресу one.example.ru/https-status-443 и увидеть Status 200 OK! Your IP (Ваш IP), значит все работает, и будем приступать к следующему этапу, защите по списками FireHol и GeoIP по GeoIP так же можно маршрутизировать трафик для клиентов из разных стран или континентов.
Часть 2. Списки FireHol, GeoIP и настройка rate limit
Подготовим директории для хранения списков
mkdir /etc/haproxy/geoip /etc/haproxy/firehol /etc/haproxy/scripts
Напишем скрипт для загрузки firehol, и преобразуем его в чистый список IP и подсетей,nano /etc/haproxy/scripts/firehol.sh
#!/bin/bash
# Определяем директории
TMP_DIR="/tmp/firehol"
DEST_DIR="/etc/haproxy/firehol"
# Создаем временную директорию
mkdir -p "$TMP_DIR"
# Скачиваем файлы
urls=(
"https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/firehol_level1.netset"
"https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/firehol_level2.netset"
"https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/firehol_level3.netset"
"https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/firehol_abusers_1d.netset"
)
files=("level1.acl" "level2.acl" "level3.acl" "abusers_1d.acl")
for i in "${!urls[@]}"; do
curl -s "${urls[$i]}" -o "$TMP_DIR/${files[$i]%.acl}.netset"
done
# Очищаем файлы от комментариев и пустых строк
for file in "${files[@]}"; do
netset_file="$TMP_DIR/${file%.acl}.netset"
acl_file="$TMP_DIR/$file"
grep -vE '^\s*#|^\s*$' "$netset_file" > "$acl_file"
done
# Генерируем md5 для файлов
declare -A md5s
for file in "${files[@]}"; do
md5s["$file"]=$(md5sum "$TMP_DIR/$file" | awk '{ print $1 }')
done
# Проверяем наличие файлов в целевой директории и обновляем их при необходимости
for file in "${files[@]}"; do
dest_file="$DEST_DIR/$file"
if [[ ! -f "$dest_file" ]] || [[ "$(md5sum "$dest_file" | awk '{ print $1 }')" != "${md5s[$file]}" ]]; then
cp "$TMP_DIR/$file" "$dest_file"
echo "Обновлен файл: $dest_file"
fi
done
chown -R haproxy:haproxy /etc/haproxy/firehol/
# Удаляем временную директорию
rm -rf "$TMP_DIR"
Данный скрипт сформирует 4 файла в директории /etc/haproxy/firehol
Теперь добавим GeoIP списки IPv4+IPv6nano /etc/haproxy/scripts/geoip.sh
#!/bin/bash
# https://github.com/Loyalsoldier/geoip/blob/release/GeoLite2-Country-Locations-en.csv
GEOIP_ACL="/etc/haproxy/geoip"
GEOIP_TEMP="/tmp"
function DownloadDB () {
# Очистим старые файлы:
rm -rf $GEOIP_TEMP/{*.csv,*.zip}
curl -o $GEOIP_TEMP/geip_csv.zip https://raw.githubusercontent.com/Loyalsoldier/geoip/release/GeoLite2-Country-CSV.zip && \
unzip -j $GEOIP_TEMP/geip_csv.zip -d $GEOIP_TEMP "*IPv4.csv" "*en.csv" "*IPv6.csv"
# Переименуем csv:
find $GEOIP_TEMP -depth -type f -name '*IPv4.csv' -exec mv {} $GEOIP_TEMP/ips4.csv \;
find $GEOIP_TEMP -depth -type f -name '*IPv6.csv' -exec mv {} $GEOIP_TEMP/ips6.csv \;
cat $GEOIP_TEMP/ips4.csv $GEOIP_TEMP/ips6.csv > $GEOIP_TEMP/ips.csv
find $GEOIP_TEMP -depth -type f -name '*en.csv' -exec mv {} $GEOIP_TEMP/countres.csv \;
}
function CreateAcl () {
if [ ! -d $GEOIP_ACL ]; then
mkdir -p $GEOIP_ACL
fi
while read line; do
COUNTRY_CODE=$(echo $line | cut -d, -f5)
COUNTRY_ID=$(echo $line | cut -d, -f1)
CONTINENT_CODE=$(echo $line | cut -d, -f3)
CONTINENT_ID=$(echo $line | cut -d, -f1)
# Создадим списки
cat $GEOIP_TEMP/ips.csv | grep $COUNTRY_ID | cut -d, -f1 > $GEOIP_ACL/$COUNTRY_CODE.list
cat $GEOIP_TEMP/ips.csv | grep $CONTINENT_ID | cut -d, -f1 > $GEOIP_ACL/$CONTINENT_CODE.acl
done <$GEOIP_TEMP/countres.csv
}
DownloadDB
CreateAcl
rm -rf $GEOIP_TEMP/{*.csv,*.zip}
chown -R haproxy:haproxy /etc/haproxy/geoip/
systemctl restart haproxy
Данный скрипт сформирует файлы со списком IP/подсетей по странам и континентам в директории /etc/haproxy/geoip
Данные скрипты добавим в crontab для обновления например раз в сутки в 4ч утра:0 4 * * * /etc/haproxy/scripts/firehol.sh && /etc/haproxy/scripts/geoip.sh && systemctl restart haproxy >/dev/null 2>&1
Настроим rate limit и подключим списки firehol, в секцию f_http добавим следующие строки после mode http, rate limit выставим на значение 30, после 30 запросов в течении 10 секунд, будет отклонять трафик с IP который превысил ограничение, в зависимости от сложности сайта limit нужно будет корректировать, для 80 порта ставлю меньше, т.к. там только acme и redirect на https.
через tcp-request connection silent-drop if подключим списки firehol, content будет сохранять для дальнейшего анализа, connection оборвет соединение без записи в журнал.
frontend f_http должен теперь выглядеть вот так:
Справка по tcp-request
tcp-request connection - работает на этапе установления соединения, прежде чем оно будет установлено, и если выполняется reject условие, немедленно завершает попытку подключения это полезно для защиты от явных IP адресов скамеров/спамеров и для защиты от DDoS, должно идти первым правилом.
tcp-request session - работает с установленной сессией TCP, если определенные условия выполняются. Например, для отклонения о превышении rate limit, немедленно завершает установленную сессию.
tcp-request content - используется для отклонения конкретного содержимого, которое передается в рамках уже установленных соединений. Это может быть полезно, для фильтрации определенных данных, например, запреты на конкретные запросы или паттерны.
Так же отличаются и действия:
reject - отклоняет соединение но отправляет клиенту ответ о завершении.
silent-drop - делает тоже что и reject только "тихо" без ответа клиенту.
accept - соответственно разрешает подключение по указанному правилу.
Установив '!' перед правилом например { src -f /etc/haproxy/geoip/RU.list }, оно будет инвертировано.
frontend f_http
bind *:80
bind [::]:80
mode http
# FireHOL IP List
tcp-request connection silent-drop if { src -f /etc/haproxy/firehol/level1.acl }
tcp-request connection silent-drop if { src -f /etc/haproxy/firehol/level2.acl }
tcp-request connection silent-drop if { src -f /etc/haproxy/firehol/level3.acl }
tcp-request connection silent-drop if { src -f /etc/haproxy/firehol/abusers_1d.acl }
# Ограничение запросов, дропаем на раннем этапе для http фронта
stick-table type ip size 1m expire 10s store conn_cur
tcp-request session track-sc0 src
tcp-request session reject if { sc_conn_cur(0) gt 30 }
Теперь добавим в TCP фронтенд ограничение в 240 запросов за 10секунд, и списки firehol
frontend f_tcp должен теперь выглядеть вот так:
frontend f_tcp
bind *:443 # ipv4 SSL Passthrough
bind [::]:443 # ipv6 SSL Passthrough
mode tcp
option tcplog
tcp-request inspect-delay 3s
# FireHOL IP List
tcp-request connection silent-drop if { src -f /etc/haproxy/firehol/level1.acl }
tcp-request connection silent-drop if { src -f /etc/haproxy/firehol/level2.acl }
tcp-request connection silent-drop if { src -f /etc/haproxy/firehol/level3.acl }
tcp-request connection silent-drop if { src -f /etc/haproxy/firehol/abusers_1d.acl }
# Ограничим частоту запросов
stick-table type ip size 1m expire 10s store conn_cur
tcp-request session track-sc0 src
tcp-request session reject if { sc_conn_cur(0) gt 240 }
tcp-request session capture req.ssl_sni len 10
tcp-request content capture req.ssl_sni len 10
tcp-request content accept if { req_ssl_hello_type 1 } or !{ req_ssl_hello_type 1 }
Про GeoIP блокировку ее добавить можно так, в секцию f_http и f_tcp внести следующую строку после firehol списков: tcp-request connection reject if { src -f /etc/haproxy/geoip/AF.acl }
тем самым в данном примере заблокировав все подсети Африканского континента, полный список континентов и стран доступен тут, установив '!' перед { src -f /etc/haproxy/geoip/AF.acl } разрешите трафик только от данного региона, и запретите весь остальной, в скрипте есть разделение, континенты с расширением acl, а файлы стран с расширением list.
Маршрутизация по странам настраивается аналогичным образом пример для TCP:use_backend tcp_ru { req.ssl_sni -i example.ru } { src -f /etc/haproxy/geoip/RU.list }
В данном примере на домен example.ru смогут войти только клиенты с IP входящих в файл RU.list. можно пойти иным путем, например:use_backend tcp_ru if { src -f /etc/haproxy/geoip/RU.list }
В данном варианте если клиент подключается с IP из Российского списка он будет отправлен на соответствующий backend tcp.use_backend tcp_by if { src -f /etc/haproxy/geoip/BY.list }
А в данном примере клиенты с IP Белоруссии уйдут на соответствующий backend tcp.
Всех остальных которые не прошли по правилам отправим на дефолтный бекенд:default_backend default_tcp
Для работы соответственно потребуется создать соответствующие бэкенды перед запуском.
Все тоже самое можно проделать на HTTP фронтэнде, на примере заблокируем клиентов из Белоруссии явно сообщив им об этом ошибкой 403 и текстом и их IP адресом:http-request deny deny_status 403 content-type text/plain lf-string "Access denied, IP not from whitelist. Your IP %[src]." if { src -f /etc/haproxy/geoip/BY.list }
Или отправим клиентов из России на один бэкенд, а клиентов из Белоруссии на второй, и установим default_backend на тот случай если их IP нет в списках стран, пример:
Для России use_backend http_ru if { src -f /etc/haproxy/geoip/RU.list }
Для Белоруссии use_backend http_by if { src -f /etc/haproxy/geoip/BY.list }
И для тех кто не прошел по правилам стран:default_backend http_default
Для Haproxy Enterprise (HAPEE) данные действия не требуются, есть модули MaxMind и Digital Element, которые упрощают работу с GeoIP.
Часть 3. mTLS и настройка УЦ для клиентских сертификатов
Первым делом загрузим XCA для удобства управления и выдачи сертификатов, я использую портативную версию, подготовим ROOTCA и SUBCA
При запуске XCA создайте БД, после открытия базы, сообщит что шифрование не безопасно, перейдите в "изменить" и поменяйте на "AES-256-CBC", так же в настройках измените алгоритм подписи по умолчанию на "SHA384" и отключите устаревшие расширения Netscape.
Создадим корневой сертификат для подписи, для этого перейдите в "Сертификаты" и "Новый сертификат"
Инструкция в картинках
Создадим корневой сертификат, которым подпишем промежуточный.
Настройте первоисточник, по умолчанию будет самозаверенный, алгоритм подписи SHA384 и шаблон выберите "CA"
Заполните субъект, в данном случае только внутреннее имя и CN нажмите сгенерировать ключ
Тип ключа выберете EC, и кривую prime256v1 или secp384r1
Настроим расширения, Установим длину цепочки т.е. сколько может быть промежуточных сертификатов, становим Key identifier, настроим срок действия, и SAN, через "редактировать" добавить "Copy CN"
Настроим применения ключа, выставим Critical. Можно сохранять
Теперь выдадим промежуточный
Теперь в "Подписание" укажем что хотим подписать данный сертификат нашим только что созданным корневым сертификатом ROOTCA.
Заполните субъект, создайте ключ по аналогии с корневым сертификатом.
Настройте расширения, идентично как в ROOTCA, с единственным отличием, промежуточный ЦС не будет выдавать сертификаты УЦ, для этого установите длину цепочки как 0
Аналогично ROOTCA
Теперь создадим ключ клиента и подпишем созданным SUBCA
Заполняем первоисточник, выбираем для подписи SUBCA и шаблон TLS_client
Заполним субъект, в CN пишем наименование клиента, в данном примере client, а во внутреннее имя добавлю имя домена, для удобства поиска, создаем ключ.
Обязательно в расширениях ставим все галочки Key indetifier, и так же SAN копируем CN
Установим значения critical.
Теперь можно экспортировать сертификат клиента в формате цепочки PKCS#12 *.pfx
Теперь необходимо установить сертификат в "Личное" для Windows, и для Android в "Сертификаты VPN и приложений"
Теперь необходимо сохранить ROOTCA и SUBCA в файл pem по отдельности, и в один файл, назовем его fullca.pem
Теперь необходимо получить файл revoked.crl, для этого, на SUBCA кликаем ПКМ>ЦС>Сгенерировать CRL, для удобства выставляйте пару лет.
Аналогичные действия и для ROOTCA
Экспортируем SUBCA и ROOTCA в файл revoked.crl именно в таком порядке.
Теперь когда у нас есть файлы ROOTCA.pem, SUBCA.pem, fullca.pem и revoked.crl можем приступать к настройке фронтенда.
В секцию f_tcp под tcp_to_https добавим:use_backend tcp_to_mtls if { req.ssl_sni -i auth.example.ru }
Теперь добавим backend
backend tcp_to_mtls
mode tcp
server mtls abns@frontendmtls.sock send-proxy-v2-ssl-cn tfo check
retry-on conn-failure empty-response response-timeout
Настроим фронтенд
frontend f_mtls
bind abns@frontendmtls.sock tfo accept-proxy ssl crt /etc/haproxy/certs/ alpn h2,http/1.1 ssl-min-ver TLSv1.3 strict-sni verify required ca-file /etc/haproxy/ca/SUBCA.pem ca-verify-file /etc/haproxy/ca/fullca.pem crl-file /etc/haproxy/ca/revoked.crl crt-ignore-err 10,23
http-request reject if { req.hdr(user-agent) -m sub evil }
http-request deny if { path -m sub /. } # запрет доступа к скрытым файлам
stick-table type ip size 100k expire 5m store http_req_rate(30s)
http-request track-sc0 src
http-request deny deny_status 429 if { sc_http_req_rate(0) gt 60 }
http-after-response set-header Strict-Transport-Security "max-age=16000000;"
http-request return status 403 content-type text/plain lf-string "Your certificate has expired, please contact the administrator" if { ssl_c_verify 10 } #text/html file /certexpired.html
http-request return status 403 content-type text/plain lf-string "Your certificate has been revoked, please contact the administrator" if { ssl_c_verify 23 } #text/html file /certrevoked.html
http-request return status 200 content-type text/plain lf-string "Status 200 OK! Your IP %[src]." if { path /mtls-status-443 }
frontendmtls
verify required - отвечает за обязательную проверку сертификата клиента.
ca-file - файл промежуточного сертификата которым подписаны клиенты.
ca-verify-file - включает в себя корневой сертификат и промежуточный (добавлен в HAProxy 2.2 ), отправляет клиенту более короткий список CA в сообщении SERVER HELLO, который будет использоваться для проверки.
crt-ignore-err - позволяет отвечать клиенту кодом ошибки если код "10" - сертификат истек или еще не наступил, "23" - сертификат отозван.
ssl-min-ver TLSv1.3 - явно указываем TLSv1.3 для защиты клиентского сертификата без отката к TLSv1.2
Создадим для хранения наших файлов директорию:mkdir /etc/haproxy/ca
Переместим файлы SUBCA.pem fullca.pem и revoked.crl в директорию удобным для вас способом.
Выставим права и владельца:chmod 700 /etc/haproxy/ca/
chown -R haproxy:haproxy /etc/haproxy/ca/
Проверяем конфигурацию командой haproxy -f /etc/haproxy/haproxy.cfg -c
Если все ок, перезапускаем сервис systemctl restart haproxy
Проверяем через браузер, заходим на auth.example.ru и если у вас появился запрос сертификата а после предоставления сертификата получаете "Status 200 OK! Your IP" значит все сделано правильно. можете добавлять свои пути/домены для маршрутизации, домены соответственно нужно будет добавить и f_tcp например так:use_backend tcp_to_mtls if { req.ssl_sni -i auth.example.ru } || { req.ssl_sni -i auth2.example.ru }
Или добавить под tcp_to_mtls аналогичную строку только с другим доменом.
Часть 4. Метрики
Подключим выгрузку метрик в Prometheus через встроенный экспортер в Haproxy, для этого есть два пути, 1. через путь в виде { path_beg /path/ } и один домен или через доп домен, так же защитим наш сервис паролем либо mTLS создав дополнительный SUBCA для метрик.
Выгрузка через второй домен, добавляем в f_tcp под tcp_to_mtlsuse_backend tcp_to_metrics if { req.ssl_sni -i metrics.example.ru }
Теперь добавим frontend для метрик и секцию с паролем и разрешим подключаться только с Российских IP, в идеале разрешить только подсети вашего провайдера или если есть белый статичный IP разрешить его (метрики так же можно скрыть за mTLS):
frontend f_metrics
bind abns@metricshttps.sock accept-proxy ssl crt /etc/haproxy/certs/ alpn h2,http/1.1 strict-sni tfo
tcp-request connection accept if { src -f /etc/haproxy/geoip/RU.list }
http-request reject if { req.hdr(user-agent) -m sub evil }
http-request deny if { path -m sub /. } # запрет доступа к скрытым файлам
stick-table type ip size 100k expire 5m store http_req_rate(30s)
http-request track-sc0 src
http-request deny deny_status 429 if { sc_http_req_rate(0) gt 60 }
http-after-response set-header Strict-Transport-Security "max-age=16000000; includeSubDomains;"
http-response set-header X-Content-Type-Options nosniff
http-response set-header X-XSS-Protection 1;mode=block
http-response set-header X-Frame-Options SAMEORIGIN
stats enable
stats uri /path/stats
stats realm Haproxy\ Statistics
stats refresh 10s
stats show-legends
stats hide-version
http-request use-service prometheus-exporter if { path_beg /haproxy-exporter-path/ }
userlist metrics
user metrics insecure-password secretpassword
Метрики будут доступны по адресу https://metrics.example.ru/haproxy-exporter-path/metrics
с логином metrics и паролем secretpassword, для Grafana использую панель Haproxy 2 Full (ID 12693)
Так же, легким движением рук можем изменить доступ по паролю на доступ по сертификату для этого подготовим промежуточный УЦ и сделаем клиента для Prometheus по аналогии с mTLS frontend, пример конфигурации:
frontend f_metrics_mtls
bind abns@metricshttps.sock tfo accept-proxy ssl crt /etc/haproxy/certs/ alpn h2,http/1.1 ssl-min-ver TLSv1.3 strict-sni verify required ca-file /etc/haproxy/ca/metrics.pem ca-verify-file /etc/haproxy/ca/metricsfull.pem crl-file /etc/haproxy/ca/metrics.crl crt-ignore-err 10,23
tcp-request connection accept if { src -f /etc/haproxy/geoip/RU.list }
http-request reject if { req.hdr(user-agent) -m sub evil }
http-request deny if { path -m sub /. } # запрет доступа к скрытым файлам
stick-table type ip size 100k expire 5m store http_req_rate(30s)
http-request track-sc0 src
http-request deny deny_status 429 if { sc_http_req_rate(0) gt 90 }
http-after-response set-header Strict-Transport-Security "max-age=16000000;"
http-request return status 403 content-type text/plain lf-string "Your certificate has expired, please contact the administrator" if { ssl_c_verify 10 }
http-request return status 403 content-type text/plain lf-string "Your certificate has been revoked, please contact the administrator" if { ssl_c_verify 23 }
http-request use-service prometheus-exporter if { path_beg /haproxy-exporter-path/ } # exporter haproxy, ссылка для prometheus ( /path_beg/metrics )
# use_backend node-exporter if { path_beg -i /node-exporter-path/ } # exporter сервера
Пример конфигурации Prometheus для авторизации по сертификату:
- job_name: 'Haproxy'
scheme: https
static_configs:
- targets: ['mdata.example.ru:443']
metrics_path: /mypath/metrics
tls_config:
# ca_file: 'ca.crt'
cert_file: 'client.crt'
key_file: 'client.key'
Добавить node-exporter со стандартной установкой на 9100 порту можно так:
В фронтенд метрик добавить use_backend node-exporter if { path_beg -i /node-exporter-path }
Теперь добавим backend, перед передачей на бекенд удалим префикс пути:
backend node-exporter
mode http
http-request replace-path /node-exporter-path(/)?(.*) /\2
server s1 127.0.0.1:9100
Теперь добавим Web статистику вставим следующий например перед фронтедом метрик:
frontend f_stats
bind abns@stats.sock
stats enable
stats uri /stats
stats realm Haproxy\ Statistics
stats refresh 10s
stats show-legends
stats hide-version
stats show-modules
backend stats
mode http
http-request replace-path /haproxy(/)?(.*) /\2
server s1 abns@stats.sock
Теперь добавим во фронтенд mTLS правило:use_backend stats if { path_beg -i /haproxy/ }
Статистика будет доступна по адресу https://auth.example.ru/haproxy/stats
Примеры из Grafana
Часть 5. 3X-UI, Reality TCP, XHTTP и DoH
Для начала создадим пользователя и группу для работы панелиadduser --system --disabled-password --disabled-login --home /usr/local/x-ui --quiet --force-badname --group xray
Для установки выполним:bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
При установке меняем порт на 49004 или удобный вам.
После завершения скрипт покажет текущую конфигурацию для подключения, адрес пути и логин с паролем, сохраняем пригодиться позже.
После установки проведем настройку:
1. Откроем юнит x-ui на редактирование nano /etc/systemd/system/x-ui.service
Необходимо после [Service] добавить следующие строки:User=xray
Group=haproxy
2. Создадим директорию для Socket: mkdir /var/lib/haproxy/xui
3. Изменим владельца файлов x-ui и директории для Socket:chown -R xray:xray /etc/x-ui/
chown -R xray:xray /usr/local/x-ui/
chown -R xray:haproxy /var/lib/haproxy/xui
4. Перечитаем конфигурацию systemd: systemctl daemon-reload
5. Перезапустим сервис x-ui и сразу проверим статус работы:systemctl restart x-ui.service && systemctl status x-ui.service
Перейдем к настройке Haproxy:
В mTLS фронтенд добавляем use_backend http_3xui if { path_beg /defaultpath/ } || { path_beg /3xui-path }
В defaultpath вставляем путь выданный панелью при установке, можно оставить как есть, во второе правило, которое придумаете сами.
Добавим backend:
backend http_3xui # Админ панель 3X-UI
mode http
option forwardfor if-none
http-request add-header X-Real-Ip %[src]
http-request set-header Host %[req.hdr(Host)]
http-request set-header X-Forwarded-For %[src]
http-request set-header X-Forwarded-Host %[req.hdr(host)]
http-request set-header X-Forwarded-Proto https if { ssl_fc }
server s1 127.0.0.1:49004 check port 49004
Перезапускаем haproxy и проверяем, если панель открылась перейдем к настройке подключений, создадим TCP REALITY:
Пример настроек inbound
1.Укажем удобное примечание например: tcp_to_reality
2.Протокол: VLESS
3.Порт не меняем
4.Протокол(транспорт): TCP(RAW)
5.Proxy Protocol: Enable
6.Sockopt: Enable
7.TCP Fast Open: Enable
8.TCP Congestion: bbr
9.Tproxy: off
10.External Proxy: enable
[Тот же] [drive.example.ru] [443]
Безопасность: Reality
11.Xver: 1
12.uTLS на ваш выбор, у меня firefox
13.Dest (Target): /var/lib/haproxy/fakehttps1.sock
14.SNI: drive.example.ru
15.Sniffing: включить по умолчанию.
Настроим HAProxy, в f_tcp добавим следующую строку под use_backend:
use_backend tcp_to_reality if { req.ssl_sni -i drive.example.ru }
Добавим бекенд:
backend tcp_to_reality
mode tcp
server r1 /x-ui/reality.sock send-proxy tfo check
retry-on conn-failure empty-response response-timeout
Теперь добавим фронтенд для маскировки например под nextcloud:
frontend f_https_reality
bind /var/lib/haproxy/fakehttps1.sock accept-proxy ssl crt /etc/haproxy/certs/ alpn h2,http/1.1 ssl-min-ver TLSv1.3 strict-sni mode 660 user haproxy group haproxy
http-request reject if { req.hdr(user-agent) -m sub evil }
acl url_discovery path /.well-known/caldav /.well-known/carddav
http-request redirect location /remote.php/dav/ code 301 if url_discovery
http-request deny if { path -m sub /. }
stick-table type ip size 100k expire 2m store http_req_rate(10s)
http-request track-sc0 src
http-request deny deny_status 429 if { sc_http_req_rate(0) gt 240 }
http-response set-header X-Content-Type-Options nosniff
http-response set-header X-XSS-Protection 1;mode=block
http-response set-header X-Frame-Options SAMEORIGIN
http-response set-header Strict-Transport-Security "max-age=16000000; includeSubDomains;" # Устанавливаем HSTS
http-request return status 200 content-type text/plain lf-string "Status 200 OK! Your IP %[src]." if { path / }
# default_backend nextcloud
Сохраняем и проверяем конфигурацию:haproxy -f /etc/haproxy/haproxy.cfg -c
Если ошибок нет, перезапускаем и проверяем подключение. После разворачиваем nextcloud убираем http-request return status 200 и раскомментируем default_backend nextcloud
Создаем бекенд для nextcloud:
backend nextcloud
mode http
option forwardfor
http-request set-header X-Forwarded-Proto https if { ssl_fc }
http-request set-header X-Forwarded-Proto http if !{ ssl_fc }
http-request set-header X-Forwarded-Host %[req.hdr(host)]
http-request set-header X-Forwarded-For %[src]
http-request add-header X-Real-Ip %[src]
server s1 127.0.0.1:49009
Таким образом можно добавить неограниченной количество inbounds
Настроим XHTTP:
Пример
Создаем inbound со следующими настройками:
1. Примечание на ваш выбор.
2. Протокол: VLESS
3. Мониторинг IP: /var/lib/haproxy/x-ui/xhttp1.sock,0660
4. Протокол(транспорт): XHTTP
5. Хост: ваш домен пример: corp.example.ru
6. Путь: /secretpath
7. Mode: auto
8. Sockopt: enable
9. TCP Fast Open: enable
10. External Proxy: enable
[TLS] [corp.example.ru] [443]
11. Безопасность: пусто
12. Sniffing: включить по умолчанию.
Пример в картинках
Добавим во фронтенд f_https:use_backend xhttp if { req.hdr(host) -i corp.example.ru } { path_beg /secretpathbeg/ } || { path /secretpath }
Добавим бекенд:
backend xhttp # XHTTP: Beyond REALITY
mode http
option forwardfor if-none
http-request add-header X-Real-Ip %[src]
http-request set-header Host %[req.hdr(Host)]
http-request set-header X-Forwarded-For %[src]
http-request set-header X-Forwarded-Host %[req.hdr(host)]
retry-on conn-failure empty-response response-timeout
server x /x-ui/xhttp1.sock send-proxy tfo check
Аналогично проверяем конфигурацию HAProxy, и перезапускаем.
Настроим DoH, есть два пути, в docker и без, пойдем вариантом без docker.
Выполняем скрипт:curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -v
Установиться с правами root, остановим сервис systemctl stop AdGuardHome.service,
Настроим конфигурацию: nano /opt/AdGuardHome/AdGuardHome.yaml
AdGuardHome.yaml
http:
pprof:
port: 6060
enabled: false
address: 127.0.0.1:49005
session_ttl: 720h
users:
- name: demo
password: $2y$10$9ho8XPNKtOBVRlfgQV8CFOVCOsFVpjXAHZRzICh0PDBA.dsaCbioO
auth_attempts: 5
block_auth_min: 15
http_proxy: ""
language: ""
theme: auto
dns:
bind_hosts:
- 127.0.0.2
port: 53
anonymize_client_ip: false
ratelimit: 20
ratelimit_subnet_len_ipv4: 24
ratelimit_subnet_len_ipv6: 56
ratelimit_whitelist: []
refuse_any: true
upstream_dns:
- 127.0.0.53
upstream_dns_file: ""
bootstrap_dns:
- 9.9.9.10
- 149.112.112.10
- 2620:fe::10
- 2620:fe::fe:10
fallback_dns: []
upstream_mode: load_balance
fastest_timeout: 1s
allowed_clients: []
disallowed_clients: []
blocked_hosts:
- version.bind
- id.server
- hostname.bind
trusted_proxies:
- 127.0.0.0/8
- ::1/128
cache_size: 4194304
cache_ttl_min: 0
cache_ttl_max: 0
cache_optimistic: false
bogus_nxdomain: []
aaaa_disabled: false
enable_dnssec: false
edns_client_subnet:
custom_ip: ""
enabled: false
use_custom: false
max_goroutines: 300
handle_ddr: true
ipset: []
ipset_file: ""
bootstrap_prefer_ipv6: false
upstream_timeout: 10s
private_networks: []
use_private_ptr_resolvers: true
local_ptr_upstreams: []
use_dns64: false
dns64_prefixes: []
serve_http3: false
use_http3_upstreams: false
serve_plain_dns: true
hostsfile_enabled: true
tls:
enabled: false
server_name: ""
force_https: false
port_https: 0
port_dns_over_tls: 0
port_dns_over_quic: 0
port_dnscrypt: 0
dnscrypt_config_file: ""
allow_unencrypted_doh: true
certificate_chain: ""
private_key: ""
certificate_path: ""
private_key_path: ""
strict_sni_check: false
querylog:
dir_path: ""
ignored: []
interval: 2160h
size_memory: 1000
enabled: true
file_enabled: true
statistics:
dir_path: ""
ignored: []
interval: 24h
enabled: true
filters:
- enabled: false
url: https://raw.githubusercontent.com/AdguardTeam/FiltersRegistry/master/filters/filter_1_Russian/filter.txt
name: Ru filter
id: 1736931102
- enabled: false
url: https://raw.githubusercontent.com/rebelion76/bankiru_plus_adblock_list/master/bankiru_plus.txt
name: bankiru_plus
id: 1736931124
- enabled: false
url: https://raw.githubusercontent.com/deathbybandaid/piholeparser/master/Subscribable-Lists/CountryCodesLists/Russia.txt
name: Подписки RU
id: 1736931125
- enabled: false
url: https://easylist-downloads.adblockplus.org/ruadlist.txt
name: ruadlist
id: 1736931126
- enabled: false
url: https://raw.githubusercontent.com/deathbybandaid/piholeparser/master/Subscribable-Lists/ParsedBlacklists/RU-AdList.txt
name: RU-AdList
id: 1736931127
- enabled: false
url: https://easylist-downloads.adblockplus.org/cntblock.txt
name: RU Adlist Counters
id: 1736931128
- enabled: false
url: https://gist.githubusercontent.com/drewpayment/4a316423f7ff7df9dce63a041c478486/raw/6a509d262d7dd687c645349be3fc6217c0764768/AdGuard%2520Russian%2520filter%2520-%2520Rules.txt
name: AdGuard Russian filter
id: 1736931118
- enabled: false
url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt
name: AdGuard DNS filter
id: 1
- enabled: false
url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_2.txt
name: AdAway Default Blocklist
id: 2
- enabled: false
url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_4.txt
name: Dan Pollock's List
id: 1736931097
- enabled: false
url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_30.txt
name: Phishing URL Blocklist (PhishTank and OpenPhish)
id: 1736931098
- enabled: false
url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_50.txt
name: uBlock₀ filters – Badware risks
id: 1736931099
- enabled: false
url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_33.txt
name: Steven Black's List
id: 1736931100
- enabled: false
url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_59.txt
name: AdGuard DNS Popup Hosts filter
id: 1736931101
- enabled: false
url: https://raw.githubusercontent.com/AdguardTeam/FiltersRegistry/master/filters/filter_3_Spyware/filter.txt
name: Spyware
id: 1736931103
- enabled: false
url: https://raw.githubusercontent.com/AdguardTeam/FiltersRegistry/master/filters/filter_17_TrackParam/filter.txt
name: Trackers
id: 1736931104
- enabled: false
url: https://raw.githubusercontent.com/AdguardTeam/FiltersRegistry/master/filters/filter_15_DnsFilter/filter.txt
name: Ad Guard filter
id: 1736931107
- enabled: false
url: https://raw.githubusercontent.com/AdguardTeam/FiltersRegistry/master/filters/filter_11_Mobile/filter.txt
name: Mobile
id: 1736931108
- enabled: false
url: https://raw.githubusercontent.com/AdguardTeam/FiltersRegistry/master/filters/filter_21_Annoyances_Other/filter.txt
name: Раздражающие элементы
id: 1736931109
- enabled: false
url: https://raw.githubusercontent.com/AdguardTeam/FiltersRegistry/master/filters/filter_22_Annoyances_Widgets/filter.txt
name: Widgets
id: 1736931110
- enabled: false
url: https://raw.githubusercontent.com/AdguardTeam/FiltersRegistry/master/filters/filter_20_Annoyances_MobileApp/filter.txt
name: Widgets_Mobile
id: 1736931111
- enabled: false
url: https://raw.githubusercontent.com/AdguardTeam/FiltersRegistry/master/filters/filter_19_Annoyances_Popups/filter.txt
name: Всплывайки сайты
id: 1736931112
- enabled: false
url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_23.txt
name: Windows Spy list
id: 1736931113
- enabled: false
url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_8.txt
name: nocoin
id: 1736931114
- enabled: false
url: https://blocklistproject.github.io/Lists/smart-tv.txt
name: smart-tv
id: 1736931115
- enabled: false
url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_49.txt
name: HaGeZi's Ultimate Blocklist
id: 1736931116
- enabled: false
url: https://adguardteam.github.io/HostlistsRegistry/assets/filter_27.txt
name: OISD Blocklist Big
id: 1736931117
- enabled: false
url: https://raw.githubusercontent.com/hant0508/uBlock-filters/master/filters.txt
name: uBlock-filters
id: 1736931119
- enabled: false
url: https://raw.githubusercontent.com/AdguardTeam/FiltersRegistry/master/filters/filter_10_Useful/filter.txt
name: AdguardTeam
id: 1736931120
- enabled: false
url: https://filters.adtidy.org/extension/chromium-mv3/filters/24.txt
name: AdGuard Quick Fixes filter MV3
id: 1736931121
- enabled: false
url: https://gitlab.com/eyeo/anti-cv/abp-filters-anti-cv/-/raw/master/russian.txt
name: Russian anti-cv
id: 1736931122
- enabled: false
url: https://filters.adtidy.org/extension/ublock/filters/1_optimized.txt
name: ublock optimized
id: 1736931123
whitelist_filters: []
user_rules: []
dhcp:
enabled: false
interface_name: ""
local_domain_name: lan
dhcpv4:
gateway_ip: ""
subnet_mask: ""
range_start: ""
range_end: ""
lease_duration: 86400
icmp_timeout_msec: 1000
options: []
dhcpv6:
range_start: ""
lease_duration: 86400
ra_slaac_only: false
ra_allow_slaac: false
filtering:
blocking_ipv4: ""
blocking_ipv6: ""
blocked_services:
schedule:
time_zone: Local
ids: []
protection_disabled_until: null
safe_search:
enabled: false
bing: true
duckduckgo: true
ecosia: true
google: true
pixabay: true
yandex: true
youtube: true
blocking_mode: default
parental_block_host: family-block.dns.adguard.com
safebrowsing_block_host: standard-block.dns.adguard.com
rewrites: []
safe_fs_patterns:
- /opt/AdGuardHome/userfilters/*
safebrowsing_cache_size: 1048576
safesearch_cache_size: 1048576
parental_cache_size: 1048576
cache_time: 30
filters_update_interval: 24
blocked_response_ttl: 10
filtering_enabled: true
parental_enabled: false
safebrowsing_enabled: false
protection_enabled: true
clients:
runtime_sources:
whois: true
arp: true
rdns: true
dhcp: true
hosts: true
persistent: []
log:
enabled: true
file: ""
max_backups: 0
max_size: 100
max_age: 3
compress: false
local_time: false
verbose: false
os:
group: ""
user: ""
rlimit_nofile: 0
schema_version: 29
Основное изменение это включение allow_unencrypted_doh: true
для данного примера, логин и пароль demo,
и используется системный резолвер, порт панели 49005, добавлены списки для блокировки, по умолчанию отключены.
Подготовим HAProxy, во фронтенд f_tcp добавим следующую строку:use_backend tcp_to_mtls if { req.ssl_sni -i ns1.example.ru }
Для понижения прав, необходимо или понизить привилегированный порт, или поднять порт dns например на 5353.
В f_https добавим:use_backend http_adh if { ssl_fc_sni -i corp.example.ru } { path_beg -i /
secretpathdns/ }
Добавим бэкенд:
backend http_adh # DOH AdGuardHome
mode http
option forwardfor if-none
http-request add-header X-Real-Ip %[src]
http-request set-header Host %[req.hdr(Host)]
http-request set-header X-Forwarded-For %[src]
http-request set-header X-Forwarded-Host %[req.hdr(host)]
http-request set-header X-Forwarded-Proto https if { ssl_fc }
http-request replace-path /secretpath(/)?(.*) /\2
server dns 127.0.0.1:49005 check port 49005
{ path_beg -i /secretpathdns/ } нужен для защиты dns-query, перед отправкой на бекенд путь будет удален, и запрос дойдет без ошибок к DoH, адрес для DoH следующий https://corp.examle.ru/secretpath/dns-query
Теперь же пришло добавить панель управления AdGuardHome, в f_tcp добавим следующую строку:use_backend tcp_to_mtls if { req.ssl_sni -i ns1.example.ru }
Теперь внесем в f_mtls следующую строку:use_backend http_adh if { ssl_fc_sni -i ns1.example.ru }
Снова проверяем конфигурацию и перезагружаем HAProxy.
Основное изменение это включение allow_unencrypted_doh: true для данного примера, логин и пароль demo, и используется системный резолвер, порт панели 49005
Подготовим HAProxy, во фронтенд f_tcp добавим следующую строку:use_backend tcp_to_mtls if { req.ssl_sni -i ns1.example.ru }
Для понижения прав, необходимо или понизить привилегированный порт, или поднять порт dns например на 53535, создать пользователя, и необходимо изменить следующие:os:
group: ""
user: ""
На созданного пользователя: например adguard:os:
group: adguard
user: adguard
В f_https добавим:use_backend http_adh if { ssl_fc_sni -i corp.example.ru } { path_beg -i /secretpathdns/ }
Добавим бэкенд:
backend http_adh # DOH AdGuardHome
mode http
option forwardfor if-none
http-request add-header X-Real-Ip %[src]
http-request set-header Host %[req.hdr(Host)]
http-request set-header X-Forwarded-For %[src]
http-request set-header X-Forwarded-Host %[req.hdr(host)]
http-request set-header X-Forwarded-Proto https if { ssl_fc }
http-request replace-path /secretpath(/)?(.*) /\2
server dns 127.0.0.1:49005 check port 49005
{ path_beg -i /secretpathdns/ } нужен для защиты dns-query, перед отправкой на бекенд путь будет удален, и запрос дойдет без ошибок к DoH, адрес для DoH следующий https://corp.examle.ru/secretpath/dns-query
Теперь же пришло добавить панель управления AdGuardHome, в f_tcp добавим следующую строку:
use_backend tcp_to_mtls if { req.ssl_sni -i ns1.example.ru }
Теперь внесем в f_mtls следующую строку:
use_backend http_adh if { ssl_fc_sni -i ns1.example.ru }
Снова проверяем конфигурацию и перезагружаем HAProxy.
Итог по 3X-UI панель работает за mTLS, кроме сервиса подписки, она доступна из вне по длинному пути, сами inbound работают через unix socket что должно обеспечить более высокую производительность в связке с TFO, REALITY развернут методом steal-oneself, при котором мы не зависим от чужого сайта/сертификата, ответ более быстрый, следовательно работать будет быстрее.
Часть 6. Установка сертификата
Для выпуска сертификата для сервиса, потребуется сделать следующие на примере ocserv:
1. Создаем директорию: mkdir /etc/ocserv/ssl
2. Добавляем пользователя acme в группу ocserv: adduser acme ocserv
3. Меняем владельца: chown ocserv:ocserv /etc/ocserv/ssl
4.Изменяем права: chmod 770 /etc/ocserv/ssl
Переключаемся на пользователя acme: sudo -u acme -s
Получим сертификат: acme.sh --issue -d example.com --stateless
Установим сертификат и перезапустим ocserv:
acme.sh --install-cert -d example.ru \
--cert-file /etc/ocserv/ssl/example.ru.pem \
--key-file /etc/ocserv/ssl/example.ru.key \
--fullchain-file /etc/ocserv/ssl/fullchain.ru.pem \
--reloadcmd "systemctl restart ocserv"
На этом все, проделываем аналогичным способом для ваш сервисов.
Заключение
В данной статье показал пример как можно защитить сервисы за mTLS, пример работы с GeoIP выгрузка в Prometheus из HAProxy exporter, и обновил информацию по REALITY.
В статье используется множество поддоменов, поменяйте на свои соответственно, аналогично и с path.
Полный конфигурации haproxy.cfg
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /var/run/haproxy/admin.sock level admin mode 660
setenv ACCOUNT_THUMBPRINT '*ACCOUNT_THUMBPRINT*' # поменять на полученный из acme
stats timeout 30s
user haproxy
group haproxy
daemon
# https://ssl-config.mozilla.org/
# Улучшенная безопастность и совместимость со старыми браузерами ( Intermediate ) Supports Firefox 27, Android 4.4.2, Chrome 31, Edge, IE 11 on Windows 7, Java 8u31, OpenSSL 1.0.1, Opera 20, Safari 9
ssl-default-bind-curves X25519:prime256v1:secp384r1 #:secp521r1:X448
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:DHE-RSA-CHACHA20-POLY1305
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-bind-options prefer-client-ciphers ssl-min-ver TLSv1.2 no-tls-tickets
ssl-default-server-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:DHE-RSA-CHACHA20-POLY1305
ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-server-options ssl-min-ver TLSv1.2 no-tls-tickets
# TLS 1.3 современная безопасность без отката к TLS 1.2 ( Modern ) Supports Firefox 63, Android 10.0, Chrome 70, Edge 75, Java 11, OpenSSL 1.1.1, Opera 57, Safari 12.1
# ssl-default-bind-curves X25519:prime256v1:secp384r1
# ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
# ssl-default-bind-options prefer-client-ciphers ssl-min-ver TLSv1.3 no-tls-tickets
# ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
# ssl-default-server-options ssl-min-ver TLSv1.3 no-tls-tickets
ssl-dh-param-file /etc/haproxy/dh4096.pem # openssl dhparam -out /etc/haproxy/dh4096.pem 4096
# tune.ssl.default-dh-param 2048
# Тюнинг http/2
tune.h2.initial-window-size 536870912 # Увеличиваем начальный размер окна для входящих и исходящих соединений.
tune.h2.fe.max-concurrent-streams 512 # Установим кол-во одновременных потоков на входящие соединений.
tune.h2.be.max-concurrent-streams 512 # Установим кол-во одновременных потоков на исходящие соединений.
#------------------------------------------
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 40s
timeout client 1m
timeout server 1m
timeout tunnel 1h
timeout http-request 30s
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
#------------------------------------------
resolvers dnsserver
nameserver ns1 127.0.0.1:53
parse-resolv-conf
resolve_retries 3
timeout resolve 1s
timeout retry 1s
hold other 30s
hold refused 30s
hold nx 30s
hold timeout 30s
hold valid 10s
hold obsolete 30s
#-----------------------------------
frontend f_http
bind *:80
bind [::]:80
mode http
# FireHOL IP List
tcp-request connection silent-drop if { src -f /etc/haproxy/firehol/level1.acl }
tcp-request connection silent-drop if { src -f /etc/haproxy/firehol/level2.acl }
tcp-request connection silent-drop if { src -f /etc/haproxy/firehol/level3.acl }
tcp-request connection silent-drop if { src -f /etc/haproxy/firehol/abusers_1d.acl }
# Ограничение запросов, дропаем на раннем этапе для http фронта
stick-table type ip size 1m expire 10s store conn_cur
tcp-request session track-sc0 src
tcp-request session reject if { sc_conn_cur(0) gt 60 }
# отклоним невалидные юзер агенты
http-request reject if { req.hdr(user-agent) -m len le 32 }
http-request reject if { req.hdr(user-agent) -m sub evil }
# ответим на запрос ACME
http-request return status 200 content-type text/plain lf-string "%[path,field(-1,/)].${ACCOUNT_THUMBPRINT}\n" if { path_beg '/.well-known/acme-challenge/' }
http-request deny if { path -m sub /. } # запрет доступа к скрытым файлам
http-request redirect scheme https code 301 unless { ssl_fc }
#------------------------------------------
frontend f_tcp
bind *:443 # ipv4 SSL Passthrough
bind [::]:443 # ipv6 SSL Passthrough
mode tcp
option tcplog
tcp-request inspect-delay 3s
# FireHOL IP List
tcp-request connection silent-drop if { src -f /etc/haproxy/firehol/level1.acl }
tcp-request connection silent-drop if { src -f /etc/haproxy/firehol/level2.acl }
tcp-request connection silent-drop if { src -f /etc/haproxy/firehol/level3.acl }
tcp-request connection silent-drop if { src -f /etc/haproxy/firehol/abusers_1d.acl }
# Ограничим частоту запросов
stick-table type ip size 1m expire 10s store conn_cur
tcp-request content track-sc0 src
tcp-request content reject if { sc_conn_cur(0) gt 240 }
tcp-request content capture req.ssl_sni len 10
tcp-request content accept if { req_ssl_hello_type 1 } or !{ req_ssl_hello_type 1 }
# Правила сопостовления
use_backend tcp-ssh if !{ req.ssl_hello_type 1 } { payload(0,7) -m bin 5353482d322e30 } or !{ req.ssl_hello_type 1 } { req.len 0 }
use_backend tcp_to_https if { req.ssl_sni -i corp.example.ru }
use_backend tcp_to_reality if { req.ssl_sni -i drive.example.ru }
use_backend tcp_to_metrics if { req.ssl_sni -i metrics.example.ru }
use_backend tcp_to_mtls if { req.ssl_sni -i auth.example.ru } || { req.ssl_sni -i ns1.example.ru }
# use_backend tcp_to_ocserv if { req.ssl_sni -i example.ru }
#------------------------------------------
frontend f_https #
bind abns@frontendhttps.sock accept-proxy ssl crt /etc/haproxy/certs/ ssl crt /etc/haproxy/ssl/ alpn h2,http/1.1 strict-sni tfo
http-request reject if { req.hdr(user-agent) -m sub evil }
http-request deny if { path -m sub /. } # запрет доступа к скрытым файлам
http-response set-header X-Content-Type-Options nosniff
http-response set-header X-XSS-Protection 1;mode=block
http-response set-header X-Frame-Options SAMEORIGIN
http-after-response set-header Strict-Transport-Security "max-age=16000000; includeSubDomains;" # Устанавливаем HSTS
stick-table type ip size 100k expire 5m store http_req_rate(10s)
http-request track-sc0 src
http-request deny deny_status 429 if { sc_http_req_rate(0) gt 300 }
http-request return status 200 content-type text/plain lf-string "Status 200 OK! Your IP %[src]." if { path /https-status-443 }
use_backend http_3xui_sub if { ssl_fc_sni -i corp.example.ru } { path_beg /sub-path/ } || { path_beg /jsub-path/ }
use_backend xhttp if { req.hdr(host) -i corp.example.ru } { path_beg -i /secretpath/ } || { path_beg -i /secretpath }
use_backend http_adh if { ssl_fc_sni -i corp.example.ru } { path_beg -i /secretpathdns/ } # { path_beg /dns-query/ }
# use_backend websocket if { req.hdr(Host) -i corp.example.ru } { path /ws } || { path_beg /ws/ }
frontend f_mtls # Административный доступ
bind abns@frontendmtlsadm.sock accept-proxy alpn h3,h2,http/1.1 tfo ssl-min-ver TLSv1.3 ssl crt /etc/haproxy/certs/ verify required ca-file /etc/haproxy/ca/ca.pem ca-verify-file /etc/haproxy/ca/fullca.pem crl-file /etc/haproxy/ca/revoked.crl crt-ignore-err 10,23
http-request silent-drop if { path_end /dns-query } || { path_beg /dns-query/ }
http-request reject if { req.hdr(user-agent) -m sub evil }
http-request deny if { path -m sub /. } # запрет доступа к скрытым файлам
http-after-response set-header Strict-Transport-Security "max-age=16000000; includeSubDomains;" # Устанавливаем HSTS
http-request return status 403 content-type text/plain lf-string "Your certificate has expired, please contact the administrator" if { ssl_c_verify 10 }
http-request return status 403 content-type text/plain lf-string "Your certificate has been revoked, please contact the administrator" if { ssl_c_verify 23 }
http-request return status 200 content-type text/plain lf-string "Status 200 OK! Your IP %[src]." if { path /https-status-443 }
use_backend http_3xui if { ssl_fc_sni -i corp.example.ru } { path_beg /defaultpath/ } || { path_beg /3xui-path }
use_backend stats if { path_beg -i /haproxy/ }
use_backend http_adh if { ssl_fc_sni -i ns1.example.ru }
#-- Statistics ----------------------------
frontend f_metrics_mtls
bind abns@metricshttps.sock tfo accept-proxy ssl crt /etc/haproxy/certs/ alpn h2,http/1.1 ssl-min-ver TLSv1.3 strict-sni verify required ca-file /etc/haproxy/ca/metrics.pem ca-verify-file /etc/haproxy/ca/metricsfull.pem crl-file /etc/haproxy/ca/metrics.crl crt-ignore-err 10,23
tcp-request connection accept if { src -f /etc/haproxy/geoip/RU.list }
http-request reject if { req.hdr(user-agent) -m sub evil }
http-request deny if { path -m sub /. } # запрет доступа к скрытым файлам
stick-table type ip size 100k expire 5m store http_req_rate(30s)
http-request track-sc0 src
http-request deny deny_status 429 if { sc_http_req_rate(0) gt 90 }
http-after-response set-header Strict-Transport-Security "max-age=16000000;"
http-request return status 403 content-type text/plain lf-string "Your certificate has expired, please contact the administrator" if { ssl_c_verify 10 }
http-request return status 403 content-type text/plain lf-string "Your certificate has been revoked, please contact the administrator" if { ssl_c_verify 23 }
http-request use-service prometheus-exporter if { path_beg /haproxy-exporter-0835n-rm05v-g1nry-r8h-h7dn-gu8v/ } # exporter haproxy, ссылка для prometheus ( /path_beg/metrics )
use_backend node-exporter if { path_beg -i /node-exporter-4umx-2nf46x3-qghy-7b3o-zxca-jte/ } # exporter сервера
backend node-exporter
mode http
http-request replace-path /node-exporter-4umx-2nf46x3-qghy-7b3o-zxca-jte(/)?(.*) /\2
server s1 127.0.0.1:9100
frontend f_stats
bind abns@stats.sock
stats enable
stats uri /stats
stats realm Haproxy\ Statistics
stats refresh 10s
stats show-legends
stats hide-version
stats show-modules
backend stats
mode http
http-request replace-path /haproxy(/)?(.*) /\2
server s1 abns@stats.sock
#------------------------------------------
frontend f_https_reality
bind /var/lib/haproxy/fakehttps1.sock accept-proxy ssl crt /etc/haproxy/certs/ alpn h2,http/1.1 strict-sni mode 660 user haproxy group haproxy ssl-min-ver TLSv1.3
http-request reject if { req.hdr(user-agent) -m sub evil }
acl url_discovery path /.well-known/caldav /.well-known/carddav
http-request redirect location /remote.php/dav/ code 301 if url_discovery
http-request deny if { path -m sub /. }
stick-table type ip size 100k expire 2m store http_req_rate(10s)
http-request track-sc0 src
http-request deny deny_status 429 if { sc_http_req_rate(0) gt 240 }
http-response set-header X-Content-Type-Options nosniff
http-response set-header X-XSS-Protection 1;mode=block
http-response set-header X-Frame-Options SAMEORIGIN
http-response set-header Strict-Transport-Security "max-age=16000000;" # Устанавливаем HSTS
http-request return status 200 content-type text/plain lf-string "Status 200 OK! Your IP %[src]." if { path / }
# default_backend nextcloud
#------- BACKEND --------------------------
#[ TCP Passthrough
backend tcp-ssh # Бекенд для SSH
mode tcp
option http-keep-alive
timeout http-keep-alive 30s
server ssh 127.0.0.1:22 check port 22
#backend tcp_to_ocserv #
# mode tcp
# server oc 127.0.0.1:48658 check port 48658 send-proxy
backend tcp_to_https #
mode tcp
server s1 abns@frontendhttps.sock send-proxy-v2-ssl-cn tfo check
retry-on conn-failure empty-response response-timeout
backend tcp_to_reality
mode tcp
server reality1 /x-ui/reality1.sock send-proxy tfo check
retry-on conn-failure empty-response response-timeout
backend tcp_to_mtls
mode tcp
server mtls abns@frontendmtlsadm.sock send-proxy-v2-ssl-cn tfo check
retry-on conn-failure empty-response response-timeout
backend tcp_to_metrics
mode tcp
server metrics abns@metricshttps.sock send-proxy-v2 tfo check
retry-on conn-failure empty-response response-timeout
backend http_3xui # Админ панель 3X-UI
mode http
option forwardfor if-none
http-request add-header X-Real-Ip %[src]
http-request set-header Host %[req.hdr(Host)]
http-request set-header X-Forwarded-For %[src]
http-request set-header X-Forwarded-Host %[req.hdr(host)]
http-request set-header X-Forwarded-Proto https if { ssl_fc }
server 3xui 127.0.0.1:49004 check port 49004
backend http_3xui_sub # подписка 3х
mode http
option forwardfor if-none
http-request add-header X-Real-Ip %[src]
http-request set-header Host %[req.hdr(Host)]
http-request set-header X-Forwarded-For %[src]
http-request set-header X-Forwarded-Host %[req.hdr(host)]
http-request set-header X-Forwarded-Proto https if { ssl_fc }
server 3xsub 127.0.0.1:49007 check port 49007
backend xhttp # XHTTP: Beyond REALITY
mode http
option forwardfor if-none
http-request add-header X-Real-Ip %[src]
http-request set-header Host %[req.hdr(Host)]
http-request set-header X-Forwarded-For %[src]
http-request set-header X-Forwarded-Host %[req.hdr(host)]
retry-on conn-failure empty-response response-timeout
server xhttp1 /x-ui/xhttp1.sock send-proxy tfo check
backend http_adh # DOH AdGuardHome
mode http
option forwardfor if-none
http-request add-header X-Real-Ip %[src]
http-request set-header Host %[req.hdr(Host)]
http-request set-header X-Forwarded-For %[src]
http-request set-header X-Forwarded-Host %[req.hdr(host)]
http-request set-header X-Forwarded-Proto https if { ssl_fc }
http-request replace-path /secretpathdns(/)?(.*) /\2
server dns 127.0.0.1:49005 check port 49005
#backend http_site1 # Apache2
# mode http
# option httpchk
# http-check expect status 200
# option forwardfor if-none
# http-request add-header X-Real-Ip %[src]
# http-request set-header Host %[req.hdr(Host)]
# http-request set-header X-Forwarded-For %[src]
# http-request set-header X-Forwarded-Host %[req.hdr(host)]
# http-request set-header X-Forwarded-Proto https if { ssl_fc }
# server ap2 127.0.0.1:8080 check
backend nextcloud #
mode http
option forwardfor
http-request set-header X-Forwarded-Proto https if { ssl_fc }
http-request set-header X-Forwarded-Proto http if !{ ssl_fc }
http-request set-header X-Forwarded-Host %[req.hdr(host)]
http-request set-header X-Forwarded-For %[src]
http-request add-header X-Real-Ip %[src]
http-response set-header Strict-Transport-Security max-age=157680000;
server nextcloud 127.0.0.1:49009 #check port 49009
# --- IP List -------------------------------
# Range 48658—48999 - TCP // 49001—49100 - http сервисы // 49101-49150 - WS