Введение

Это вторая часть цикла про mTLS. В первой разобрали теорию: как работает рукопожатие, от каких атак защищает и где принципиально бессилен. Здесь — практика. Разберём реальный сценарий: homelab на одном сервере с Traefik и Dokploy. Пройдём путь от модели угроз до конкретных попыток атаковать собственный сервер — с командами и объяснениями, что происходит.

Если первую часть не читали — не страшно. Ниже есть краткий раздел с основными понятиями, достаточный, чтобы двигаться дальше.

Кратко: что такое mTLS и зачем он нужен?

Уже читали первую часть? Пропустите следующий раздел и переходите сразу к Прежде чем открывать терминал.

Mutual TLS(mTLS) — расширение протокола TLS, при котором обе стороны соединения предъявляют и проверяют криптографические сертификаты. Если клиент не предъявляет сертификаты / предъявляет невалидный сертификат, соединение разрывается на транспортном уровне до любого HTTP-взаимодействия.

Основные причины его интеграции в homelab:

  1. Защита «на подступах» (Drop до HTTP).

  2. Защита сервисов без встроенного логин/пароль.

  3. Безопасный доступ без VPN.

  4. «Исключение» из списков сканирования ботами, которые сканируют интернет.

  5. Удобное управление доступом.

Терминология по mTLS

CA (Certificate Authority) — удостоверяющий центр. В случае mTLS организация использует свой собственный CA. Она создаёт корневой сертификат и подписывает клиентские сертификаты. В отличие от публичного TLS, где CA (например, Let’s Encrypt, DigiCert) проверяет владение доменом, mTLS CA полностью под контролем администратора. Вкратце, вы создаёте сертификат, а потом добавляете или передаёте его устройствам, которые должны получить доступ.

Клиентский сертификат — X.509-сертификат, установленный на конкретном устройстве или в браузере.

Приватный ключ — используется при TLS-рукопожатии для доказательства владения сертификатом.

Что mTLS защищает

Тип атаки

Как mTLS блокирует

Эффективность

Man-in-the-Middle (MitM)

Злоумышленник не владеет приватным ключом клиента и не может завершить TLS-рукопожатие

Высокая

Credential Stuffing

Без клиентского сертификата сервер не переходит к HTTP, форма авторизации недостижима

Полная

Brute Force / перебор паролей

Аналогично — нет доступа к HTTP без сертификата

Полная

Phishing (кража пароля)

Даже зная пароль, злоумышленник не может войти без сертификата жертвы

Высокая

Spoofing / Impersonation

Идентичность привязана к криптографическому сертификату, подделать который без CA невозможно

Высокая

Session Hijacking

Сессионный токен может быть привязан к mTLS-сертификату (certificate-bound tokens)

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

L7 DDoS от ботов без сертификата

Боты без сертификата отсекаются на уровне TLS-рукопожатия, не нагружая приложение

Высокая

Обнаружение сервиса (Shodan, Censys)

Сервер в режиме STRICT не возвращает баннеры неавторизованным клиентам; сканер видит ошибку TLS

Частичная

Уязвимости приложения

Без сертификата невозможно как-либо воздействовать на сайт/сервис.

Высокая (до тех пор пока нет у злоумышленника сертификата)

Прежде чем открывать терминал

Перед настройкой нужно ответить на четыре вопроса.

1. Что у вас есть? Нарисуйте схему — пусть даже на бумаге. Какие сервисы выставлены наружу, какие работают только внутри, что с чем общается. В моём случае это выглядело так: один сервер, на нём Traefik как точка входа, за ним Dokploy на порту 3000, PostgreSQL и Redis внутри Docker-сети, наружу смотрят только порты 80 и 443.

2. Кто пользуется? Один человек или команда — это принципиально разные модели. Один человек означает один клиентский сертификат: простое управление, быстрый отзыв. Команда означает отдельный сертификат на каждого, процесс выдачи и отзыва, логирование того, кто и когда заходил.

3. Что произойдёт, если взломают? Это определяет уровень паранойи. Личный homelab с экспериментами — одно. Панель управления, где лежат production секреты клиентов — совсем другое.

4. От кого защищаетесь? Три реальных уровня угроз: случайный бот/сканер (самый частый), целевая атака (кто-то конкретно хочет попасть именно на сервер), инсайдер (человек, у которого уже есть доступ). mTLS отлично справляется с первым уровнем, частично со вторым, и никак с третьим.

Это обязательные вопросы.

Могут возникнуть и другие вопросы — например, подойдёт ли это под ваш стек? Разберём основную проблему, с которой вы столкнётесь: это reverse proxy и его поддержка mTLS. Нужно найти прокси с полноценной поддержкой mTLS. Какой реверс-прокси подойдёт лучше — смотрите самостоятельно для вашего сервера. При выборе reverse proxy стоит обратить внимание на следующее:

1. Механизм обновления сертификатов и CRL

Умеет ли прокси обновлять данные без перезагрузки (Hot Reload). На что смотреть: если у вас много сертификатов, «reload» конфига (как в классическом Nginx) может вызвать кратковременный скачок нагрузки или микролаги. И правильно ли работает «отзыв сертификатов» (Certificate Revocation). В нашем случае у Traefik нет поддержки Certificate Revocation (получается так: вы сделали отзыв сертификата, но он всё ещё работает). Итог: делаем костыльное решение. Как пример рабочего костыльного решения — создание отдельного промежуточного CA для каждого клиентского сертификата.

2. Поддержка OCSP Stapling

Проверка сертификата по огромным спискам CRL (файлам) — это медленно. Проверьте, умеет ли прокси сам запрашивать эти данные и кэшировать их.

3. Глубина проверки mTLS (Client Auth)

Иногда просто «проверить подпись» недостаточно. Обратите внимание, может ли прокси: проверять Certificate Chain (цепочку доверия) до определённого корня; проверять SAN (Subject Alternative Name) или специфические поля в сертификате клиента для маршрутизации; извлекать данные из сертификата и пробрасывать их в заголовки (X-Client-Cert-DN) бэкенду. (Может быть что-то ещё.)

4. Производительность шифрования

TLS требует ресурсов CPU. Это значит, что нужно правильно настроить прокси (и/или стек защиты), чтобы при каждой, скажем так, «попытке подключения» к серверу у нас не было сильной нагрузки и были защита и ограничения на попытки подключения, т.к. если у нас будут попытки DoS/DDoS-атак на TLS, то, соответственно, будет нагрузка на сервер = краш сервера.

5. Наблюдаемость (Observability)

Вы должны видеть, почему соединение разорвано. Хороший прокси должен давать детальные метрики: сколько ошибок TLS, сколько отказов из-за истекшего срока, сколько из-за отозванного сертификата.

Это обязательные требования к mTLS. Если какой-то из пунктов не работает — это проблема, и её придётся обходить костылями.

Реальный сценарий

Мини-homelab для управления Docker и иных сервисов/инструментов с GitHub и др.

Ниже скриншот, где уже выбрали и протестировали этот стек на устойчивость к типичным атакам, но минусы, конечно, тоже есть.

Протестировав достаточное количество атак, могу сказать вкратце, что при текущем стеке единственный вариант атаки, который я нашёл, — это DoS/DDoS на TLS и кража сертификатов. (Самоподписанный сертификат не пройдёт, если CA настроен правильно — сервер принимает только сертификаты, подписанные именно твоим CA. Мы это тестировали — без правильного cert соединение рвётся на handshake.)

Немного о стеке: Dokploy выступает как платформа для управления Docker и репозиториями (и другими сервисами). Webmin планируется ставить как инструмент управления самим сервером (не полноценно, но всё же), поэтому он присутствует здесь, но с предупреждением: доступ в интернет нужно либо очень хорошо защищать (для Webmin), либо не открывать вообще. Также, наверное, стоит упомянуть, что в этом сценарии все инструменты, сервисы и контейнеры должны быть под mTLS, т.к. это homelab, где доступ есть только у одного человека с 1–3 устройств условно (телефон, ПК и ещё что-нибудь для доступа к своим сервисам).

Минусы у этого стека тоже есть. Например, если отказаться от Traefik (ну, захотели поставить Nginx), то стоит отказаться и от Dokploy из-за его нативной интеграции с Traefik (часть функций не будет работать). Поэтому стоит, опять же, учитывать при «построении сервера», что вы получаете и что теряете.

Безопасность

Мы переходим к самому интересному — проверке защиты на базе нашего сценария. Какие инструменты стоит взять и что проверять?

Самое главное — фаерволл, и смотрим, что у нас доступно из интернета: смотреть рекомендую через nmap. В основном всё должно быть закрыто, и открыты только 80/443 порты. «Специфические порты» — это порты SSH или др. порты для чего-либо — стоит подумать, как защищать. В моём случае я не пользуюсь SSH, мне достаточно 80/443. Docker управляет iptables напрямую и обходит UFW (или другой фаерволл). Это значит, что даже если UFW настроен правильно, Docker может открыть порт наружу через свои собственные правила, и UFW об этом не узнает. Поэтому блокировать нужно через цепочку DOCKER-USER — это надёжный способ закрыть Docker-порты. Пример ниже:

# Разрешить Docker-сетям и localhost
iptables -I DOCKER-USER 1 -p tcp -s 172.16.0.0/12 --dport 3000 -j ACCEPT
iptables -I DOCKER-USER 2 -p tcp -s 127.0.0.1 --dport 3000 -j ACCEPT
# Заблокировать всем остальным
iptables -I DOCKER-USER 3 -p tcp --dport 3000 -j DROP

# Сохранить правила чтобы не слетели после перезагрузки
apt install iptables-persistent -y
netfilter-persistent save

Как проверить, какие порты открыты после того, что сделали выше? Используются nmap и следующие команды:

# Быстро – топ 1000 портов
nmap --top-ports 1000 ваш-ip

# Полная проверка всех 65535 портов (займёт ~2 минуты)
nmap -p- -T4 ваш-ip

В нашем случае результатом после настройки будут открыты только 80/443 порты.

Работает ли mTLS?

Убедитесь в первую очередь, что все возможности работают правильно: правильно создаётся сертификат, отозванный сертификат не работает и т.д., и т.п. — всё, что связано с mTLS.

Правильно ли вы настроили доступ к сервисам? В идеале сервер не должен отвечать без сертификата вообще. Пример: добавили на свой сервис / на свой домен mTLS-доступ — сервис без mTLS не ответит. Правильно. Но если сделать запросы на IP вашего сервера, то он ответит, потому что не закрыт mTLS. Это неправильно, может быть дырой в безопасности, и её обязательно нужно закрыть!

Как закрыть такую дыру?

В моём сценарии я добавил catch-all роутер, который будет отклонять все запросы не по домену, и применять mTLS глобально на entrypoint + firewall.

Попытки сделать атаки. Вот варианты, которые использовались для атаки на сервер:

  • Доступ по голому IP — должен вернуть 403.

curl -sk -o /dev/null -w "%{http_code}\n" https://ваш-ip

Объяснение: отправка запросов на IP, минуя домен, проверка, что сервер не отвечает без домена. Итог: 403. Означает, что соединение есть, но доступ запрещён.

  • Подмена Host заголовка — должен вернуть 421.

curl -sk -o /dev/null -w "%{http_code}\n" \
  -H "Host: ваш-домен" https://ваш-ip

Объяснение: обращаемся к IP, но подделываем заголовки, чтобы проверить, нельзя ли обойти mTLS, просто указав нужный домен в заголовке. 421 означает, что сервер потребовал прямое соединение.

  • Домен без клиентского сертификата — должен оборвать TLS.

curl -vk https://ваш-домен

Объяснение: обычная проверка, можно ли получить доступ без клиентского сертификата.

  • TLS-даунгрейд — должен отклонить соединение.

curl -sk --tls-max 1.2 -o /dev/null -w "%{http_code}\n" https://ваш-домен
curl -sk --tls-max 1.1 -o /dev/null -w "%{http_code}\n" https://ваш-домен

Объяснение: делаем попытки принудительно договориться об использовании старых версий TLS, сервер должен принимать только TLS 1.3.

  • Перебор путей — все должны вернуть 000 (TLS рвётся до HTTP).

for path in /admin /.env /api /dashboard /health /metrics; do
  code=$(curl -sk --max-time 3 -o /dev/null -w "%{http_code}" https://ваш-домен$path)
  echo "$code $path"
done
  • Traefik внутренние эндпоинты – все должны вернуть 403.

for path in /dashboard/ /api/ /api/rawdata /metrics /ping; do
  code=$(curl -sk --max-time 3 -o /dev/null -w "%{http_code}" https://ваш-ip$path)
  echo "$code $path"
done
  • Flood без сертификата.

# wrk – HTTP/2 flood
wrk -t4 -c100 -d30s https://ваш-домен
# Ожидаемый результат: 0 requests, ~2000+ read errors

# h2load – HTTP/2 rapid reset
apt install nghttp2-client -y
h2load -n 100 -c 10 https://ваш-ip
# Ожидаемый результат: 100 failed, 0 succeeded

Объяснение: с помощью wrk и h2load мы создаём множество одновременных подключений без сертификата, это проверка на поведение сервера под нагрузкой. Все соединения должны получить статус failed.

  • Заголовки ответа:

sudo curl -sk \
  --cert /path/to/client.crt \
  --key /path/to/client.key \
  -I https://ваш-домен | grep -iE "server|x-powered|strict-transport|x-frame"

Это всё стоит делать с другого устройства (да хоть с телефона через Termux). И пройтись по рекомендациям от TLS-аудита.

apt install testssl.sh -y
testssl https://ваш-домен

Что должны увидеть в идеале:

SSLv2/3, TLS 1.0/1.1/1.2  not offered
TLS 1.3                    offered (OK)
Forward Secrecy            offered (OK)
Heartbleed, POODLE, BEAST  not vulnerable
Rating: A+

Осталось только проверить, что работает Fail2ban. Возможно, есть и другие векторы атак (именно для этого стека), поэтому это прям… минимум для проверки. Можно ещё через DAST-инструменты найти какие-нибудь проблемы на сервере, но будьте осторожны при их использовании! (Что такое DAST-инструменты, можете изучить тут).

Заключение

Мы имеем хорошую защиту с минимальным потреблением ресурсов. Через Dokploy-панель можно смотреть мониторинг и запросы на сервер. По результатам тестов стек держит всё, что удалось «бросить»: запросы без сертификата, подмену заголовков, TLS-даунгрейд, перебор путей — всё рвётся на handshake. Traefik не отдаёт внутренние эндпоинты, по голому IP — 403, домен без сертификата — обрыв TLS.

Два реальных вектора, которые остаются, — DoS на TLS-рукопожатие и кража сертификата. Частично это закрывается через Fail2ban и rate limiting, но полной защиты нет. Из ограничений стека: Dokploy нативно завязан на Traefik — захотите сменить прокси, придётся менять и платформу. Webmin присутствует, но в интернет его лучше не открывать совсем. Стек рассчитан на одного человека с 1–3 устройствами — на команду адаптировать тоже возможно.

В конечном счёте mTLS — это инструмент. Как любой инструмент, он работает хорошо только тогда, когда вы понимаете, зачем берёте его в руки.

Источники и рекомендации

© 2026 ООО «МТ ФИНАНС»

Комментарии (4)