Это продолжение статьи про совмещение Vless, WebSocket и ssh на порту 443 силами HAProxy. В данном продолжении полностью перепишем конфигурацию HAProxy, добавим GeoIP списки для блокировки доступа, развернем личный DOH силами AdGuardHome на одном домене с WebSocket и сайтами и добавим openconnect vpn.
Начнем с базовой информации, нам потребуется 3 доменных имени например основной домен mydomain.com или my.domain.com и два дополнительных для VLESS и OCServ, допустим 1.mydomain.com ( my1.domain.com ) и 2.mydomain.com ( my2.domain.com ) соответственно, домены могут быть и бесплатными или техдомены хостера, не важно нам нужна лишь А запись на IP сервера.
HAProxy будет маршрутизировать трафик в режиме TCP для 443 порта на основе SNI, дополнительные домены пойдут на свои backend сервисы, а весь остальной или только основой уйдет на http frontend, где будут размещены защищенные сервисы на основе пути (статистика HAProxy, XUI панель, WebSocket's в лице wstunnel и DOH про него позже) а все остальное уйдет на default_backend на котором было бы неплохо разместить какой нибудь сайт.
Приступим к подготовке сервера, в данной статье я буду работать от root, так будет проще настроить методом ctrl+c ctrl+v, для начала обновим список пакетов, установим необходимые пакеты и выполним базовую настройку:
Обновляем систему apt update && apt upgrade -y
Устанавливаем пакеты apt install ocserv haproxy unzip netcat-traditional socat htop unbound unbound-anchor seccomp
Проведем базовую защиту сервера, отключим IPv6 и внесем правки в sysctl.conf, применим после внесения командой sysctl -pnano /etc/sysctl.conf
# Защита от IP-спуфинга net.ipv4.conf.all.rp_filter = 1 net.ipv4.conf.default.rp_filter = 1 # Игнорировать широковещательные запросы ICMP net.ipv4.icmp_echo_ignore_broadcasts = 1 # Защита от плохих сообщений об ошибках icmp net.ipv4.icmp_ignore_bogus_error_responses = 1 # Отключить маршрутизацию исходных пакетов net.ipv4.conf.all.accept_source_route = 0 net.ipv6.conf.all.accept_source_route = 0 net.ipv4.conf.default.accept_source_route = 0 net.ipv6.conf.default.accept_source_route = 0 # Включить exec shield ядра kernel.randomize_va_space = 1 # Блокировать SYN атаки net.ipv4.tcp_syncookies = 1 net.ipv4.tcp_max_syn_backlog = 2048 net.ipv4.tcp_synack_retries = 2 net.ipv4.tcp_syn_retries = 5 # Игнорировать марсианские пакеты net.ipv4.conf.all.log_martians = 1 net.ipv4.icmp_ignore_bogus_error_responses = 1 # Игнорировать перенаправления отправки net.ipv4.conf.all.send_redirects = 0 net.ipv4.conf.default.send_redirects = 0 # Игнорировать перенаправления ICMP net.ipv4.conf.all.accept_redirects = 0 net.ipv6.conf.all.accept_redirects = 0 net.ipv4.conf.default.accept_redirects = 0 net.ipv6.conf.default.accept_redirects = 0 net.ipv4.conf.all.secure_redirects = 0 net.ipv4.conf.default.secure_redirects = 0 # Disabled IPv6 net.ipv6.conf.all.disable_ipv6 = 1 net.ipv6.conf.default.disable_ipv6 = 1 net.ipv6.conf.lo.disable_ipv6 = 1
Разрешим подключения на ssh и http/sufw allow 22/tcpufw allow 80,443/tcp
Активируем фаерволufw enable
Теперь настроим unbound в качестве локального резолвера (шаг не обязательный и можно пропустить)
Остановим systemd resolved systemctl stop systemd-resolved.service
Отключим DNSStubListenersh -c 'echo DNSStubListener=no' >> /etc/systemd/resolved.conf
Удалим resolv.confrm -f /etc/resolv.conf
Пропишем localhost в качестве ответчика DNSecho 'nameserver 127.0.0.1' >> /etc/resolv.conf
Запустим systemd resovedsystemctl start systemd-resolved.service
Подготовим unbound:
Скачаем список корневых DNS серверовcurl -o /etc/unbound/root.hints https://www.internic.net/domain/named.cache
Теперь создадим скрипт для автоматического обновления корневых DNS
echo '#!/bin/sh curl -o /etc/unbound/root.hints https://www.internic.net/domain/named.cache' > /etc/cron.daily/root_hints
Сделаем его исполняемымchmod 700 /etc/cron.daily/root_hints
Сгенерируем ключ DNSSEC для валидацииunbound-anchor -a /etc/unbound/root.key
Поменяем владельца для root.hints root.keychown unbound:unbound /etc/unbound/root.hints chown unbound:unbound /etc/unbound/root.key
Теперь создадим файл конфигурацииnano /etc/unbound/unbound.conf.d/dns.conf и внесем следующий код
Скрытый текст
server: port: 53 verbosity: 0 num-threads: 1 outgoing-range: 512 num-queries-per-thread: 1024 msg-cache-size: 512m rrset-cache-size: 64m cache-max-ttl: 21600 cache-min-ttl: 2600 infra-host-ttl: 60 infra-lame-ttl: 120 interface: 127.0.0.1 outgoing-interface: $IP access-control: 127.0.0.1/8 allow_snoop root-hints: "/etc/unbound/root.hints" username: unbound directory: "/etc/unbound" logfile: "/var/log/unbound.log" log-time-ascii: yes use-syslog: no do-not-query-localhost: no qname-minimisation: yes minimal-responses: yes prefetch: yes use-caps-for-id: yes rrset-roundrobin: yes hide-identity: yes hide-version: yes so-rcvbuf: 4m so-sndbuf: 4m do-ip4: yes do-ip6: no do-udp: yes do-tcp: yes deny-any: yes
В конфигурации do-not-query-localhost: no позволит нам работать на localhost и отвечать на запросы, num-threads: 1 отвечает за кол-во потоков (по ядрам), interface: 127.0.0.1 включит прослушивание только на localhost с портом 53, outgoing-interface: $IP где $IP это IPv4 вашего сервера полученный командой ip a или панели хостера, а access-control: 127.0.0.1/8 allow_snoop разрешит трафик с localhost и позволит делать диагностику запросов.
Теперь настройка HAProxy
Сначала установим и создадим аккаунт acme для получения сертификатов
Добавим пользователя root в группу haproxyadduser root haproxy
Создадим директорию для acmemkdir /usr/local/share/acme.sh/
Скачаем acme.sh git clone https://github.com/acmesh-official/acme.sh.git
Перейдем в каталог с acme cd acme.sh/
Выполним установку в созданную директорию./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/
Дадим права на запускchmod 755 /usr/local/share/acme.sh/
Создадим директорию для хранения сертификатов HAProxymkdir /etc/haproxy/certs
Поменяем создателяchown haproxy:haproxy /etc/haproxy/certs
Установим права chmod 770 /etc/haproxy/certs
Выполним инициализацию acme зарегистрируем аккаунт и поменяем default сервер выдачи youremail@example.com замените на свою почтуacme.sh --set-default-ca --server letsencrypt --register-account -m youremail@example.com
На выходе у вас должна быть строка вида "ACCOUNT_THUMBPRINT 'diosrg8siojgiodsjhosdijhoisdfjsd' " сохраняем ее она потребуется дальше.
Создадим базовую конфигурацию HAProxy для первого запуска и получения сертификатов.
Создадим ключи DH, процедура генерации ключа может занять продолжительное время! openssl dhparam -out /etc/haproxy/dh4096.pem 4096
Очистим дефолтую конфигурациюecho '' > /etc/haproxy/haproxy.cfg
Откроем конфигnano /etc/haproxy/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 'diosrg8siojgiodsjhosdijhoisdfjsd' # поменять на полученный из acme stats timeout 30s user haproxy group haproxy daemon 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-options prefer-client-ciphers no-tls-tickets ssl-min-ver TLSv1.2 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-options no-tls-tickets ssl-min-ver TLSv1.2 ssl-dh-param-file /etc/haproxy/dh4096.pem # Тюнинг http/2 для WebSocket tune.h2.initial-window-size 536870912 # Увеличиваем начальный размер окна для входящих и исходящих соединений. tune.h2.fe.max-concurrent-streams 512 # Установим кол-во одновременных потоков на входящие соединений. tune.h2.fe.glitches-threshold 1024 # Установим порог автоматического завершения соединения при превышении заданного кол-ва ошибок. tune.h2.be.max-concurrent-streams 512 # Установим кол-во одновременных потоков на исходящие соединений. tune.h2.be.glitches-threshold 1024 # Установим порог автоматического завершения соединения при превышении заданного кол-ва ошибок в 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 nameserver ns2 127.0.0.53: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 stats mode http bind 127.0.0.1:58080 stats enable stats uri /stats stats realm Haproxy\ Statistics stats refresh 10s stats auth sdvhsis3w:opsdiv90-ikps stats show-legends stats hide-version #------------------------------------------ frontend http 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 } 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 redirect scheme https unless { ssl_fc } frontend tcp bind *:443 # ssl passthrough mode tcp option tcplog tcp-request inspect-delay 6s # Защитим от перегрузки, если с одного IP за 10сек открыто более 30 соедиенний, отклоним все запросы и закроем все соединения # Нужно учитывать что WsTunnel с активным параметром --connection-min-idle <n> будет занимать указанное колличество при старте # У VLESS так же есть мультилексор, тоже нужно учитывать при настройке # Может так случиться если несколько клиентов в одно и тоже время с одного IP решат подключиться, все они потеряют доступ к серверу. # Для себя я вывел что 30 одновременных подключений с одного IP на случай если например дома выключат свет 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 30 } 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 } # Я бы не рекомендовал, вы лешитесь доступа если haproxy упадет default_backend tcp_to_https frontend https bind 127.0.0.1:444 accept-proxy ssl crt /etc/haproxy/certs alpn h2,http/1.1 strict-sni http-request reject if { req.hdr(user-agent) -m sub evil } http-request reject if { path -m sub /. } http-response set-header Strict-Transport-Security "max-age=16000000; includeSubDomains; preload;" # Устанавливаем HSTS use_backend http_stats if { ssl_fc_sni -i mydomain.com } { path_beg /ha-stats-secret-path/ } #->-----tcp-backend-------------------------- backend tcp_to_https mode tcp server https 127.0.0.1:444 send-proxy-v2-ssl-cn check port 444 backend tcp-ssh mode tcp option http-keep-alive timeout http-keep-alive 30s server ssh 127.0.0.1:22 check port 22 #->->---http-backend------------------------- backend http_stats mode http http-request replace-path /ha-stats-secret-path(/)?(.*) /\2 server stats 127.0.0.1:58080
Теперь можно проверить конфигурацию на ошибки haproxy -f /etc/haproxy/haproxy.cfg -c
Если ошибок нет, запускаемsystemctl start haproxy
Теперь приступим к получению первого сертификата для основного домена, для это выполним следующее:acme.sh --issue -d mydomain.com --stateless
Если все настроено было правильно, HAProxy ответ на запрос LE и вы увидите полученный сертификат, и путь к нему, но это еще не все, теперь установим сертификат на "горячую"
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 mydomain.com --deploy-hook haproxy
Теперь можем перейти по адресу mydomain.com, если сайт открылся как https все сделано правильно, в любом случае можно для верности перезапустить сервис если сайт не открылся как https, при данном переходе на сайт у вас будет 503 ошибка, это нормально.systemctl restart haproxy
Теперь подготовим GeoIP списки для фильтрации стран и континентов.
На просторах интернета нашел на github списки GeoIP, скажем автору Loyalsoldier спасибо за поддержание списков в актуальном состоянии.
Написал скрипт для выгрузки с репозитория актуального списка преобразования его в continent_code и country_iso_code.
Создадим директории для скрипта и хранения GeoIP списковmkdir /etc/haproxy/geoip .mkdir /etc/haproxy/acl .mkdir /etc/haproxy/script .
Создадим скрипт в каталоге /etc/haproxy/scriptnano /etc/haproxy/script/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" # Переименуем csv: find $GEOIP_TEMP -depth -type f -name '*IPv4.csv' -exec mv {} $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 systemctl restart haproxy
Теперь имея списки в директории /etc/haproxy/geoip можем настраивать региональные блокировки, прошу учесть что списки GeoIP не точные, вы можете случайно заблокировать себя!
Используя оператор 'или' добавим в http frontend следующие содержимое после
http-request reject if { req.hdr(user-agent) -m sub evil }
# Заблочим Африку, Азию, Южную Америку, Океанию, Европу и Северную Америку не блочим для запросов ACME http-request reject if { src -f /etc/haproxy/geoip/AF.acl } || { src -f /etc/haproxy/geoip/AS.acl } || { src -f /etc/haproxy/geoip/AN.acl } || { src -f /etc/haproxy/geoip/OC.acl } || { src -f /etc/haproxy/geoip/SA.acl }
Теперь добавим в секцию TCP frontend после tcp-request inspect-delay 6s
# Заблочим Африку, Азию, Южную/Северную Америку, Океанию и Антарктику tcp-request connection reject if { src -f /etc/haproxy/geoip/AF.acl } || { src -f /etc/haproxy/geoip/AS.acl } || { src -f /etc/haproxy/geoip/AN.acl } || { src -f /etc/haproxy/geoip/OC.acl } || { src -f /etc/haproxy/geoip/SA.acl } || { src -f /etc/haproxy/geoip/NA.acl }
Настало время добавить в https frontend после http-request reject if { path -m sub /. }
http-request deny deny_status 403 if !{ src -f /etc/haproxy/geoip/RU.list }
Учтите что вашего IP адреса может не оказаться и вы потеряете доступ к https, по этому в разделах выше нет данных правил, вы можете составить свои правила для своих подсетей назвать их mylist.acl разместить в /etc/haproxy/acl и добавить их в списки на разрешение
http-request deny deny_status 403 if !{ src -f /etc/haproxy/geoip/RU.list } || !{ src -f /etc/haproxy/mylist.acl }
Оператор || он же или, т.е. если ваш IP будет присутствовать хотя-бы в одном из правил, вы получите доступ. Использовать с осторожностью при работе с белым списком.
Параметр connection в TCP frontend работает с соединением на самой ранней точке, в случае правила запрета будет сбрасывает соединение без уведомления клиента и записи в журнал, можно изменить на content который в свою очередь запишет в журнал и сбросит соединение без ответа клиенту, в статистике HAProxy можно будет отслеживать сброшенные соединения. Правила connection должны быть над правилами content.
Применяется для разрыва соединения с нежелательных IP-адресов.
Параметр silent-drop в http frontend отвечает за тихое отключение клиента без уведомления, можно заменить на deny который отправит ответ клиенту о закрытии соединения. Как говорит документация "тихое отбрасывание повлияет на любые брандмауэры с отслеживанием состояния или прокси-серверы между балансировщиком нагрузки и клиентом, поскольку они часто будут удерживать соединение, не зная, что оно было отключено." эффективнее использовать deny, я же комбинирую правила, для IP-адресов deny, а для user-agent и скрытых файлой "/." silent-drop.
Если установить http-request reject if { req.hdr(user-agent) -m len le 32 } в секции frontend https - перестанет работать DOH.
Конфигурация после добавления GeoIP
Скрытый текст
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 'diosrg8siojgiodsjhosdijhoisdfjsd' # поменять на полученный из acme stats timeout 30s user haproxy group haproxy daemon 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-options prefer-client-ciphers no-tls-tickets ssl-min-ver TLSv1.2 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-options no-tls-tickets ssl-min-ver TLSv1.2 ssl-dh-param-file /etc/haproxy/dh4096.pem # Тюнинг http/2 для WebSocket tune.h2.initial-window-size 536870912 # Увеличиваем начальный размер окна для входящих и исходящих соединений. tune.h2.fe.max-concurrent-streams 512 # Установим кол-во одновременных потоков на входящие соединений. tune.h2.fe.glitches-threshold 1024 # Установим порог автоматического завершения соединения при превышении заданного кол-ва ошибок. tune.h2.be.max-concurrent-streams 512 # Установим кол-во одновременных потоков на исходящие соединений. tune.h2.be.glitches-threshold 1024 # Установим порог автоматического завершения соединения при превышении заданного кол-ва ошибок в 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 nameserver ns2 127.0.0.53: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 stats mode http bind 127.0.0.1:58080 stats enable stats uri /stats stats realm Haproxy\ Statistics stats refresh 10s stats auth sdvhsis3w:opsdiv90-ikps stats show-legends stats hide-version #------------------------------------------ frontend http 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 } # Защитим от перегрузки # Если в течении 1m будет выполнено более 10 запросов отклоним ответом 429 Too Many Requests stick-table type ip size 100k expire 2m store http_req_rate(30s) http-request track-sc0 src # Отклоним с ответом http-request deny deny_status content-type text/html lf-string "<p>Per our policy, you are limited to 10 requests per minute, but you have exceeded that limit with %[sc_http_req_rate(0)] requests per minute.</p>" if { sc_http_req_rate(0) gt 30 } # Заблочим Африку, Азию, Южную Америку, Океанию, Европу и Северную Америку не блочим для запросов ACME http-request deny deny_status 403 if { src -f /etc/haproxy/geoip/AF.acl } || { src -f /etc/haproxy/geoip/AS.acl } || { src -f /etc/haproxy/geoip/AN.acl } || { src -f /etc/haproxy/geoip/OC.acl } || { src -f /etc/haproxy/geoip/SA.acl } 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 redirect scheme https unless { ssl_fc } frontend tcp bind *:443 # ssl passthrough mode tcp option tcplog tcp-request inspect-delay 6s # Заблочим Африку, Азию, Южную/Северную Америку, Океанию и Антарктику tcp-request connection reject if { src -f /etc/haproxy/geoip/AF.acl } || { src -f /etc/haproxy/geoip/AS.acl } || { src -f /etc/haproxy/geoip/AN.acl } || { src -f /etc/haproxy/geoip/OC.acl } || { src -f /etc/haproxy/geoip/SA.acl } || { src -f /etc/haproxy/geoip/NA.acl } # Защитим от перегрузки, если с одного IP за 10сек открыто более 30 соедиенний, отклоним все запросы и закроем все соединения # Нужно учитывать что WsTunnel с активным параметром --connection-min-idle <n> будет занимать указанное колличество при старте # У VLESS так же есть мультилексор, тоже нужно учитывать при настройке # Может так случиться если несколько клиентов в одно и тоже время с одного IP решат подключиться, все они потеряют доступ к серверу. # Для себя я вывел что 30 одновременных подключений с одного IP на случай если например дома выключат свет 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 30 } 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 } # Я бы не рекомендовал, вы лешитесь доступа если haproxy упадет default_backend tcp_to_https frontend https bind 127.0.0.1:444 accept-proxy ssl crt /etc/haproxy/certs alpn h2,http/1.1 strict-sni http-request reject if { req.hdr(user-agent) -m sub evil } http-request reject if { path -m sub /. } # Защитим от перегрузки, если перестанет работать WsTunnel (чем больше клиентов тем выше установить счетчик) # Если в течении 30с будет выполнено более 30 запросов отклоним ответом 429 Too Many Requests stick-table type ip size 100k expire 2m store http_req_rate(30s) http-request track-sc0 src # Отклоним с ответом о превышенном кол-ве запросов http-request deny deny_status content-type text/html lf-string "<p>Per our policy, you are limited to 30 requests per 30s, but you have exceeded that limit with %[sc_http_req_rate(0)] requests per 30s.</p>" if { sc_http_req_rate(0) gt 30 } # GeoIP блокировка всех кроме RU и белого списка (можете воспользоваться content-type из примера выше, сообщить о запрете доступа с их IP вывести IP адрес.) # Добавьте свой IP перед использованием в белый список http-request deny deny_status 403 if !{ src -f /etc/haproxy/geoip/RU.list } || !{ src -f /etc/haproxy/mylist.acl } http-response set-header Strict-Transport-Security "max-age=16000000; includeSubDomains; preload;" # Устанавливаем HSTS use_backend http_stats if { ssl_fc_sni -i mydomain.com } { path_beg /ha-stats-secret-path/ } #->-----tcp-backend-------------------------- backend tcp_to_https mode tcp server https 127.0.0.1:444 send-proxy-v2-ssl-cn check port 444 backend tcp-ssh mode tcp option http-keep-alive timeout http-keep-alive 30s server ssh 127.0.0.1:22 check port 22 #->->---http-backend------------------------- backend http_stats mode http http-request replace-path /ha-stats-secret-path(/)?(.*) /\2 server stats 127.0.0.1:58080
Переходим к настройке сервисов
Начнем добавлять сервисы с OpenConnect
Для начала нужно получить сертификаты, для этого сделаем пару скриптов, начнем с post-hook-oc.sh
Разместим скрипты рядом с acme.sh в директории .acme.sh
находясь в домашней директории root выполняемnano .acme.sh/post-hook-oc.sh
#!/bin/sh cp /root/.acme.sh/1.mydomain.com_ecc/fullchan.cer /etc/ocserv/fullchan.cer cp /root/.acme.sh/1.mydomain.com_ecc/1.mydomain.com.key /etc/ocserv/key.pem systemctl restart ocserv
И второй hook-renew с идентичным содержимым первомуnano .acme.sh/hook-renew-oc.sh
Теперь сделаем их исполняемымиchmod 700 .acme.sh/post-hook-oc.sh && chmod 700 .acme.sh/hook-renew-oc.sh
Подготовим запросacme.sh --issue -d 1.mydomain.com --stateless --post-hook /root/.acme.sh/post-hook-oc.sh --renew-hook /root/.acme.sh/hook-renew-oc.sh
Данный запрос после успешного выполнения создаст сертификаты для ocserv в /etc/ocserv, при успешном обновлении он обновит сертификат /etc/ocserv и перезапустит сервис.
Подготовим конфигурацию OCServ
Скрытый текст
# адрес подкл: https://1.mydomain.com/?my-secret-path # cd /etc/ocserv > ocpasswd username # имя tun device = ocserv socket-file = ocserv.sock chroot-dir = /var/lib/ocserv #pid-file = /var/run/ocserv.pid #mtu = 1420 #буфер mtu #output-buffer = 10 # глобальное ограничение скорости, байт/сек #rx-data-per-sec = 40000 #tx-data-per-sec = 40000 # уровень логгирования, 1 basic, 2 info, 3 debug, 4 http, 8 sensitive, 9 TLS, default 0 он же 2 log-level = 2 # Включаем прокси протокол listen-proxy-proto = true # изоляция процессов, по умолчанию выкл, для включения прописать 'true' и установить apt install seccomp, снижает производительность ~2% (ocserv должен быть собран с данной опцией) isolate-workers = false # ограничение кол-ва входящих соединений, для предотвращения перегруза rate-limit-ms = 100 # keepalive должен быть меньше чем timeout server и timeout client на haproxy, у меня 1 минута, уст в 40 сек keepalive = 40 # Авторизация через PAM с gid выше 3000 #auth = "pam[gid-min=3000]" # Авторизация через файл auth = "plain[passwd=/etc/ocserv/ocpasswd]" # таймаут авторизации auth-timeout = 240 # время запрета авторизации после не удачной попытки min-reauth-time = 300 # Блок клиентов после не удачной авторизации, по умолчанию 10баллов = 1 попытка max-ban-score = 80 # время сбороса баллов ban-reset-time = 1200 # Уст время жизни куки, для повторой авторизации без пароля, в случае отвала, например для роуминга между сетями cookie-timeout = 600 # разрешим роуминг deny-roaming = false # Постоянные куки, ввод пароля 1 раз #persistent-cookies = true # время переподключения, запроса ключа rekey-time = 172800 # метод переключения, ssl бесшовное, new-tunnel использовать только есть проблемы с ssl rekey-method = ssl # Слушаем на localhost listen-host = 127.0.0.1 # То же самое, но для UDP. Но открывать мы его не будем на firewall #udp-listen-host = $IPADDR # Номер порта для входящих подключений tcp-port = 29999 # То же самое для UDP #udp-port = 443 run-as-user = ocserv run-as-group = ocserv # LE сертификат вашего сервера. #server-cert = /etc/letsencrypt/live/my.domain.ru/fullchain.pem #server-key = /etc/letsencrypt/live/my.domain.ru/privkey.pem # acme сертификат вашего сервера. server-cert = /etc/ocserv/fullchain.cer server-key = /etc/ocserv/1.mydomain.com.key # banner = "Server" #pre-login-banner = "Welcome" # Можно ограничить количество одновременно подключенных клиентов max-clients = 32 # И одновременно подключенных одинаковых клиентов (с одинаковым логином), 0 - безлимитно max-same-clients = 2 # ваш домен например: 1.mydomain.com default-domain = 1.mydomain.com # диапазон IP-адресов, которые вы будете выдавать подключенным клиентам ipv4-network = 10.12.14.0 ipv4-netmask = 255.255.255.0 # альтернативно #ipv4-network = 10.12.14.0/24 # предотвращает утечку DNS, должно быть true # включать при если будет использоваться для выхода в интернет tunnel-all-dns = false # DNS-сервер, которые будут использовать ваши клиенты. # Их может быть несколько dns = 8.8.8.8 dns = 1.1.1.1 # маршруты, которые будут переданы клиентам: какие диапазоны IP нужно будет отправлять через VPN route = 10.12.14.0/255.255.255.0 # route = 192.168.0.0/255.255.0.0 # route = fef4:db8:1000:1001::/64 # default = все # route = default # маршруты которые не должны маршрутизироваться # no-route = 192.168.5.0/255.255.255.0 # сохранение ip пользователя когда это возможно predictable-ips = true # файлы для переопределения параметров конфигурации для отдельных юзеров или групп #config-per-user = /etc/ocserv/config-per-user/ #config-per-group = /etc/ocserv/config-per-group/ # должно быть true если мы подключаемся клиентами от Cisco cisco-client-compat = true # Включаем маскировку camouflage = true camouflage_secret = "my-secret-path" camouflage_realm = "Admin Panel" # для выгрузки в Prometheus #use-occtl = true # Скрипт при подключении и отключении пользователя, можно вывести в ТГ или log файл #connect-script = /usr/bin/myscript #disconnect-script = /usr/bin/myscript # host-update от клиента #host-update-script = /usr/bin/myhostnamescript # Для DTLS #Dead peer detection in seconds #dpd = 90 # Dead peer detection for mobile clients #mobile-dpd = 1800 # DTLS если не пришел UDP в течении (сек) перейти на tls #switch-to-tcp-timeout = 25 # обнаружение mtu #try-mtu-discovery = false # HTTP headers included-http-headers = Strict-Transport-Security: max-age=31536000 ; includeSubDomains included-http-headers = X-Frame-Options: deny included-http-headers = X-Content-Type-Options: nosniff included-http-headers = Content-Security-Policy: default-src 'none' included-http-headers = X-Permitted-Cross-Domain-Policies: none included-http-headers = Referrer-Policy: no-referrer included-http-headers = Clear-Site-Data: "cache","cookies","storage" included-http-headers = Cross-Origin-Embedder-Policy: require-corp included-http-headers = Cross-Origin-Opener-Policy: same-origin included-http-headers = Cross-Origin-Resource-Policy: same-origin included-http-headers = X-XSS-Protection: 0 included-http-headers = Pragma: no-cache included-http-headers = Cache-control: no-store, no-cache
Теперь можно запустить сервис systemctl start ocserv
И проверим егоsystemctl status ocserv
Если ошибок нет создадим первого пользователя
Перейдем в /etc/ocserv cd /etc/ocserv
Выполним ocpasswd nameuser дважды введем пароль и перезапустим сервисsystemctl restart ocserv
Сервис будет слушать localhost и порт 29999 для входящих tcp соединений с поддержкой прокси протокола, udp и DTLS мы не используем.
При данных настройках внешнего подключения не будет, это только для внутренней сети, для доступа в интернет нужно разрешить форвард трафика. Как это сделать будет в конце.
Настало время добавить ocserv в HAProxy для этого в секции TCP frontend добавить следующую строку после tcp-request content accept if { req_ssl_hello_type 1 } or !{ req_ssl_hello_type 1 }use_backend ocserv { req.ssl_sni -i 1.mydomain.com }
И добавим backend
backend ocserv mode tcp server s1 127.0.0.1:29999 send-proxy check port 29999
И перезапустим HAProxy командой systemctl restart haproxy
Проверим что HAProxy запущен systemctl status haproxy
Теперь можно проверить подключение, адрес будет выглядеть так (я заменил : на [:] что бы не считалось ссылкой) https[:]//1.mydomain.com/?my-secret-path
Переходим в браузер вбиваем наш домен если все работает как должно, появится basic авторизация http, можем подключать клиентов.
На очереди X-UI
Развернем docker
Самый простой способ использовать официальный скриптcurl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh
Проверим запуск docker командой docker ps
Теперь развернем X-UI
Создадим директорию с именем x-ui mkdir /root/x-ui
Создадим docker compose файл со следующим содержимымnano /root/x-ui/docker-compose.yml
--- services: xui: image: alireza7/x-ui container_name: x-ui hostname: $yourhostname volumes: - $PWD/db/:/etc/x-ui/ - $PWD/cert/:/root/cert/ environment: XRAY_VMESS_AEAD_FORCED: "false" tty: true network_mode: host restart: unless-stopped
Теперь запустим наш контейнер docker compose up -d
По умолчанию панель запускается на порту 54321 запомним это
Получим сертификаты для панели
Для начала создадим post и renew скрипты все шаги повторить из ocserv, отличается только содержимоеnano .acme.sh/post-hook-xui.sh
#!/bin/sh cp /root/.acme.sh/2.mydomain.com_ecc/fullchain.cer /root/x-ui/cert/cert.pem cp /root/.acme.sh/2.mydomain.com_ecc/2.mydomain.com.key /root/x-ui/cert/key.pem cd /root/x-ui && docker compose down && docker compose up -d
Подготовим запросacme.sh --issue -d 2.mydomain.com --stateless --post-hook /root/.acme.sh/post-hook-xui.sh --renew-hook /root/.acme.sh/hook-renew-xui.sh
Подготовим HAProxy добавим админ панель в https frontend после use_backend http_statsuse_backend http_xui if { ssl_fc_sni -i 2.mydomain.com } { path_beg /xui/ } || { path /xui }
Напишем backend
backend http_xui mode http option httpchk GET /xui/ option forwardfor if-none 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-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)] server s1 127.0.0.1:54321 check
Сохраняем и перезапускаем haproxy.
Переходим mydomain.com/xui в браузере открылась панель, все ок значит, панель ругается на дефолтый порт, поменяем его на 50000 и самое главное добавим панели путь на /my-secret-path2/ и обновим конфигурацию HAProxy.
Изменим use_backend http_xui if { ssl_fc_sni -i 2.mydomain.com } на use_backend http_xui if { ssl_fc_sni -i 2.mydomain.com } { path_beg /my-secret-path2/ }
И отредактируем backend
backend http_xui mode http option httpchk GET /my-secret-path2/xui/ option forwardfor if-none 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-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)] server s1 127.0.0.1:50000 check
После перезапустим haproxy и проверим доступность X-UI перейдя по адресу mydomain.com/my-secret-path2/xui если открылась админ панель значит все ок.
Настроим VLESS, дефолтый логин и пароль от панели admin
Переходим в подключения создаем подключение, протокол VLESS устанавливаем ему порт 50001, протокол передачи TCP (RAW), включаете PROXY Protocol, активируете External Proxy, безопасность TLS, указываете sni сертификата 2.mydomain.com, шифры auto, указывайте uTLS я использую firefox, пропишем путь для сертификата /root/cert/cert.pem и для ключа /root/cert/key.pem , активируем Sniffing, оставляем по дефолту, теперь добавим в HAProxy
В разделе TCP frontend добавляем под ocservuse_backend vless if { req.ssl_sni -i 2.mydomain.com}
Добавим backend vless
backend vless mode tcp timeout tunnel 1h server s1 127.0.0.1:50001 send-proxy check port 50001
Перезапустим HAProxy, и попробуем подключиться для этого с панели по QR коду и скопировав данные добавляете в приложение (я пользуюсь nekobox) и активируете его, сайты открываются, трафик идет, а в панели клиент горит как подключенный, значит все ок.
Добавим поддержку DNS over HTTPS
Создаем директорию mkdir /root/adh/
Напишем docker compose
--- services: adguardhome: image: adguard/adguardhome container_name: adguardhome volumes: - ./cert:/opt/adguardhome/cert - ./conf:/opt/adguardhome/conf - ./work:/opt/adguardhome/work - ./log:/var/log/AdGuardHome restart: unless-stopped network_mode: host
Добавим конфигурацию mkdir /root/adh/confnano /root/adh/conf/AdGuardHome.yaml
Скрытый текст
http: pprof: port: 6060 enabled: false address: 0.0.0.0:30000 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.1 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
Прямого доступа к панели нет, логин и пароль demo
Добавим dns-query в HAProxy
Под use_backend http_xui добавим use_backend dns_query if { ssl_fc_sni -i mydomain.com } { path_beg /dns-query/ } || { path /dns-query }
Теперь добавим backend
backend dns_query mode http option httpchk option forwardfor if-none 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-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)] server doh 127.0.0.1:30000
И перезапустим haproxy
Добавляем в любой клиент с поддержкой DoH адрес htpps[:]//mydomain.com/dns-query и проверяем проходят ли запросы, если да приступим к защите сервиса
Под use_backend добавим default_backend http_adh
И добавим backend под backend dns_query
backend http_adh mode http server adh 127.0.0.1:30000
Перезапускаем, переходим по нашему домену mydomain.com и у нас должна открыться панель, логин и пароль demo, настроим клиентов, создадим клиента например mydevice на вкладке клиенты, теперь добавим его во вкладке DNS внизу в раздел разрешенные клиенты.
Проверим работоспособность клиента https[:]//mydomain.com/dns-query/mydevice и проверяем проходят ли запросы, если да закончим настройку клиентов, позже можно добавить еще. Уберем default_backend http_adh и полностью закомментируем backend http_adh , перезапустим haproxy.
Настроим Wstunnel я подыму один сервис socks5, вы можете масштабировать под нужное кол-во.
Создадим директорию в etc:mkdir /etc/wstunnelПерейдем в нее:cd /etc/wstunnelЗагрузим релиз wstunnel:wget https://github.com/erebe/wstunnel/releases/download/v10.1.8/wstunnel_10.1.8_linux_amd64.tar.gz
Создадим пользователя под которым будем запускать на wstunnel:adduser --system wstunnel
Теперь добавим группу:addgroup wstunnel
Теперь добавим нашего пользователя в группу:usermod -aG wstunnel wstunnel
Распакуем tar -xvf wstunnel_10.1.8_linux_amd64.tar.gz
У нас в директории появились файл нас интересует только файл wstunnel, поменяем владельца файла на созданного пользователяchown wstunnel:wstunnel ./wstunnel
Сделаем его исполняемымchmod 700 ./wstunnel
Теперь напишем systemd-юнит для запуска наших скриптовСоздадим файл командойsudo nano /etc/systemd/system/wstunnel-socks.service со следующим содержимым:
[Unit] Description=Start & Check Wstunnel socks5 After=network.target After=haproxy.service [Service] WorkingDirectory=/etc/wstunnel PIDFile=/etc/wstunnel/wstunnel-socks5.pid ExecStart=/etc/wstunnel/wstunnel server -r ws-socks-secret-path ws://127.0.0.1:40001 --websocket-ping-frequency-sec 20 Restart=always RestartSec=10 User=wstunnel Group=wstunnel [Install] WantedBy=multi-user.target
Дополнительно но не обязательно, напишем скрипт проверки туннеля, порты добавляются через пробел, сервисы через пробел с кавычками. nano /etc/wstunnel/check-all-wstunnel.sh
и сделаем его исполняемым chmod 700 /etc/wstunnel/check-all-wstunnel.sh
#!/bin/bash # Установим часовой пояс для правильного отображения времени в логе export TZ=Europe/Moscow # Цель для пинга TARGET="127.0.0.1" # Массив портов PORTS=(40001) # Массив сервисов SERVICES=("wstunnel-socks5-run.service") # Для добавления добавить порт и сервис в последовательности # Проверка работоспособности check_tunnel() { local port=$1 local service=$2 # Проверка открытого порта через netcat if ! nc -z $TARGET $port; then echo "$(date '+%Y-%m-%d %H:%M:%S') Сервис на порту $port не работает... Перезапуск $service" >> /var/log/wstunnel-chk.log systemctl restart $service else echo "$(date '+%Y-%m-%d %H:%M:%S') Сервис на порту $port работает." fi } # Запуск проверки массива портов for i in "${!PORTS[@]}"; do check_tunnel ${PORTS[$i]} ${SERVICES[$i]} done
Добавим его в cron на проверку каждые 5 минcrontab -e
*/5 * * * * /etc/wstunnel/check-*.sh
Добавим в HAProxy use_backend ws-socks if { ssl_fc_sni -i mydomain.com } { path /ws-socks-secret-path } || { path_beg /ws-socks-secret-path/ }
И раздел backend
backend ws-socks mode http option http-keep-alive timeout http-keep-alive 25s timeout tunnel 1h server s1 127.0.0.1:40001 check
Перезапускаем HAProxy.
На клиента скачиваем Wstunnel, распаковываем для примера будет windows
В папке с файлом wstunnel создаете файл start.cmd со следующим содержимымwstunnel client -P ws-socks-secret-path -L socks5://127.0.0.1:1080 wss://mydomain.com:443 -с 7 --tls-verify-certificate --websocket-ping-frequency-sec 20
Настраиваете браузер на использование socks5 сервера 127.0.0.1 и порта 1080 включите перенаправление dns.
Остается только добавить какой нибудь безобидный сайт с котиками для этого добавьте под use_backend в разделе https backenddefault_backend cats
И секцию backend
backend cats mode http option httpchk option forwardfor if-none 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-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)] server s1 127.0.0.1:[ваш порт сайта]
Заключение
В статье мы разобрали работу с GeoIP в HAProxy, сформировали списки, защитили наши сервисы от определенного кол-ва ботов через отсечение континентов от наших frontend 'ов, показал пример как можно маршрутизировать OCServ, VLESS, и основной домен с сайтами и добавил ssh на 443 порт.
Полная конфигурация HAProxy
Скрытый текст
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 'diosrg8siojgiodsjhosdijhoisdfjsd' # поменять на полученный из acme stats timeout 30s user haproxy group haproxy daemon 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-options prefer-client-ciphers no-tls-tickets ssl-min-ver TLSv1.2 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-options no-tls-tickets ssl-min-ver TLSv1.2 ssl-dh-param-file /etc/haproxy/dh4096.pem # Тюнинг http/2 для WebSocket tune.h2.initial-window-size 536870912 # Увеличиваем начальный размер окна для входящих и исходящих соединений. tune.h2.fe.max-concurrent-streams 512 # Установим кол-во одновременных потоков на входящие соединений. tune.h2.fe.glitches-threshold 1024 # Установим порог автоматического завершения соединения при превышении заданного кол-ва ошибок. tune.h2.be.max-concurrent-streams 512 # Установим кол-во одновременных потоков на исходящие соединений. tune.h2.be.glitches-threshold 1024 # Установим порог автоматического завершения соединения при превышении заданного кол-ва ошибок в 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 nameserver ns2 127.0.0.53: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 stats mode http bind 127.0.0.1:58080 stats enable stats uri /stats stats realm Haproxy\ Statistics stats refresh 10s stats auth sdvhsis3w:opsdiv90-ikps stats show-legends stats hide-version #------------------------------------------ frontend http 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 } # Защитим от перегрузки # Если в течении 1m б��дет выполнено более 10 запросов отклоним ответом 429 Too Many Requests stick-table type ip size 100k expire 2m store http_req_rate(30s) http-request track-sc0 src # Отклоним с ответом http-request deny deny_status content-type text/html lf-string "<p>Per our policy, you are limited to 10 requests per minute, but you have exceeded that limit with %[sc_http_req_rate(0)] requests per minute.</p>" if { sc_http_req_rate(0) gt 30 } # Заблочим Африку, Азию, Южную Америку, Океанию, Европу и Северную Америку не блочим для запросов ACME http-request deny deny_status 403 if { src -f /etc/haproxy/geoip/AF.acl } || { src -f /etc/haproxy/geoip/AS.acl } || { src -f /etc/haproxy/geoip/AN.acl } || { src -f /etc/haproxy/geoip/OC.acl } || { src -f /etc/haproxy/geoip/SA.acl } 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 redirect scheme https unless { ssl_fc } frontend tcp bind *:443 # ssl passthrough mode tcp option tcplog tcp-request inspect-delay 6s # Заблочим Африку, Азию, Южную/Северную Америку, Океанию и Антарктику tcp-request connection reject if { src -f /etc/haproxy/geoip/AF.acl } || { src -f /etc/haproxy/geoip/AS.acl } || { src -f /etc/haproxy/geoip/AN.acl } || { src -f /etc/haproxy/geoip/OC.acl } || { src -f /etc/haproxy/geoip/SA.acl } || { src -f /etc/haproxy/geoip/NA.acl } # Защитим от перегрузки, если с одного IP за 10сек открыто более 30 соедиенний, отклоним все запросы и закроем все соединения # Нужно учитывать что WsTunnel с активным параметром --connection-min-idle <n> будет занимать указанное колличество при старте # У VLESS так же есть мультилексор, тоже нужно учитывать при настройке # Может так случиться если несколько клиентов в одно и тоже время с одного IP решат подключиться, все они потеряют доступ к серверу. # Для себя я вывел что 30 одновременных подключений с одного IP на случай если например дома выключат свет 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 30 } 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 } # Я бы не рекомендовал, вы лешитесь доступа если haproxy упадет use_backend ocserv if { req.ssl_sni -i 1.mydomain.com } use_backend vless if { req.ssl_sni -i 2.mydomain.com } default_backend tcp_to_https frontend https bind 127.0.0.1:444 accept-proxy ssl crt /etc/haproxy/certs alpn h2,http/1.1 strict-sni http-request reject if { req.hdr(user-agent) -m sub evil } http-request reject if { path -m sub /. } # Защитим от перегрузки, если перестанет работать WsTunnel (чем больше клиентов тем выше установить счетчик) # Если в течении 30с будет выполнено более 30 запросов отклоним ответом 429 Too Many Requests stick-table type ip size 100k expire 2m store http_req_rate(30s) http-request track-sc0 src # Отклоним с ответом о превышенном кол-ве запросов http-request deny deny_status content-type text/html lf-string "<p>Per our policy, you are limited to 30 requests per 30s, but you have exceeded that limit with %[sc_http_req_rate(0)] requests per 30s.</p>" if { sc_http_req_rate(0) gt 30 } # GeoIP блокировка всех кроме RU и белого списка (можете воспользоваться content-type из примера выше, сообщить о запрете доступа с их IP вывести IP адрес.) # Добавьте свой IP перед использованием в белый список, применять осторожно # http-request deny deny_status 403 if !{ src -f /etc/haproxy/geoip/RU.list } || !{ src -f /etc/haproxy/mylist.acl } http-response set-header Strict-Transport-Security "max-age=16000000; includeSubDomains; preload;" # Устанавливаем HSTS use_backend http_stats if { ssl_fc_sni -i mydomain.com } { path_beg /ha-stats-secret-path/ } use_backend http_xui if { ssl_fc_sni -i 2.mydomain.com } { path_beg /my-secret-path2/ } use_backend dns_query if { ssl_fc_sni -i mydomain.com } { path_beg /dns-query/ } || { path /dns-query } use_backend ws-socks if { ssl_fc_sni -i mydomain.com } { path /ws-socks-secret-path } || { path_beg /ws-socks-secret-path/ } # default_backend http_adh # панель настройки AdGuardHome # default_backend cats # раскомментируйте после добавления сайта обманки #->-----tcp-backend-------------------------- backend tcp_to_https mode tcp server https 127.0.0.1:444 send-proxy-v2-ssl-cn check port 444 backend tcp-ssh mode tcp option http-keep-alive timeout http-keep-alive 30s server ssh 127.0.0.1:22 check port 22 backend ocserv mode tcp server s1 127.0.0.1:29999 send-proxy check port 29999 backend vless mode tcp timeout tunnel 1h server s1 127.0.0.1:50001 send-proxy check port 50001 #->->---http-backend------------------------- backend http_stats mode http http-request replace-path /ha-stats-secret-path(/)?(.*) /\2 server stats 127.0.0.1:58080 backend http_xui mode http option httpchk GET /xui/ option forwardfor if-none 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-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)] server s1 127.0.0.1:54321 check backend dns_query mode http option httpchk option forwardfor if-none 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-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)] server doh 127.0.0.1:30000 #backend http_adh # mode http # server adh 127.0.0.1:30000 backend ws-socks mode http option http-keep-alive timeout http-keep-alive 25s timeout tunnel 1h server s1 127.0.0.1:40001 check #Здесь вы добавите секцию backend cats для маскировки
Дополнение по OCServ для доступа в интернет
Скрытый текст
Необходимо добавить в /etc/sysctl.conf следующую строкуnet.ipv4.ip_forward = 1и применить конфигурацию sysctl -p
Внесем изменения в /etc/default/ufw
Изменим DEFAULT_FORWARD_POLICY="DROP" на DEFAULT_FORWARD_POLICY="ACCEPT"
Добавим NAT в /etc/ufw/before.rules перед *filter
*nat :POSTROUTING ACCEPT [0:0] -A POSTROUTING -s 10.13.14.0/24 -o eth0 -j MASQUERADE COMMIT
После выполнить ufw disable и ufw enable
Upd. Реализовал reality за haproxy, в дальнейшем с данной конфигурацией можно будет перейти на cdn, +добавлю вариант vless-ws. Продолжение скоро.
