Files
gitea-token-access/HARDENING.md
delta-cloud-208e c3f5f94477 docs: add HARDENING.md — step-by-step DPI bypass and server hardening guide
Covers 7 issues: WireGuard tunnel, zapret/nfqws for DPI, SNI protection,
dnscrypt-proxy, HSTS, SSE timeout, wstunnel fallback. Includes commands,
verification checklist, and rollback for each step.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 15:25:18 +00:00

21 KiB
Raw Permalink Blame History

HARDENING.md — Защита Gitea от DPI/ТСПУ и повышение стабильности

Серверы: sensey24.ru (45.132.50.182) + core-gitlab-01 (1.1.1.122) Дата аудита: 2026-02-21 Gitea: 1.24.5, порт 3000 на core-gitlab-01


Текущее состояние

Компонент Статус Примечание
TCP tuning (BBR, буферы) OK sysctl настроен на обоих серверах
MSS clamping (1200) OK iptables TCPMSS на sensey24.ru
TCP keepalive OK net.ipv4.tcp_keepalive_time=60
Apache ProxyPass OK Gitea проксируется через ppp0 (20.0.0.2:3000)
IPsec (strongSwan) ПРОБЛЕМА SA = 0, туннель без шифрования
DPI/ТСПУ защита НЕТ zapret, obfs4, stunnel не установлены
SNI скрытие НЕТ TLS ClientHello в открытом виде
DNS шифрование НЕТ 8.8.8.8 plaintext
HSTS НЕТ Заголовок не настроен
WireGuard ЧАСТИЧНО Установлен на sensey24.ru, не настроен

Проблема 1: IPsec SA = 0 → Замена на WireGuard

Почему WireGuard вместо IPsec

  • strongSwan показывает 0 Security Associations — туннель фактически не шифрует
  • WireGuard проще в настройке, быстрее, меньше attack surface
  • WireGuard уже установлен на sensey24.ru
  • UDP 51820 меньше подвержен DPI-блокировкам, чем ESP/IKE

1.1 Генерация ключей

На sensey24.ru:

ssh -p 31216 robot@sensey24.ru

# Ключи уже могут быть — проверяем
ls /etc/wireguard/

# Генерация ключей сервера
wg genkey | tee /etc/wireguard/server_private.key | wg pubkey > /etc/wireguard/server_public.key
chmod 600 /etc/wireguard/server_private.key

На core-gitlab-01:

ssh -p 7913 robot@sensey24.ru  # это проброс на core-gitlab-01

# Установка WireGuard
sudo apt update && sudo apt install -y wireguard

# Генерация ключей клиента
wg genkey | tee /etc/wireguard/client_private.key | wg pubkey > /etc/wireguard/client_public.key
chmod 600 /etc/wireguard/client_private.key

1.2 Настройка на sensey24.ru (WG-сервер)

# Подставить реальные ключи
SERVER_PRIVATE=$(cat /etc/wireguard/server_private.key)
CLIENT_PUBLIC=$(cat /etc/wireguard/client_public.key)  # скопировать с core-gitlab-01

cat > /etc/wireguard/wg0.conf << EOF
[Interface]
Address = 10.10.10.1/24
ListenPort = 51820
PrivateKey = ${SERVER_PRIVATE}

# Разрешить форвардинг
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

[Peer]
# core-gitlab-01
PublicKey = ${CLIENT_PUBLIC}
AllowedIPs = 10.10.10.2/32
EOF

chmod 600 /etc/wireguard/wg0.conf
# Включение
sudo systemctl enable wg-quick@wg0
sudo wg-quick up wg0

# Открыть порт
sudo iptables -A INPUT -p udp --dport 51820 -j ACCEPT
sudo netfilter-persistent save

1.3 Настройка на core-gitlab-01 (WG-клиент)

CLIENT_PRIVATE=$(cat /etc/wireguard/client_private.key)
SERVER_PUBLIC=$(cat /etc/wireguard/server_public.key)  # скопировать с sensey24.ru

cat > /etc/wireguard/wg0.conf << EOF
[Interface]
Address = 10.10.10.2/24
PrivateKey = ${CLIENT_PRIVATE}

# Keepalive через NAT
[Peer]
PublicKey = ${SERVER_PUBLIC}
Endpoint = 45.132.50.182:51820
AllowedIPs = 10.10.10.1/32
PersistentKeepalive = 25
EOF

chmod 600 /etc/wireguard/wg0.conf
sudo systemctl enable wg-quick@wg0
sudo wg-quick up wg0

1.4 Миграция Apache ProxyPass с ppp0 на wg0

На sensey24.ru — обновить конфиг Apache:

# Найти текущий конфиг с ProxyPass
grep -r "20.0.0.2" /etc/apache2/

# Заменить IP в ProxyPass
# Было:  ProxyPass / http://20.0.0.2:3000/
# Стало: ProxyPass / http://10.10.10.2:3000/
sudo sed -i 's|20\.0\.0\.2|10.10.10.2|g' /etc/apache2/sites-enabled/*.conf

# Проверить синтаксис и перезагрузить
sudo apache2ctl configtest
sudo systemctl reload apache2

1.5 Тест

# На sensey24.ru:
ping -c 3 10.10.10.2
curl -s -o /dev/null -w "%{http_code}" http://10.10.10.2:3000/

# Проверить WG-туннель:
sudo wg show

# Ожидаемый результат:
# interface: wg0
#   latest handshake: <недавнее время>
#   transfer: X received, Y sent

1.6 Откат

# На sensey24.ru — вернуть Apache на ppp0:
sudo sed -i 's|10\.10\.10\.2|20.0.0.2|g' /etc/apache2/sites-enabled/*.conf
sudo systemctl reload apache2

# Остановить WG:
sudo wg-quick down wg0
sudo systemctl disable wg-quick@wg0

Проблема 2: DPI/ТСПУ блокирует HTTPS → zapret (nfqws)

Почему zapret

  • zapret/nfqws модифицирует исходящие TLS ClientHello пакеты
  • Ломает DPI-сигнатуры без изменения для клиента
  • Работает прозрачно на уровне iptables/nfqueue
  • Не требует клиентского ПО

2.1 Установка на sensey24.ru

ssh -p 31216 robot@sensey24.ru

cd /opt
sudo git clone --depth 1 https://github.com/bol-van/zapret.git
cd zapret

# Сборка
sudo make -C nfq

# Проверить бинарник
ls -la nfq/nfqws

2.2 Конфигурация nfqws для HTTPS

# Создать конфиг
sudo mkdir -p /etc/zapret

cat > /etc/zapret/nfqws.conf << 'EOF'
# Стратегия для обхода DPI на порту 443 (HTTPS/TLS)
# --dpi-desync=fake,split2 — отправляет фейковый ClientHello + разбивает настоящий
# --dpi-desync-ttl=6 — фейковый пакет "умирает" до DPI, но после маршрутизатора
NFQWS_ARGS="--dpi-desync=fake,split2 --dpi-desync-ttl=6 --dpi-desync-fooling=md5sig"
EOF

2.3 Правило iptables для перенаправления в NFQUEUE

# Перехватываем исходящий HTTPS-трафик
sudo iptables -t mangle -A POSTROUTING -p tcp --dport 443 -m connbytes --connbytes 1:6 --connbytes-mode packets --connbytes-dir=original -j NFQUEUE --queue-num 200 --queue-bypass

# Сохранить
sudo netfilter-persistent save

2.4 Systemd unit

cat > /etc/systemd/system/nfqws.service << 'EOF'
[Unit]
Description=nfqws DPI bypass
After=network.target
Wants=network.target

[Service]
Type=simple
EnvironmentFile=/etc/zapret/nfqws.conf
ExecStart=/opt/zapret/nfq/nfqws --qnum=200 $NFQWS_ARGS
Restart=always
RestartSec=5
LimitNOFILE=65535

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable nfqws
sudo systemctl start nfqws

2.5 Тест

# Проверить что nfqws запущен
sudo systemctl status nfqws

# Проверить что пакеты идут через NFQUEUE
sudo iptables -t mangle -L POSTROUTING -v -n | grep NFQUEUE

# Тест подключения с внешнего хоста
curl -v --connect-timeout 10 https://git.sensey24.ru/

2.6 Откат

sudo systemctl stop nfqws
sudo systemctl disable nfqws
sudo iptables -t mangle -D POSTROUTING -p tcp --dport 443 -m connbytes --connbytes 1:6 --connbytes-mode packets --connbytes-dir=original -j NFQUEUE --queue-num 200 --queue-bypass
sudo netfilter-persistent save

Проблема 3: SNI в открытом виде

Вариант A: zapret --dpi-desync (уже решает)

Конфигурация из Проблемы 2 уже разбивает TLS ClientHello. Параметр split2 отделяет SNI от начала handshake, что ломает простые DPI-фильтры по SNI.

Для усиления можно добавить --dpi-desync-split-pos=1 в /etc/zapret/nfqws.conf:

# Обновить конфиг
sudo sed -i 's|NFQWS_ARGS="|NFQWS_ARGS="--dpi-desync-split-pos=1 |' /etc/zapret/nfqws.conf
sudo systemctl restart nfqws

Вариант B: Cloudflare Proxy (опционально)

Если запрос идёт через Cloudflare, SNI показывает IP Cloudflare, а не реальный сервер.

  1. Добавить домен git.sensey24.ru в Cloudflare
  2. Включить проксирование (оранжевое облачко)
  3. В настройках SSL → Full (Strict)
  4. В Apache добавить заголовки для реального IP:
# /etc/apache2/sites-enabled/git.sensey24.ru.conf
RemoteIPHeader CF-Connecting-IP
RemoteIPTrustedProxy 173.245.48.0/20 103.21.244.0/22 103.22.200.0/22

Минус: Cloudflare добавляет задержку и видит весь трафик. Для приватного Git-сервера zapret предпочтительнее.


Проблема 4: DNS в открытом виде → dnscrypt-proxy

4.1 Установка на sensey24.ru

sudo apt update && sudo apt install -y dnscrypt-proxy

4.2 Конфигурация

sudo cp /etc/dnscrypt-proxy/dnscrypt-proxy.toml /etc/dnscrypt-proxy/dnscrypt-proxy.toml.bak

cat > /etc/dnscrypt-proxy/dnscrypt-proxy.toml << 'EOF'
listen_addresses = ['127.0.0.53:53']
max_clients = 250

# Серверы с поддержкой DoH и DNSSEC
server_names = ['cloudflare', 'google', 'scaleway-fr']

[query_log]
  file = '/var/log/dnscrypt-proxy/query.log'

[sources]
  [sources.'public-resolvers']
    urls = ['https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/public-resolvers.md',
            'https://download.dnscrypt.info/resolvers-list/v3/public-resolvers.md']
    cache_file = '/var/cache/dnscrypt-proxy/public-resolvers.md'
    minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3'
EOF

4.3 Отключение systemd-resolved и переключение DNS

# Остановить systemd-resolved (если используется)
sudo systemctl stop systemd-resolved
sudo systemctl disable systemd-resolved

# Настроить resolv.conf
sudo rm -f /etc/resolv.conf
echo "nameserver 127.0.0.53" | sudo tee /etc/resolv.conf
sudo chattr +i /etc/resolv.conf  # защита от перезаписи

# Запуск
sudo systemctl enable dnscrypt-proxy
sudo systemctl restart dnscrypt-proxy

4.4 Проверка

# Проверить что dnscrypt-proxy слушает
ss -ulnp | grep 53

# Проверить резолвинг
dig git.sensey24.ru @127.0.0.53

# Убедиться что DNS НЕ идёт в plaintext (должен быть пустой вывод)
sudo tcpdump -i eth0 -n port 53 -c 10 &
dig google.com
# Если tcpdump ничего не показывает — DNS зашифрован

4.5 Откат

sudo chattr -i /etc/resolv.conf
echo "nameserver 8.8.8.8" | sudo tee /etc/resolv.conf
sudo systemctl stop dnscrypt-proxy
sudo systemctl disable dnscrypt-proxy
sudo systemctl enable systemd-resolved
sudo systemctl start systemd-resolved

Проблема 5: Нет HSTS → Apache header

5.1 Включение

ssh -p 31216 robot@sensey24.ru

# Включить модуль headers
sudo a2enmod headers

# Добавить HSTS в конфиг SSL-сайта
sudo sed -i '/<VirtualHost \*:443>/a\    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"' /etc/apache2/sites-enabled/*ssl*.conf

# Если файл называется иначе — найти нужный:
grep -rl "443" /etc/apache2/sites-enabled/

# Перезагрузить
sudo apache2ctl configtest && sudo systemctl reload apache2

5.2 Проверка

curl -sI https://git.sensey24.ru/ | grep -i strict
# Ожидаемый результат:
# Strict-Transport-Security: max-age=31536000; includeSubDomains

5.3 Откат

sudo sed -i '/Strict-Transport-Security/d' /etc/apache2/sites-enabled/*ssl*.conf
sudo systemctl reload apache2

Проблема 6: Apache SSE timeout → ProxyTimeout для EventSource

Почему

Gitea использует Server-Sent Events (SSE) для real-time обновлений. Стандартный ProxyTimeout 60 обрывает SSE-соединения.

6.1 Добавить location-specific timeout

ssh -p 31216 robot@sensey24.ru

# Найти конфиг виртуального хоста
grep -rl "ProxyPass" /etc/apache2/sites-enabled/

Добавить в конфиг виртуального хоста (внутри <VirtualHost *:443>):

cat >> /tmp/sse-patch.conf << 'EOF'

    # SSE/EventSource — длинные соединения
    <Location "/user/events">
        ProxyPass http://10.10.10.2:3000/user/events
        ProxyPassReverse http://10.10.10.2:3000/user/events
        ProxyTimeout 3600
    </Location>

    # Gitea API streaming
    <Location "/api/v1">
        ProxyTimeout 300
    </Location>
EOF

Вставить перед закрывающим </VirtualHost>:

# Определить номер строки </VirtualHost> в SSL-конфиге
CONF=$(grep -rl "ProxyPass.*3000" /etc/apache2/sites-enabled/)
LINE=$(grep -n "</VirtualHost>" "$CONF" | tail -1 | cut -d: -f1)
sudo sed -i "${LINE}r /tmp/sse-patch.conf" "$CONF"

sudo apache2ctl configtest && sudo systemctl reload apache2

6.2 Проверка

# SSE должен держаться дольше 60 секунд
curl -N -H "Accept: text/event-stream" https://git.sensey24.ru/user/events &
sleep 70
# Если соединение живо через 70 секунд — работает
kill %1

6.3 Откат

Удалить добавленные блоки <Location> из конфига и sudo systemctl reload apache2.


Проблема 7: WireGuard через wstunnel (если WG блокируют)

Когда нужно

Если провайдер/ТСПУ блокирует UDP 51820 (WireGuard), трафик оборачивается в WebSocket поверх HTTPS (порт 443), что выглядит как обычный HTTPS.

7.1 Установка wstunnel на обоих серверах

# На sensey24.ru и core-gitlab-01:
WSTUNNEL_VERSION="10.1.0"
wget "https://github.com/erebe/wstunnel/releases/download/v${WSTUNNEL_VERSION}/wstunnel_${WSTUNNEL_VERSION}_linux_amd64.tar.gz"
tar xzf "wstunnel_${WSTUNNEL_VERSION}_linux_amd64.tar.gz"
sudo mv wstunnel /usr/local/bin/
sudo chmod +x /usr/local/bin/wstunnel

# Проверка
wstunnel --version

7.2 Серверная сторона (sensey24.ru)

cat > /etc/systemd/system/wstunnel-server.service << 'EOF'
[Unit]
Description=wstunnel server (WireGuard over WSS)
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/wstunnel server \
    --restrict-to 127.0.0.1:51820 \
    wss://0.0.0.0:8443
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable wstunnel-server
sudo systemctl start wstunnel-server

# Открыть порт
sudo iptables -A INPUT -p tcp --dport 8443 -j ACCEPT
sudo netfilter-persistent save

При этом WireGuard должен слушать на 127.0.0.1:51820. Обновить /etc/wireguard/wg0.conf:

[Interface]
Address = 10.10.10.1/24
ListenPort = 51820
# Привязать к localhost — трафик приходит через wstunnel
PostUp = ip route add 10.10.10.0/24 dev wg0

7.3 Клиентская сторона (core-gitlab-01)

cat > /etc/systemd/system/wstunnel-client.service << 'EOF'
[Unit]
Description=wstunnel client (WireGuard over WSS)
After=network.target
Before=wg-quick@wg0.service

[Service]
Type=simple
ExecStart=/usr/local/bin/wstunnel client \
    --local-to-remote udp://127.0.0.1:51820:127.0.0.1:51820 \
    wss://45.132.50.182:8443
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable wstunnel-client
sudo systemctl start wstunnel-client

Обновить /etc/wireguard/wg0.conf на core-gitlab-01:

[Interface]
Address = 10.10.10.2/24
PrivateKey = <CLIENT_PRIVATE_KEY>

[Peer]
PublicKey = <SERVER_PUBLIC_KEY>
# Endpoint теперь через wstunnel (localhost)
Endpoint = 127.0.0.1:51820
AllowedIPs = 10.10.10.1/32
PersistentKeepalive = 25
sudo systemctl restart wg-quick@wg0

7.4 Тест

# На core-gitlab-01:
ping -c 3 10.10.10.1

# Проверить что wstunnel работает:
sudo systemctl status wstunnel-client
ss -tnp | grep 8443
# Должно показать TCP-соединение к 45.132.50.182:8443

# На sensey24.ru:
sudo wg show
# Должен показать handshake и transfer

7.5 Откат

# На core-gitlab-01:
sudo systemctl stop wstunnel-client
sudo systemctl disable wstunnel-client
# Вернуть Endpoint в wg0.conf на 45.132.50.182:51820
sudo systemctl restart wg-quick@wg0

# На sensey24.ru:
sudo systemctl stop wstunnel-server
sudo systemctl disable wstunnel-server
sudo iptables -D INPUT -p tcp --dport 8443 -j ACCEPT
sudo netfilter-persistent save

Верификация — полный чеклист

После применения всех изменений выполнить проверки:

Инфраструктура

# 1. WireGuard туннель активен
sudo wg show
# Ожидание: handshake < 2 минут назад, transfer > 0

# 2. Ping через WG
ping -c 5 10.10.10.2  # с sensey24.ru
ping -c 5 10.10.10.1  # с core-gitlab-01

# 3. Gitea доступна через WG
curl -s -o /dev/null -w "%{http_code}" http://10.10.10.2:3000/
# Ожидание: 200

DPI/ТСПУ обход

# 4. nfqws запущен
sudo systemctl is-active nfqws
# Ожидание: active

# 5. NFQUEUE ловит пакеты
sudo iptables -t mangle -L POSTROUTING -v -n | grep "NFQUEUE.*200"
# Ожидание: счётчик pkts > 0

# 6. Внешний доступ к Gitea
curl -v --connect-timeout 15 https://git.sensey24.ru/
# Ожидание: HTTP 200, без timeout

DNS

# 7. dnscrypt-proxy работает
sudo systemctl is-active dnscrypt-proxy
# Ожидание: active

# 8. DNS через зашифрованный канал
dig +short google.com @127.0.0.53
# Ожидание: IP-адрес

# 9. Нет plaintext DNS
sudo timeout 10 tcpdump -i eth0 -n port 53 -c 1 2>/dev/null
dig google.com > /dev/null 2>&1
# Ожидание: tcpdump не перехватил пакетов

HTTPS/TLS

# 10. HSTS заголовок присутствует
curl -sI https://git.sensey24.ru/ | grep -i "strict-transport"
# Ожидание: Strict-Transport-Security: max-age=31536000

# 11. SSL Labs (внешний тест)
# Открыть: https://www.ssllabs.com/ssltest/analyze.html?d=git.sensey24.ru
# Ожидание: A или A+

Производительность

# 12. SSE держится > 60 сек
timeout 75 curl -s -N -H "Accept: text/event-stream" https://git.sensey24.ru/user/events > /dev/null 2>&1
echo "Exit code: $?"
# Ожидание: exit code 124 (killed by timeout, не обрыв соединения)

# 13. Git clone работает
cd /tmp && git clone https://git.sensey24.ru/aibot777/test-repo.git 2>&1 | tail -1
rm -rf test-repo
# Ожидание: успешное клонирование

Сводная таблица (после применения)

# Проверка Команда Ожидание
1 WG туннель sudo wg show handshake < 2 мин
2 WG ping ping 10.10.10.2 0% loss
3 Gitea через WG curl http://10.10.10.2:3000/ 200
4 nfqws systemctl is-active nfqws active
5 NFQUEUE iptables -t mangle -L pkts > 0
6 Внешний доступ curl https://git.sensey24.ru/ 200
7 dnscrypt systemctl is-active dnscrypt-proxy active
8 DNS resolve dig @127.0.0.53 google.com ответ есть
9 No plaintext DNS tcpdump port 53 пусто
10 HSTS curl -sI | grep strict заголовок есть
11 SSL Labs браузер A/A+
12 SSE > 60s timeout 75 curl SSE exit 124
13 Git clone git clone success

Порядок применения (рекомендуемый)

  1. WireGuard (Проблема 1) — база для шифрованного канала
  2. Миграция Apache (1.4) — переключить ProxyPass на wg0
  3. zapret/nfqws (Проблема 2) — обход DPI
  4. HSTS (Проблема 5) — быстро, одна команда
  5. SSE timeout (Проблема 6) — стабильность
  6. dnscrypt-proxy (Проблема 4) — DNS шифрование
  7. wstunnel (Проблема 7) — только если WG заблокирован

Каждый шаг независим и может быть откачен отдельно. Рекомендуется применять по одному и тестировать после каждого.