# 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:** ```bash 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:** ```bash 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-сервер) ```bash # Подставить реальные ключи 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 ``` ```bash # Включение 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-клиент) ```bash 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 ``` ```bash sudo systemctl enable wg-quick@wg0 sudo wg-quick up wg0 ``` ### 1.4 Миграция Apache ProxyPass с ppp0 на wg0 **На sensey24.ru** — обновить конфиг Apache: ```bash # Найти текущий конфиг с 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 Тест ```bash # На 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 Откат ```bash # На 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 ```bash 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 ```bash # Создать конфиг 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 ```bash # Перехватываем исходящий 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 ```bash 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 Тест ```bash # Проверить что 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 Откат ```bash 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`: ```bash # Обновить конфиг 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: ```apache # /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 ```bash sudo apt update && sudo apt install -y dnscrypt-proxy ``` ### 4.2 Конфигурация ```bash 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 ```bash # Остановить 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 Проверка ```bash # Проверить что 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 Откат ```bash 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 Включение ```bash ssh -p 31216 robot@sensey24.ru # Включить модуль headers sudo a2enmod headers # Добавить HSTS в конфиг SSL-сайта sudo sed -i '//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 Проверка ```bash curl -sI https://git.sensey24.ru/ | grep -i strict # Ожидаемый результат: # Strict-Transport-Security: max-age=31536000; includeSubDomains ``` ### 5.3 Откат ```bash 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 ```bash ssh -p 31216 robot@sensey24.ru # Найти конфиг виртуального хоста grep -rl "ProxyPass" /etc/apache2/sites-enabled/ ``` Добавить в конфиг виртуального хоста (внутри ``): ```bash cat >> /tmp/sse-patch.conf << 'EOF' # SSE/EventSource — длинные соединения ProxyPass http://10.10.10.2:3000/user/events ProxyPassReverse http://10.10.10.2:3000/user/events ProxyTimeout 3600 # Gitea API streaming ProxyTimeout 300 EOF ``` Вставить перед закрывающим ``: ```bash # Определить номер строки в SSL-конфиге CONF=$(grep -rl "ProxyPass.*3000" /etc/apache2/sites-enabled/) LINE=$(grep -n "" "$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 Проверка ```bash # SSE должен держаться дольше 60 секунд curl -N -H "Accept: text/event-stream" https://git.sensey24.ru/user/events & sleep 70 # Если соединение живо через 70 секунд — работает kill %1 ``` ### 6.3 Откат Удалить добавленные блоки `` из конфига и `sudo systemctl reload apache2`. --- ## Проблема 7: WireGuard через wstunnel (если WG блокируют) ### Когда нужно Если провайдер/ТСПУ блокирует UDP 51820 (WireGuard), трафик оборачивается в WebSocket поверх HTTPS (порт 443), что выглядит как обычный HTTPS. ### 7.1 Установка wstunnel на обоих серверах ```bash # На 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) ```bash 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`: ```ini [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) ```bash 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: ```ini [Interface] Address = 10.10.10.2/24 PrivateKey = [Peer] PublicKey = # Endpoint теперь через wstunnel (localhost) Endpoint = 127.0.0.1:51820 AllowedIPs = 10.10.10.1/32 PersistentKeepalive = 25 ``` ```bash sudo systemctl restart wg-quick@wg0 ``` ### 7.4 Тест ```bash # На 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 Откат ```bash # На 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 ``` --- ## Верификация — полный чеклист После применения всех изменений выполнить проверки: ### Инфраструктура ```bash # 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/ТСПУ обход ```bash # 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 ```bash # 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 ```bash # 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+ ``` ### Производительность ```bash # 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 заблокирован > Каждый шаг независим и может быть откачен отдельно. Рекомендуется применять по одному и тестировать после каждого.