Если вы когда-либо выставляли сервис в интернет и смотрели на логи — вы знаете, что происходит в первые минуты. Сканеры, боты, перебор паролей. Firewall помогает, но не всегда. VPN — хорошо, но не всегда удобно и сами протоколы в России к примеру хорошо работают. А что если сервер будет просто отказывать в соединении всем, у кого нет нужного криптографического сертификата — ещё до того, как они увидят страницу логина? Это и есть mTLS.
В статье разберём: что такое mTLS и как работает рукопожатие, как это связано с Zero Trust, от каких атак защищает и где принципиально бессилен, какие риски несёт сама PKI-инфраструктура и где чаще всего ошибаются при реализации. В конце — практика: как мы в Opensophy сделали mtls.sh, bash-скрипт для управления mTLS-сертификатами под Traefik, и почему архитектура «промежуточный CA на каждого клиента» позволяет мгновенно отзывать доступ без CRL и OCSP в Traefik.
Статья будет полезна всем, кто хочет защитить свои сервисы — будь то домашняя лаборатория, панели управления вроде Proxmox или Portainer/Dokploy, внутренние API или любой сервис, который не должен быть доступен всем подряд. Если коротко: если вы выставляете что-то в интернет и не хотите, чтобы туда мог зайти кто угодно — mTLS для этого и существует.
Что такое mTLS
Mutual TLS (mTLS) — расширение стандартного протокола TLS, при котором обе стороны соединения предъявляют и проверяют криптографические сертификаты. В обычном HTTPS только клиент проверяет сервер; сервер не знает, кто к нему подключается, и полагается на прикладную аутентификацию (логин, пароль, токен). В mTLS сервер дополнительно требует клиентский сертификат, подписанный доверенным центром сертификации (CA).
Если клиент не предъявляет сертификат или предъявляет невалидный — соединение разрывается на транспортном уровне, до любого HTTP-взаимодействия. Именно поэтому пользователь видит:
Доступ запрещёнВаш сертификат отклонен сайтом или не был выдан.ERR_BAD_SSL_CLIENT_AUTH
Это знак того, что mTLS отработал штатно.
CA (Certificate Authority) — удостоверяющий центр. В случае mTLS организация выступает собственным CA: создаёт корневой сертификат и подписывает им клиентские сертификаты. В отличие от публичного TLS, где CA (Let’s Encrypt, DigiCert) проверяет владение доменом, mTLS CA полностью под контролем администратора. (Вкратце это вы делаете сертификат и потом добавляете/делитесь с теми какое устройство должно получить доступ.)
Клиентский сертификат — X.509-сертификат, установленный на конкретном устройстве или в браузере. Содержит публичный ключ и метаданные (CN, Organization, срок действия). Подписан CA.
Приватный ключ — используется при TLS-рукопожатии для доказательства владения сертификатом. Никогда не передаётся серверу. На практике распространяется в составе .p12-файла (защищённый паролем контейнер, содержащий ключ + сертификат), который импортируется в браузер или на устройство — после чего ключ хранится в системном хранилище.
Как работает рукопожатие
Стандартный TLS (одностороннее доверие)
-
Клиент отправляет
ClientHello— предлагает версии протокола и алгоритмы шифрования -
Сервер отвечает
ServerHelloи отдаёт свой сертификат -
Клиент проверяет сертификат сервера через доверенный CA
-
Стороны обмениваются
Finished— канал установлен
Сервер при этом не знает, кто подключился. Аутентификация пользователя — задача приложения (логин, пароль, токен).
mTLS (взаимная аутентификация)
-
Клиент отправляет
ClientHello -
Сервер отвечает
ServerHello, отдаёт свой сертификат и добавляетCertificateRequest— требование предъявить клиентский сертификат -
Клиент отправляет свой сертификат
-
Клиент отправляет
CertificateVerify— подпись приватным ключом, доказывающую что ключ действительно принадлежит ему -
Сервер проверяет три вещи:
-
сертификат клиента подписан доверенным CA
-
клиент владеет соответствующим приватным ключом
-
сертификат не истёк и не отозван
-
-
Только после успешной проверки сервер отправляет
Finished -
Клиент отвечает
Finished— шифрованный канал установлен
Если на шаге 5 что-то не так — соединение разрывается. Клиент даже не увидит HTTP-ответа.
Что mTLS защищает — и что нет
mTLS работает на транспортном уровне (L4/L5). Всё, что происходит до установления зашифрованного канала, — в зоне его ответственности.
|
Тип атаки |
Как 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 |
Частичная |
Cloudflare описывает использование mTLS (через API Shield) для защиты от неавторизованных запросов. Это документально подтверждённый сценарий применения, но не специфичная защита от volumetric DDoS.
После успешного TLS-рукопожатия mTLS передаёт управление прикладному уровню (L7). Всё, что происходит внутри установленного соединения — вне его ответственности. Согласно OWASP Top 10 следующие атаки mTLS не блокирует:
Cross-Site Scripting (XSS). Если приложение уязвимо к XSS, вредоносный скрипт выполняется в браузере легитимного пользователя (у которого есть сертификат). Запросы скрипта к серверу будут успешно аутентифицированы через mTLS — он исходит от доверенного клиента.
SQL Injection. mTLS не анализирует содержимое передаваемых данных. Вредоносный SQL-запрос, отправленный авторизованным клиентом, будет доставлен до базы данных в полной сохранности.
Cross-Site Request Forgery (CSRF). Браузер жертвы уже имеет mTLS-сертификат и установленную сессию. Поддельный запрос, инициированный вредоносной страницей, будет принят сервером как легитимный.
Broken Access Control. mTLS аутентифицирует — подтверждает, кто подключился. Авторизация — что ему разрешено — остаётся задачей приложения. Если RBAC настроен некорректно, обычный пользователь с сертификатом может получить доступ к admin-интерфейсу.
Уязвимые зависимости (Log4Shell и подобные). Уязвимости в серверных библиотеках эксплуатируются через легитимный mTLS-канал авторизованным клиентом.
Инсайдерская угроза. Сотрудник с выданным корпоративным сертификатом имеет полный mTLS-доступ. Его вредоносные действия mTLS не ограничивает.
Все эти типы атак доступны тогда, когда атакующий имеет уже mTLS-сертификат.
Риски, ошибки реализации и инфраструктура
mTLS создаёт асимметрию в контексте отказоустойчивости. Запросы от клиентов без сертификата не доходят до веб-сервера — ресурсы CPU не тратятся на парсинг HTTP, рендеринг страниц, запросы к БД. Но каждое mTLS-рукопожатие требует больше вычислительных ресурсов, чем обычный TLS (проверка двух сертификатов, дополнительные криптографические операции). Злоумышленник может инициировать тысячи незавершённых рукопожатий, исчерпывая CPU сервера — это классическая DoS-атака на уровне TLS. От SYN-флуда и UDP-амплификации mTLS не защищает вовсе — для этого нужны внешние средства.
При одновременной перезагрузке большого количества подов в K8s инфраструктуре (например, после обновления кластера) все они одновременно обращаются к CA за новыми сертификатами. При высокой нагрузке CA может не справиться с запросами, и сервисы в режиме STRICT не смогут подняться — возникает каскадный отказ. Меры профилактики: кэширование сертификатов, горизонтальное масштабирование CA, промежуточные центры выдачи, jitter при обновлении сертификатов.
Компрометация приватного ключа клиента. Если злоумышленник похитит .p12-файл с устройства пользователя (вместе с паролем или без), он получит полноценный mTLS-доступ до момента отзыва сертификата. Меры защиты: хранение ключей в аппаратных токенах (YubiKey, TPM), защита устройств паролём/биометрией, мониторинг аномальных сессий. Для большинства одиночных homelab это вообще не требуется, нужно только если у вас доступ уже от 2-5 человек к сервисам.
Компрометация корневого CA. Это самый опасный сценарий. Если приватный ключ корневого CA украден — злоумышленник может выпустить любой клиентский сертификат и получить доступ ко всему, что защищено mTLS. Вся инфраструктура доверия рушится. Рекомендуемые меры:
-
Держите CA-ключ offline. Идеально — на отдельной машине без доступа в сеть, которая включается только для выпуска новых сертификатов.
-
HSM (Hardware Security Module) — специализированное железо для хранения криптографических ключей. Грубо говоря, это может быть флешка, из которой ключ невозможно вытащить — устройство только подписывает данные внутри себя, наружу ключ не отдаёт никогда. Для homelab это избыточно, но в продакшне где требуется максимум безопасности это стандарт. (Хотя в других случаях и предприятиях флешки запрещены.)
-
Ограничьте доступ к файлу ключа — права 600, только root, никаких резервных копий в облаке.
Проблемы отзыва. Если говорим про K8S: в Service Mesh (к примеру Istio) проверка CRL и OCSP часто отключается из соображений производительности. Это означает, что скомпрометированный сертификат будет работать до истечения срока его действия. Отраслевая практика — использование короткоживущих сертификатов (от 2 до 24 часов) вместо классических механизмов отзыва. Istio по умолчанию выдаёт сертификаты сроком 24 часа. Если сервер настроен на обязательную проверку OCSP в режиме «fail-closed», атака на OCSP-сервер делает невозможным доступ для всех клиентов — даже легитимных.
Ошибки реализации. Исследователь GitHub Security Lab Майкл Степанкин представил на Black Hat USA 2023 и DEF CON 31 детальный разбор уязвимостей в реализациях mTLS. Ключевые выводы:
Распространённая архитектура: фронтенд-прокси (Nginx, HAProxy, Traefik) выполняет mTLS-аутентификацию и передаёт данные о клиентском сертификате бэкенду через HTTP-заголовки (например, X-Client-Cert или X-SSL-Client-DN). Если бэкенд не проверяет, что заголовки пришли именно от доверенного прокси, злоумышленник может отправить поддельные заголовки и выдать себя за другого пользователя.
Сертификат с CA:TRUE в Basic Constraints может быть использован для подписи других сертификатов. Если сервер не проверяет это поле, клиент с CA-сертификатом может выпустить произвольный клиентский сертификат и аутентифицироваться от имени любого пользователя. Степанкин обнаружил подобные CVE в нескольких open-source identity servers.
Если сервер выполняет проверку OCSP или CRL до верификации подписи сертификата, злоумышленник может использовать URL из расширения CRLDP для SSRF-атак или JNDI-инъекций (как в случае с Apereo CAS). Правило: отзыв проверяется после валидации подписи, не до.
Примечание если проверять источник: SAP-кейс описывался как «случай с Black Hat», однако в публичных материалах Black Hat USA 2023 (Степанкин) описаны уязвимости в open-source серверах (Apereo CAS и других), а не в SAP-продуктах. Реальные уязвимости Степанкина — в identity servers с неправильной обработкой цепочек сертификатов.
Практика: mtls.sh
Поскольку Istio заточен в основном под k8s, а другие решения для меня показались довольно «большими» — вплане того чтобы выпускать сертификаты приходиться писать много команд, поэтому представляю вам скрипт который упрощает выпуск сертификатов и делает управление намного легче. Скрипт заточен в основном под docker/podman итп контейнеры где стоит Traefik.
Хотелось бы предложить it-сообществу решение проблемы в Traefik а именно то что Traefik не поддерживает CRL. Это означает что отзыв сертификата не работает, скрипт решает эту проблему следующим путём.
Ключевое решение в mtls.sh — создание отдельного промежуточного CA для каждого клиентского сертификата:
Корневой CA├── Int-CA (alice) -> client.crt (alice)├── Int-CA (bob) -> client.crt (bob)└── Int-CA (carol) -> client.crt (carol)
Все промежуточные CA собираются в один файл clients-bundle.crt, который передаётся Traefik. При отзыве сертификата alice её промежуточный CA просто исключается из bundle. Bob и Carol не затронуты. Traefik подхватывает обновлённый bundle без перезагрузки (через file provider). Никакого CRL, никакого OCSP, никаких задержек. Этот подход позволяет реализовать отзыв без стандартных механизмов отзыва сертификатов — что особенно ценно в homelab-окружениях, где настраивать OCSP-ответчик может быть нецелесообразно.
Скрипт работает в двух режимах интеграции с Traefik. Режим new создаёт новый домен и полностью управляет его конфигурацией:
Домен: myapp.example.comTarget: http://localhost:3000-> Создаёт роутер, сервис и TLS-опции в mtls-manager.yml
Режим patch добавляет mTLS к существующему роутеру (например, созданному Dokploy) — минимально инвазивно:
Существующий файл: /etc/dokploy/traefik/dynamic/dokploy.ymlРоутер: my-existing-app-> Добавляет строку options: mtls-my-existing-app
Пресеты к путям также есть для Dokploy т.к это основная платформа для управления контейнерами и сайтами в opensophy проектах. Вы можете создать запрос в гитхабе чтобы добавить новые пресеты или сделать самому их.
Жизненный цикл сертификата в mtls.sh:
-
Добавить сервис (new или patch)
-
Создать сертификат -> получить client.p12
-
Импортировать .p12 на устройство
-
Доступ к сервису работает
-
При необходимости -> Отозвать (почти мгновенная блокировка)
-
При необходимости -> Удалить файлы
mtls.sh — инструмент для homelab и небольших инфраструктур. Он не был создан для продакшн-кластеров с тысячами сервисов, ECDSA-сертификатов (только RSA) и полноценного PKI с CRL/OCSP.
Возможные проблемы
Очистка браузерных хранилищ. При импорте .p12 браузер сохраняет не только клиентский сертификат, но и промежуточный CA из цепочки. При перевыпуске сертификатов старые промежуточные CA накапливаются в хранилище. Примеры ниже помогут убрать «хлам» из настроек браузера.
Linux:
# Найти базу сертификатовfind /home -name "cert9.db" 2>/dev/null# Установить certutilapt install libnss3-tools -y# Просмотр (Chrome или другого браузера)certutil -L -d /home/<user>/.local/share/pki/nssdb/# Удаление (по точному имени из колонки Certificate Nickname)certutil -D -d /home/<user>/.local/share/pki/nssdb/ -n "opensophy - mTLS-Manager"
Не удаляйте сертификаты с атрибутом
u,u,u— это активные клиентские сертификаты. Промежуточные CA имеют пустой атрибут,,.
Windows:
# ПросмотрGet-ChildItem -Path Cert:\CurrentUser\CA | Where-Object { $_.Subject -like "*opensophy*" }# УдалениеGet-ChildItem -Path Cert:\CurrentUser\CA | Where-Object { $_.Subject -like "*opensophy*" } | Remove-Item
Firefox на Windows использует собственное хранилище: about:preferences#privacy -> Просмотр сертификатов -> Центры сертификации.
Android (в зависимости от ОС/версии/модели): Настройки -> Безопасность -> Шифрование и учётные данные -> Доверенные данные -> вкладка Пользователь. Или: Настройки -> в поиске вводим Сертификат и ищем подобные данные в устройстве.
iOS / iPadOS (в зависимости от ОС/версии/модели): Если сертификат установлен через профиль: Настройки -> Основные -> VPN и управление устройством -> найти профиль -> Удалить.
Выводы и сравнение
mTLS — это отличный инструмент для конкретной задачи: убедиться, что к вашему сервису подключается только то устройство, которому вы явно выдали сертификат. Для homelab это означает что Proxmox, Portainer, Home Assistant и прочие админки перестают быть видны для сканеров и ботов — они просто получают ошибку TLS ещё до того, как увидят страницу логина. Никакого брутфорса, никакого credential stuffing, никаких попыток эксплуатировать форму входа. При этом важно понимать границы: mTLS доверяет устройству, а не человеку за ним. Если устройство скомпрометировано — скомпрометирован и доступ. Если приложение уязвимо к XSS или SQL-инъекции — mTLS это не исправит. Он работает на транспортном уровне и выше не смотрит.
mtls.sh мы написали потому что хотели простое решение без лишних зависимостей и написания огромного кол-во команд — только openssl и python3. Архитектура с промежуточным CA на каждого клиента решает главную проблему Traefik: отсутствие поддержки CRL. Отозвать доступ — значит просто убрать один сертификат из bundle. Никаких перезапусков, никаких сложных конфигураций.
Если вы только начинаете — попробуйте на одном сервисе. Защитите что-то одно, посмотрите как это работает, потом масштабируйте. mTLS проще в эксплуатации, чем кажется на первый взгляд.
|
Критерий |
mTLS |
VPN |
IAP |
|---|---|---|---|
|
Уровень OSI |
L4 (транспортный) |
L3 (сетевой) |
L7 (прикладной) |
|
Что защищает |
Конкретный сервис/домен |
Весь сетевой стек |
Конкретное приложение |
|
Установка клиента |
Импорт .p12 в браузер |
VPN-клиент |
Нет (браузер + SSO) |
|
Удобство для пользователя |
Прозрачно после импорта |
Нужно включать VPN |
Удобно (SSO/2FA) |
|
Страница логина в интернете |
Нет |
Нет |
Да |
|
Подходит для IoT/API |
Отлично |
Сложно |
Не применимо |
|
Управление доступом |
По устройству |
По IP/пользователю |
По идентификатору пользователя |
Когда я выбрал бы mTLS: нужен доступ только к конкретному сервису с устройства, где невозможно или нежелательно устанавливать VPN-клиент или VPN-протоколы/клиент попросту не работает; нужна защита API или автоматизации без UI-аутентификации.
Когда я выбрал бы VPN: нужен полный доступ к инфраструктуре, удалённая работа.
Когда я выбрал бы IAP: публичный сервис с множеством пользователей, SSO и 2FA обязательны, не нужно управлять сертификатами.
Статья доступна на сайте opensophy. Статьи, блоги и документации в первую очередь выходят и обновляются на сайте с указанием автора и соавторов.
ссылка на оригинал статьи https://habr.com/ru/articles/1025856/