
Введение
Это вторая часть цикла про mTLS. В первой разобрали теорию: как работает рукопожатие, от каких атак защищает и где принципиально бессилен. Здесь — практика. Разберём реальный сценарий: homelab на одном сервере с Traefik и Dokploy. Пройдём путь от модели угроз до конкретных попыток атаковать собственный сервер — с командами и объяснениями, что происходит.
Если первую часть не читали — не страшно. Ниже есть краткий раздел с основными понятиями, достаточный, чтобы двигаться дальше.
Кратко: что такое mTLS и зачем он нужен?
Уже читали первую часть? Пропустите следующий раздел и переходите сразу к Прежде чем открывать терминал.
Mutual TLS(mTLS) — расширение протокола TLS, при котором обе стороны соединения предъявляют и проверяют криптографические сертификаты. Если клиент не предъявляет сертификаты / предъявляет невалидный сертификат, соединение разрывается на транспортном уровне до любого HTTP-взаимодействия.
Основные причины его интеграции в homelab:
Защита «на подступах» (Drop до HTTP).
Защита сервисов без встроенного логин/пароль.
Безопасный доступ без VPN.
«Исключение» из списков сканирования ботами, которые сканируют интернет.
Удобное управление доступом.
Терминология по 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 — это инструмент. Как любой инструмент, он работает хорошо только тогда, когда вы понимаете, зачем берёте его в руки.
Источники и рекомендации
Второй сценарий: корпоративный K8s. Kubernetes-кластер, команда, Istio Service Mesh, автоматическая ротация сертификатов каждые 24 часа, SPIFFE ID вместо CN, проблемы каскадного отказа при перезапуске кластера. Это разные масштабы, риски, инструменты. Анна Лучник подробно разбирает этот сценарий в статье с DevOps Conf — рекомендую прочитать.
OpenSSL — Генерация CA, сертификатов.
© 2026 ООО «МТ ФИНАНС»