mTLS и идентичность сервиса
Обычный TLS доказывает клиенту подлинность сервера. mTLS заставляет обе стороны предъявить сертификаты, превращая криптографическую личность в единицу авторизации между сервисами — а короткоживущие сертификаты и автоматическую ротацию делает самой трудной частью.
Платёжный сервис доверяет сервису заказов, потому что запросы приходят по приватной сети кластера с общим API-ключом в заголовке. Один скомпрометированный под — и атакующий уже внутри этой сети, держит ключ из утёкшей переменной окружения и переигрывает его из рабочей нагрузки, которой нечего делать рядом с платежами. Каждая проверка, которую выполнил платёжный сервис, — исходный IP в нужном CIDR, валидный bearer-токен — прошла. Сеть сообщила ему «это пришло из доверенного места», и в этом была вся история авторизации. Лечится это не более удачным правилом фаервола. Лечится тем, что каждый сервис доказывает, кто он есть, ключом, который держит только он, на каждом соединении, в обе стороны.
К концу этого урока ты будешь знать, как mTLS превращает криптографический сертификат в идентичность сервиса, почему короткоживущие сертификаты и автоматическая ротация — это и есть настоящая инженерная работа, и где mTLS заканчивается, а в дело должна вступить политика.
От «сервер настоящий» к «настоящие обе стороны»
Обычный TLS — это односторонняя аутентификация. Во время рукопожатия сервер присылает свой сертификат, клиент сверяет его с доверенным CA, и дальше клиент знает, что говорит с настоящим api.example.com. А вот сервер не узнаёт ничего проверяемого о клиенте — кто дотянулся до порта, тот и получил TLS-сессию. Эта асимметрия нормальна для браузера, заходящего на публичный сайт, потому что человек потом залогинится. Но для трафика между сервисами это дыра: там нет человека, а «логином» исторически был bearer-токен или сетевой ACL — и то и другое атакующий, уже находящийся внутри периметра, может украсть или удовлетворить.
mTLS (взаимный TLS) закрывает эту асимметрию. Рукопожатие TLS 1.3 (RFC 8446) поддерживает CertificateRequest: после того как аутентифицировался сервер, он просит клиента тоже предъявить сертификат, и клиент доказывает владение соответствующим приватным ключом подписью CertificateVerify поверх транскрипта рукопожатия. Теперь у обоих концов есть криптографически доказанная личность ещё до того, как пройдёт хоть один байт прикладных данных. Что критично: приватный ключ никогда не пересекает провод — сторона доказывает, что владеет ключом, подписывая, поэтому наблюдение или переигрывание рукопожатия не даёт атакующему ничего.
Это и есть несущий сдвиг для сети с нулевым доверием (NIST SP 800-207): доверие больше не свойство того, откуда пришёл пакет, а того, какая личность предъявила валидный учётный признак. Приватная сеть перестаёт быть границей доверия. Скомпрометированный под всё равно не сможет выдать себя за сервис заказов, потому что не держит его приватный ключ.
Что на самом деле идентифицирует сертификат
Сертификат аутентифицирует личность, но для сервисов личность — это не имя хоста, которое набрал бы браузер, а рабочая нагрузка. Господствующее соглашение здесь — SPIFFE: личность рабочей нагрузки кодируется как URI в поле Subject Alternative Name (SAN) сертификата, SPIFFE ID вида spiffe://prod.example/ns/payments/sa/orders. Эта строка говорит: в домене доверия prod.example это сервисная учётная запись orders в пространстве имён payments. Когда сервис заказов соединяется с платежами, платежи читают проверенный SAN, видят знакомый SPIFFE ID и затем решают, разрешено ли этой личности делать то, что она просит.
Вот эту последнюю оговорку инженеры и пропускают. mTLS даёт тебе аутентификацию — сильный, неподделываемый ответ на вопрос «кто это?». Он не даёт авторизации — «разрешено ли этой личности делать это?». Валидный сертификат из твоего домена доверия доказывает, что вызывающий — легитимная рабочая нагрузка; он ничего не говорит о том, должна ли эта нагрузка иметь право оформлять возвраты. Полный дизайн таков: mTLS устанавливает личность на уровне соединения, а слой политики (SPIFFE-осведомлённая авторизация, AuthorizationPolicy в Istio, проверка в OPA) принимает по этой личности решение allow/deny на каждый запрос. Смешать одно с другим — «сертификат прошёл проверку, значит, пропускаем» — это и есть способ построить плоский, полностью доверенный mesh, где любая скомпрометированная нагрузка может вызвать что угодно.
▸Почему это работает
Почему не оставить bearer-токены между сервисами? Bearer-токен по определению — учётный признак, который может переиграть любой, кто им владеет: он не несёт доказательства того, кто его предъявляет. Утёк он в лог, переменную окружения, неверно смаршрутизированный запрос — это отмычка до самого истечения (часто часы). Клиентский сертификат mTLS привязан к приватному ключу, который никогда не покидает рабочую нагрузку; владение доказывается подписью живого рукопожатия, так что красть с провода нечего — переигрывать нечего. Токены отвечают на «валидна ли эта строка?»; mTLS отвечает на «участвовал ли владелец именно этого ключа именно в этом рукопожатии?» — вещь куда более узкую и куда труднее подделываемую.
Самое трудное: ротация в масштабе
Криптография mTLS решена; жизнь или смерть — в эксплуатации. Если каждому сервису нужен сертификат и приватный ключ, ты теперь держишь центр сертификации и конвейер раздачи для тысяч короткоживущих учётных признаков. Зрелый инстинкт — делать сертификаты короткоживущими: часы, а не годы. Сертификат, живущий час, сжимает окно, в котором украденный ключ полезен, и почти снимает проблему отзыва: ты не воюешь с печально ненадёжной машинерией отзыва CRL/OCSP, а просто даёшь сертификату истечь и не перевыпускаешь его нагрузке, которую отрезал. Истечение становится твоим отзывом.
Но короткоживущие сертификаты работают, только если ротация полностью автоматизирована и перекрывается. Вот сбой, который поднимает людей среди ночи: задача обновления умерла, сертификаты истекли в 03:00, и внезапно каждый сервис отвергает каждую сторону разом — самонанесённый общекластерный отказ без всякого атакующего. Защиты таковы: ротировать задолго до истечения (обновлять на ~50–66% срока жизни, чтобы у провалившегося обновления оставались часы запаса), перезагружать новый сертификат без обрыва живых соединений и делать ротацию якоря доверия (CA) отдельным, более медленным процессом с перекрытием, где во время переключения одновременно доверяют и старому, и новому корню. На практике этот цикл за тебя крутит service mesh (Istio, Linkerd) или SPIRE из SPIFFE — выпуская, раздавая и ротируя сертификаты нагрузок через sidecar или агент, так что код приложения никогда не касается ключа. Сделка, которую ты покупаешь: настоящая личность на каждую нагрузку в обмен на эксплуатацию высокодоступного CA, чей отказ теперь — продакшен-отказ.
| Аспект | Сетевой ACL / общий токен | mTLS + идентичность нагрузки |
|---|---|---|
| Единица доверия | Исходный IP / владение строкой | Криптографическая личность (SPIFFE ID в SAN) |
| Украденный признак | Переигрываем весь срок жизни токена | Ключ не покидает нагрузку; на проводе нечего переиграть |
| Скомпрометированный под | Внутри сети → доверенный по умолчанию | Всё равно не предъявит чужую идентичность сервиса |
| Отзыв | Ротировать общий секрет повсюду | Дать короткоживущему сертификату истечь; не перевыпускать |
| Главная цена эксплуатации | Разрастание фаервола; раздача секретов | Держать HA CA + автоматическую ротацию с перекрытием |
Как зрелый инженер рассуждает о внедрении
mTLS не бесплатен, и честная формулировка — это компромисс, а не серебряная пуля. Ты получаешь доверие на основе личности, переживающее пробой сети и превращающее латеральное движение из «бесплатно внутри периметра» в «всё ещё нужен приватный ключ каждого сервиса». Платишь же ты тем, что CA должен быть высокодоступным (его отказ — твой отказ), циклом автоматической ротации, который нельзя дать молча сломаться, и реальной ценой CPU/задержки на каждом рукопожатии — поэтому ты терминируешь mTLS на sidecar и держишь сессии долгоживущими, чтобы её амортизировать. И ты должен помнить границу: mTLS аутентифицирует рабочую нагрузку, а не конечного пользователя. Личность человека (его сессия, его скоупы) по-прежнему едет внутри запроса на прикладном уровне; mTLS защищает трубу и доказывает идентичность сервиса-вызывающего, но сервис, которому доверено тебя вызывать, всё ещё может нести поддельное или раздутое по правам пользовательское утверждение, если ты его не проверишь.
Твои сервисы уже работают по mTLS через mesh: каждое соединение предъявляет валидный сертификат нагрузки из твоего домена доверия. Новое требование: только сервис `orders` может вызывать `POST /refunds` у `payments`. Как правильно это обеспечить?
Что в рукопожатии mTLS мешает атакующему, полностью записавшему рукопожатие легитимного клиента, переиграть его и выдать себя за этого клиента?
Команда делает сертификаты нагрузок валидными на 2 года, «чтобы не возиться с ротацией». Почему для mTLS-mesh это неверное решение?
Упорядочи шаги mTLS-соединения между сервисами, от открытия рукопожатия до принятия решения о доступе:
- 1 Клиент открывает TLS 1.3-соединение к серверу
- 2 Сервер предъявляет свой сертификат и CertificateRequest
- 3 Клиент предъявляет свой сертификат + подпись CertificateVerify, доказывающую владение ключом
- 4 Обе стороны сверяют предъявленные сертификаты с доверенным CA
- 5 Слой политики авторизует запрос против проверенной SPIFFE-личности
- 01Объясни, как mTLS меняет единицу доверия для трафика между сервисами и почему скомпрометированный под внутри сети всё равно не может выдать себя за другой сервис.
- 02Почему короткоживущие сертификаты и автоматическая ротация — самая трудная часть mTLS, и какой сбой кладёт весь кластер?
Обычный TLS доказывает клиенту только сервер, поэтому трафик между сервисами скатывается к доверию сетевому положению или переигрываемому bearer-токену — и то и другое побеждает внутренний атакующий. mTLS добавляет обмен CertificateRequest/CertificateVerify, так что обе стороны доказывают личность, подписывая живое рукопожатие приватным ключом, который никогда не покидает нагрузку, смещая единицу доверия с «откуда пришёл пакет» на «какая личность предъявила валидный признак» — сердце сети с нулевым доверием. Для сервисов эта личность — рабочая нагрузка, обычно SPIFFE ID в поле SAN сертификата. Что критично, mTLS лишь аутентифицирует; авторизация остаётся отдельным слоем политики, решающим, может ли проверенная личность совершить действие. Криптография проста — инженерия в том, чтобы держать высокодоступный CA и автоматический цикл ротации с перекрытием, с короткоживущими сертификатами, чтобы истечение заменяло хрупкий отзыв, а украденный ключ умирал за часы. Поэтому когда кто-то говорит «у нас mTLS, значит, мы zero-trust», твой первый вопрос: авторизует ли хоть что-то проверенную личность на каждом запросе — или ты лишь доказал, кто звонит, и затем доверил ему всё?
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.