Это моя первая статья, я буду ссылаться на других авторов и статьи для уточнения и инструкции.
Так как про VPN писать у нас запрещено, напишу про балансировку и маршрутизацию в Haproxy, про VPN дам лишь общие рекомендации для защиты удаленного доступа. Я использую
1. Нам потребуется два домена (поддомена), подойдут и технические домены хостера.
2. Сам
Все эксперименты провожу на Ubuntu 22.04 LTS
Обновляем список пакетов и обновляем систему работаю от sudo пользователя:
sudo apt update && apt upgrade -y
Устанавливаем необходимые пакеты: sudo apt install -y haproxy netcat-traditional htop
Фаервол настроили? Если нет настраиваемsudo ufw allow 22/tcp
разрешаем ssh (если не поменяли)sudo ufw allow 80,443/tcp
Разрешаем доступ к Web портам
Сгенерируем DH, терпеливо ждем окончанияsudo openssl dhparam -out /etc/haproxy/dh4096.pem 4096
Настроим Haproxy
Я использую acme.sh для получения сертификатов и deploy метод описанный тут.
В секцию global, пропишем современные шифры, оставим TLSv1.2 на случай блокировки TLSv1.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 '(ваш отпечаток полученный при настройке 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й
Пропишем секцию defaults, резолвер DNS и сервер статистики, настроим timeout, для туннелей выставим 1 час.
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 20s
timeout client 20s
timeout server 20s
timeout tunnel 1h
timeout http-request 20s
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.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 user:password
stats show-legends
stats hide-version
Теперь напишем TCP frontend, правила acl и beckend для перенаправления на http frontend и vless
frontend tcp
bind *:80,*:443
mode tcp
option tcplog
tcp-request inspect-delay 10s
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 vless if { req.ssl_sni -i api-app.$вашдомен }
use_backend tcp_to_http if { dst_port 80 } #redirect https
default_backend tcp_to_https # перенаправляем все на frontend http
# use_backend ssh if !{ req.ssl_hello_type 1 } { payload(0,7) -m bin 5353482d322e30 } or !{ req.ssl_hello_type 1 } { req.len 0 } # там мы можен перенести ssh на 443 порт
backend tcp_to_http
mode tcp
server http 127.0.0.1:8080 send-proxy-v2-ssl-cn
backend tcp_to_https
mode tcp
timeout tunnel 1h
timeout client 50s
timeout server 50s
server https 127.0.0.1:8443 send-proxy-v2-ssl-cn
backend vless #x-ui
mode tcp
timeout tunnel 2h
balance roundrobin
server s1 127.0.0.1:4444 check send-proxy
#backend ssh
# mode tcp
# timeout tunnel 2h
# balance roundrobin
# server s1 127.0.0.1:22 check
Слушаем 80 и 443 порт в tcp и инспектируем SNI, по умолчанию не прошедший SNI перенаправляем на http frontend.
Все что пришло на 80 порт перенаправляем на backend tcp_to_http который нужен лишь для редиректа на https об этом в секции http frontend, используем proxy protocol v2 ssl cn для передачи информации о клиенте на frontend.
Если раскомментировать use_backend ssh и backend ssh вы получите рабочий ssh на 443 порту.
Теперь секция frontend http:
frontend https
bind 127.0.0.1:8080 accept-proxy
bind 127.0.0.1:8443 accept-proxy ssl crt /etc/haproxy/certs/ alpn h2,http/1.1 strict-sni
http-request deny if { req.hdr(user-agent) -m len le 32 }
http-request deny if { req.hdr(user-agent) -m sub evil }
http-request deny if { path -m sub /. }
http-response set-header Strict-Transport-Security "max-age=16000000; includeSubDomains; preload;"
redirect scheme https if !{ ssl_fc }
http-request return status 200 content-type text/plain lf-string "%[path,field(-1,/)].${ACCOUNT_THUMBPRINT}n" if { path_beg '/.well-known/acme-challenge/' } #позволим отвечать haproxy на запросы LE
use_backend wstunnel1 if { ssl_fc_sni -i $вашдомен } { path /wss-secretpath1 } || { path_beg /wss-secretpath1/ }
use_backend wstunnel2 if { ssl_fc_sni -i $вашдомен } { path /wss-secretpath2 } || { path_beg /wss-secretpath2/ }
use_backend http_xui if { ssl_fc_sni -i $вашдомен } { path_beg /xui-secretpath/ } || { path_beg /xui-secretpath }
use_backend http_stats if { ssl_fc_sni -i $вашдомен } { path_beg /stats-secretpath/ }
В данном варианте наш http фронтед слушает на портах 8080 и 8443 соответственно и принимает прокси протокол, перенаправляем http на https и устанавливаем HSTS что бы использовать только https.
Теперь разберем acl:use_backend wstunnel
и номер отвечают как за WebSocket, параметр масштабируемый, можно запустить необходимые кол-во экземпляров wstunnel.
use_backend http_xui
тут просто обычно xui запускается на порту 54321 туда и перенаправим.
use_backend http_stats
отвечает за сервер статистики настроенный выше в секции defaults, с логином и паролем user:password (поменяли надеюсь?)
Все правила path_beg
и path
замените на свои, обязательно установите в x-ui путь, для этого сделаете default_backend http_xui
или откройте порт, ufw allow 54321/tcp
а потом закройте как поменяете заменив allow на deny.
Секция backend http:
backend wstunnel1
mode http
option http-keep-alive
timeout connect 30s
timeout http-keep-alive 30s
timeout client 50s
timeout server 50s
timeout tunnel 4h
balance roundrobin
server s1 127.0.0.5:54443
backend wstunnel2
mode http
option http-keep-alive
timeout connect 30s
timeout http-keep-alive 30s
timeout client 50s
timeout server 50s
#option http-server-close
timeout tunnel 4h
balance roundrobin
server s1 127.0.0.5:5453
backend http_xui
mode http
option httpchk
option forwardfor
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 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 set-header X-Forwarded-Proto http if !{ ssl_fc }
server x-ui 127.0.0.1:54321
backend http_stats
mode http
http-request replace-path /stats-secretpath(/)?(.*) /2
server stats 127.0.0.1:58080
backend wstunnel
отвечают за WebSocket, в данном случае их два.
backend http_xui
понятно отвечает за веб панель x-ui.
http_stats
за сервер статистики.
Теперь когда конфиг haproxy готов, а сертификаты (надеюсь получены) займемся настройкой бекендов.
Я использую wstunnel от erebe, с помощью него можно туннелировать трафик через websocket, можно использовать как общий socks5 прокси, либо туннель для конкретного сервиса, дополнительно можно прочитать на странице проекта, приступим к его настройке:
Берем ссылку со страницы release на момент написания статьи последняя версия 10.1.8.
Создадим директорию в 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
Теперь создадим скрипт запуска и проверки туннеля c именем socksnano /etc/wstunnel/socks.sh
со следующим содержимым:
#!/bin/bash
# Настройки
WSTUNNEL_PATH="/etc/wstunnel/wstunnel" # Укажите путь к wstunnel
WS_URL="ws://127.0.0.5:14443" # URL для туннеля
RESTRICT_PATH_PREFIX="wss-secretpath1" # Укажите путь для --restrict-http-upgrade-path-prefix
PING_TARGET="127.0.0.5" # Цель для пинга
CHECK_PORT=14443 # Порт проверки
CHECK_INTERVAL=10 # Интервал проверки в секундах
# Запуск wstunnel
start_tunnel() {
echo "Запуск wstunnel..."
$WSTUNNEL_PATH server $WS_URL --restrict-http-upgrade-path-prefix $RESTRICT_PATH_PREFIX &
TUNNEL_PID=$!
}
# Проверки работоспособности туннеля
check_tunnel() {
while true; do
# Проверка порта с помощью netcat
if ! nc -z $PING_TARGET $CHECK_PORT; then
echo "Туннель не работает, перезапуск..."
kill $TUNNEL_PID
start_tunnel
fi
sleep $CHECK_INTERVAL
done
}
# Запуск туннеля и проверки
start_tunnel
check_tunnel
Рассмотрим скрипт в секции "настройка" мы устанавливаем параметры запуска поменяйте на свои, RESTRICT_PATH_PREFIX обязательно меняем и главное что бы он совпадал с haproxy. Секция "Запуск wstunnel" запустит наш wstunnel с указанными параметрами. Секция "Проверки работоспособности туннеля" отвечает за проверку поднятого туннеля по средствам netcat, проверяем наличие открытого порта wstunnel если порт закрыт перезагрузим нам wstunnel.
Поменяем создателя: chown wstunnel:wstunnel ./socks.sh
Сделаем скрипт исполняемым:
chmod 700 ./socks.sh
Теперь можно проверить скрипт выполнив ./socks.sh
Теперь напишем второй скрипт только для работы c системным резолвером DNS для защиты наших DNS запросовnano /etc/wstunnel/dns.sh
со следующим содержимым (вы так же можете написать и для ssh добавив еще бекенд и фронтед в haproxy с другим путем):
#!/bin/bash
# Настройки
WSTUNNEL_PATH="/etc/wstunnel/wstunnel" # Укажите путь к wstunnel
WS_URL="ws://127.0.0.5:5453" # URL для туннеля
RESTRICT_PATH_PREFIX="wss-secretpath2" # Укажите путь для --restrict-http-upgrade-path-prefix
PING_TARGET="127.0.0.5" # Цель для пинга
CHECK_PORT=5453 # Порт проверки
CHECK_INTERVAL=10 # Интервал проверки в секундах
Restrict_TO="127.0.0.53:53" # сервис для коннекта в данном случае системный DNS резолвер
# Функция для запуска wstunnel
start_tunnel() {
echo "Запуск wstunnel..."
$WSTUNNEL_PATH server $WS_URL --restrict-http-upgrade-path-prefix $RESTRICT_PATH_PREFIX --restrict-to $Restrict_TO & TUNNEL_PID=$!
}
# Функция для проверки работоспособности туннеля
check_tunnel() {
while true; do
# Проверка порта с помощью netcat
if ! nc -z $PING_TARGET $CHECK_PORT; then
echo "Туннель не работает, перезапуск..."
kill $TUNNEL_PID
start_tunnel
fi
sleep $CHECK_INTERVAL
done
}
# Запуск туннеля и проверки
start_tunnel
check_tunnel
Выполняем тоже что и со скриптом socks.sh
меняем владельца и даем права на исполнение, отличие только в имени скрипта.
Теперь напишем systemd для запуска наших скриптов
Создадим файл командойnano /etc/systemd/system/ws-socks.service
со следующим содержимым:
[Unit]
Description=Start & Check Wstunnel
After=network.target
[Service]
ExecStart=/etc/wstunnel/socks.sh
User=wstunnel
Group=wstunnel
[Install]
WantedBy=multi-user.target
И для DNS:
nano /etc/systemd/system/ws-dns.service
со следующим содержимым:
[Unit]
Description=Start & Check Wstunnel
After=network.target
[Service]
ExecStart=/etc/wstunnel/dns.sh
User=wstunnel
Group=wstunnel
[Install]
WantedBy=multi-user.target
Запускаем:
systemctl start ws-socks.service ws-dns.service
Проверяем:systemctl status ws-socks.service
systemctl status ws-dns.service
Если в обоих случаях все ок и все процессы запущены, проверим через htop
от какого пользователя запущены, ищем наши процессы, если запущены от пользователя wstunnel то все сделано правильно.
Теперь поставим их в автозагрузку systemctl enable ws-socks.service ws-dns.service
Теперь настроим клиентов, в моем случае первый клиент armdebian с systemd, второй OpenWRT с init.d, и Windows.
Выполним загрузку wstunnel и настройку аналогичную серверу, создадим директорию в etc, добавим пользователя и группу.
Для systemd, запустим на локальной машине socks5 прокси через WebSocket.
Командой создадим сервис:nano /etc/systemd/system/ws-socks.service
[Unit]
Description=wstunnel service
After=network.target
[Service]
ExecStart=/etc/wstunnel/wstunnel client --http-upgrade-path-prefix wss-secretpath1 --local-to-remote socks5://0.0.0.0:2080 wss://$ваш.домен:443 --tls-verify-certificate --connection-min-idle 4
Restart=no
User=nobody
Group=nogroup
[Install]
WantedBy=multi-user.targets
Теперь второй сервис для запросов DNS на 5353 порту например для локального adguardhome nano /etc/systemd/system/ws-dns.service
[Unit]
Description=wstunnel service
After=network.target
[Service]
ExecStart=/etc/wstunnel/wstunnel client --http-upgrade-path-prefix wss-secretpath2 --local-to-remote udp://5353:127.0.0.53:53 wss://$ваш.домен:443 --tls-verify-certificate --connection-min-idle 2
Restart=no
User=nobody
Group=nogroup
[Install]
WantedBy=multi-user.targets
Напишем скрипт проверки туннеля socks5 и разместим его так же в /etc/wstunnelnano /etc/wstunnel/chk-socks.sh
#!/bin/bash
# настройки проверки
TARGET=127.0.0.1
PORT=2080
SERVICE=ws-socks.service
# Проверяем доступность порта
if nc -z $TARGET $PORT; then
# Если соединение установлено, ничего не делаем
echo "Порт $PORT доступен."
else
# Если соединение не удалось, перезапускаем службу
echo "Порт $PORT недоступен. Перезапуск службы wstunnel"
systemctl restart $SERVICE
fi
И второй скрипт для проверки туннеля dnsnano /etc/wstunnel/chk-dns.sh
#!/bin/bash
# настройки проверки
TARGET=127.0.0.1
PORT=5353
SERVICE=ws-dns.service
# Проверяем доступность порта
if nc -z $TARGET $PORT; then
# Если соединение установлено, ничего не делаем
echo "Порт $PORT доступен."
else
# Если соединение не удалось, перезапускаем службу
echo "Порт $PORT недоступен. Перезапуск службы wstunnel"
systemctl restart $SERVICE
fi
Теперь разместим наши скрипты в Crontab от root: выполнимsudo crontab -e
И добавим в конец:*/5 * * * * /etc/wstunnel/chk-dns.sh
*/5 * * * * /etc/wstunnel/chk-socks.sh
В данных вариантах скрипта проверки мы только проверяем наличие доступного порта, если не доступен перезапускаем сервис.
Теперь напишем клиента для OpenWRT, для начала узнаем архитектуру нашего роутера
командой opkg print-architecture
у моего роутера архитектура aarch64_cortex-a53
Теперь загрузим Wstunnel для нашей архитектуры
Перейдем в tmp:cd /tmp
Загрузим:wget https://github.com/erebe/wstunnel/releases/download/v10.1.8/wstunnel_10.1.8_linux_arm64.tar.gz
Распакуем:tar -xvf wstunnel_10.1.8_linux_arm64.tar.gz
Переместим wstunnel:mv wstunnel /usr/local/bin/wstunnel
Сделаем исполняемым:chown +x /usr/local/bin/wstunnel
Теперь напишем сервис:
vi /etc/init.d/ws-socks
#!/bin/sh /etc/rc.common
# Определяем имя службы
START=99
STOP=10
SERVICE_NAME="wstunnel socks5"
# Определяем команды для запуска и остановки
start() {
echo "Starting $SERVICE_NAME..."
/usr/local/bin/wstunnel client --http-upgrade-path-prefix wss-secretpath1 --local-to-remote socks5://0.0.0.0:2080 wss://$ваш.домен:443 --tls-verify-certificate --connection-min-idle 4 &
echo $! > /var/run/$SERVICE_NAME.pid
}
stop() {
echo "Stopping $SERVICE_NAME..."
if [ -f /var/run/$SERVICE_NAME.pid ]; then
kill $(cat /var/run/$SERVICE_NAME.pid)
rm /var/run/$SERVICE_NAME.pid
else
echo "$SERVICE_NAME is not running."
fi
}
Аналогично напишем для нашего dns сервиса vi /etc/init.d/ws-dns
#!/bin/sh /etc/rc.common
# Определяем имя службы
START=99
STOP=10
SERVICE_NAME="wstunnel dns"
# Определяем команды для запуска и остановки
start() {
echo "Starting $SERVICE_NAME..."
/usr/local/bin/wstunnel client --http-upgrade-path-prefix wss-secretpath2 --local-to-remote udp://5353:127.0.0.53:53 wss://$ваш.домен:443 --tls-verify-certificate --connection-min-idle 2 &
echo $! > /var/run/$SERVICE_NAME.pid
}
stop() {
echo "Stopping $SERVICE_NAME..."
if [ -f /var/run/$SERVICE_NAME.pid ]; then
kill $(cat /var/run/$SERVICE_NAME.pid)
rm /var/run/$SERVICE_NAME.pid
else
echo "$SERVICE_NAME is not running."
fi
}
Сделаем скрипты исполняемым: chmod +x /etc/init.d/ws-dns
chmod +x /etc/init.d/ws-socks
Запустим:/etc/init.d/ws-socks start
/etc/init.d/ws-dns start
Добавим в автозагрузку:/etc/init.d/ws-socks enable
/etc/init.d/ws-dns enable
Теперь у вас есть socks5 прокси через wstunnel скрытый за haproxy как альтернатива для VPN, далее можно воспользоваться точечной маршрутизацией например инструкция от itdog для роутеров с использованием tun2socks или просто указать в браузере ip роутера (локального сервера) порт 2080 где развернут wstunnel и получить доступ в удаленную сеть, так же при наличии adguardhome или dnsmasq можете и нужно прописать наш локальный 127.0.0.1:5353 как основной резолвер, тем самым мы защитим наши DNS запросы от подмены.
Клиент для Windows так же скачиваем со страницы проекта, распаковываем переходим в папку и создаем файл с именем например start, копируем туда следующие содержимое:wstunnel client --http-upgrade-path-prefix wss-secretpath1 --local-to-remote socks5://0.0.0.0:2080 wss://$ваш.домен:443 --tls-verify-certificate --connection-min-idle 4
Теперь сохраним его как start.cmd, запустив его получим локальный socks5 прокси, можно забить в браузер или воспользоваться nekobox с маршрутизаций и подключить наш socks5 к нему.
В конфигурации клиента wstunnel присутствует флаг --tls-verify-certificate он нужен для проверки сертификата сервера, если его убрать можно будет подключится к невалидному сертификату например самоподписанному или просроченному.
Рекомендация по настройке VPN
Для мобильных клиентов можно использовать VLESS + TLS
Пример настройки для X-UI:
Протокол VLESS
Порт например 4444 (он будет не публичный)
Протокол передачи TCP
Обязательно включаем PROXY Protocol и External Proxy:
Для External Proxy: Тот же $ваш.домен(поддомен для VLESS) 443
Безопасность TLS
SNI $ваш.домен(поддомен для VLESS)
Cipher Suites Auto
Min/Max Version 1.2 1.3
Сертификат должен лежать в директории ./x-ui/cert
Сертификат /root/cert/fullchain.pem
Ключ /root/cert/privkey.pem
На Хабре уже был целый цикл статей про настройку панелей для VLESS например вот.
Вот мой пример docker compose файла, я использую сеть host чтобы избежать nat докера:
---
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
Еще не забыли что нужно сайт еще разместить?
Разместите сайт на apache2 или nginx на localhost и любом доступном порте
Откроем конфиг Haproxy sudo nano /etc/haproxy/haproxy.cfg
И добавим в конец:
backend http_defailt
mode http
option httpchk
option forwardfor
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 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 set-header X-Forwarded-Proto http if !{ ssl_fc }
server s1 127.0.0.1:8090
Теперь в секции frontend http нужно добавить следующий текст под use_backend:
defailt_backend http_defailt
Перезапускаем haproxy sudo systemctl restart haproxy
Проверяем в браузере вводим ваш домен, если открылся сайт все сделано правильно, теперь у вас есть рабочий VPN с VLESS, WebSocket в качестве альтернативы VPN и рабочий сайт обманка.
Если тема будет интересна напишу продолжение с добавлением stunnel со SNI, расширенной маршрутизацией и защитой haproxy по спискам белых IP или блоклистам.
Автор: ki11j0y