Спросите у своего специалиста по PKI, сколько корней доверия в компании. Он назовёт число. Оно будет неверным.
Регламент выпуска сертификатов существует, ключи корневого центра лежат в HSM, процедура ротации согласована с юристами и утверждена приказом. Всё это правда. И всё это описывает ровно один PKI из тех, что реально работают в инфраструктуре. В кластере Kubernetes рядом живёт второй, развёрнутый командой платформы полтора года назад в рамках задачи «включить mTLS между микросервисами». Никто ничего не прятал. Просто задача звучала как сетевая, а не как криптографическая.
Откуда он берётся
Istio поднимает mTLS между подами сам, и для этого ему нужен свой удостоверяющий центр. Оговорка важная: по умолчанию режим PERMISSIVE, то есть sidecar шифрует исходящий трафик к соседям по мешу, но входящий принимает и открытым текстом тоже. Строгий STRICT включают отдельно, и включают не всегда. При установке компонент istiod проверяет, лежит ли в namespace istio-system секрет cacerts с вашим промежуточным сертификатом. Если не лежит, istiod генерирует самоподписанный корневой сертификат сам и кладёт его в секрет istio-ca-secret. Срок действия по умолчанию — десять лет.
Дальше начинается штатная работа. Каждый под получает рабочий сертификат с идентификатором в формате SPIFFE, вида spiffe://cluster.local/ns/payments/sa/checkout. Сертификаты живут сутки и обновляются автоматически. Разработчики видят зелёные метрики mTLS и закрывают задачу. В отчёте для ИБ появляется строка «трафик между сервисами шифруется». Формально всё честно.
То же самое делает Consul Connect при helm install с включённым внедрением прокси. Linkerd честнее: его Helm-чарт требует передать якорь доверия явным параметром, зато linkerd install из командной строки сгенерирует самоподписанный якорь сам, и именно так ставят большинство тестовых контуров, которые потом становятся продом. Общее у всех трёх одно: кнопка «поставить» разворачивает удостоверяющий центр, и ни один шаг установки не требует согласования с безопасностью.
Что именно вы теряете
Закрытый ключ корня лежит в объекте Kubernetes Secret. Не в HashiCorp Vault, не в аппаратном модуле. В etcd, в кодировке base64, которую половина инженеров по инерции называет шифрованием. Если в кластере не настроен EncryptionConfiguration, ключ хранится в etcd открытым текстом. И попадает в каждый бэкап etcd, который ночью уезжает в объектное хранилище с политикой доступа, написанной другой командой в другом году.
Тот, кто прочитал этот секрет, получает право выпускать валидные сертификаты для любого сервиса меша. Не для одного, для любого. Он может представиться сервисом биллинга, пройти проверку mTLS, получить данные и уйти. Ваши политики авторизации Istio при этом продолжат работать как задумано, потому что с их точки зрения запрос пришёл от легитимного сервиса с правильным SPIFFE-идентификатором. Взаимная аутентификация перестаёт что-либо доказывать ровно в тот момент, когда доверие к корню становится необоснованным.
Проверьте, у кого в кластере есть право get на секреты в istio-system. Обычно выясняется, что доступ имеют все инженеры платформенной команды, оператор бэкапов, пара CI-сервисаккаунтов с широким ClusterRole и агент мониторинга, которому когда-то дали лишнее ради одной метрики.
Отзыва нет. Встроенный CA не публикует ни список отозванных сертификатов, ни точку OCSP: отзывать в этой модели попросту нечем и негде. Утечка ключа лечится только перевыпуском всей иерархии с одновременным рестартом рабочих нагрузок, то есть управляемым простоем всего меша. Плана такой операции у вас нет, потому что нельзя планировать реагирование для системы, о существовании которой не знаешь.
И последнее. Журнала выпуска не существует в том виде, который примет аудитор. Istiod пишет операционные логи, но это не реестр выданных сертификатов с привязкой к заявителю и основанию. Вопрос «покажите все сертификаты, выпущенные этим центром за квартал» останется без ответа.
Всё это удобно сложить в одну таблицу и показать её тому, кто принимает решение. Слева то, что вы получили вместе с кнопкой «поставить», справа — то, что уже описано в вашем регламенте выпуска сертификатов.
| Функция | Встроенный CA Istio | Корпоративный PKI |
|---|---|---|
| CRL (список отозванных сертификатов) | ❌ Нет | ✅ Есть |
| OCSP (проверка статуса по запросу) | ❌ Нет | ✅ Есть |
| Аудит выдачи | ❌ Логов нет | ✅ Логи в SIEM |
| Интеграция с HSM | ❌ Нет | ✅ Ключи в HSM |
| Политики именования | ❌ Автоматически, по SPIFFE | ✅ По стандарту компании |
| Срок действия корня | ⚠️ 10 лет по умолчанию, меняется только до установки | ✅ Настраивается, обычно 1–2 года |
| Ротация ключа | ❌ Только пересборка меша | ✅ Плановая процедура |
Ни одна строка этой таблицы не является дефектом Istio. Меш проектировали как систему, которая обязана работать без внешних зависимостей, и в своей задаче он её решает. Проблема появляется в тот момент, когда результат такой установки молча получает статус производственного корня доверия.
Возражение, которое вы услышите
Опытный инженер платформы ответит так: рабочие сертификаты живут двадцать четыре часа, механизм отзыва в такой модели избыточен, это осознанный архитектурный выбор команды Istio, а не дефект.
Про рабочие сертификаты он прав. Короткий срок жизни действительно закрывает задачу отзыва для конечных сертификатов, и спорить тут не с чем. Только корень живёт не сутки, а десять лет, и вот к нему этот аргумент не применим вообще никак. Компрометация ключа рабочего пода стоит вам одного дня. Компрометация корня стоит вам всего меша до конца десятилетия, и обнаружить её штатными средствами вы не сможете.
Разговор поэтому идёт не про отзыв. Он про то, что корень доверия производственной системы обязан жить там же, где живут остальные ваши корни, и управляться теми же процедурами.
Аудит: что запустить сегодня
Проверять нужно каждый кластер отдельно. Прод, стейджинг, тестовый контур инженера, который обещал его удалить в марте. Многокластерные инсталляции Istio часто настроены так, что у каждого кластера свой корень, и это худший из вариантов: единого списка нет ни у кого.
Первая команда показывает, есть ли центр внутри кластера:
kubectl get secret -n istio-system | grep -E 'istio-ca-secret|cacerts'
Секрет cacerts означает, что кто-то принёс сертификат снаружи. Секрет istio-ca-secret означает, что istiod сгенерировал его сам. Наличие только второго — уже готовый ответ.
Вторая команда достаёт публичную часть и показывает, кто это подписал:
kubectl get secret istio-ca-secret -n istio-system \
-o jsonpath='{.data.ca-cert\.pem}' | base64 -d | \
openssl x509 -noout -issuer -subject -dates
Совпадающие Issuer и Subject со значением вроде O=cluster.local дают самоподписанный корень. Дата окончания примерно на десять лет вперёд от даты создания кластера подтверждает, что это умолчание, к которому никто не прикасался: срок задаётся переменной CITADEL_SELF_SIGNED_CA_CERT_TTL и после выпуска корня уже не меняется. Если вместо этого вы видите имя своего корпоративного центра, команда сделала работу правильно, и дальше остаётся проверить только срок действия промежуточного сертификата.
Третья команда отвечает на вопрос, кто может забрать ключ:
kubectl auth can-i get secret/istio-ca-secret -n istio-system \
--as=system:serviceaccount:ci:deployer
Подставьте сюда реальные сервисаккаунты вашего CI, оператора бэкапов и системы мониторинга. Для флага --as нужно право impersonate, поэтому запускать придётся из-под администратора кластера — что само по себе неплохой повод заметить, у скольких людей оно есть. Результат стоит выписать в отчёт целиком: список субъектов с доступом к корневому ключу производственной системы обычно производит на руководство более сильное впечатление, чем любое описание сценария атаки.
Отдельно посмотрите на живой сертификат рабочей нагрузки, чтобы увидеть всю цепочку доверия глазами приложения:
istioctl proxy-config secret <pod> -n <namespace> -o json | \
jq -r '.dynamicActiveSecrets[] | select(.name=="default") | .secret.tlsCertificate.certificateChain.inlineBytes' | \
base64 -d | openssl x509 -noout -text | head -20
Istio не один такой
Закончив с мешем, не останавливайтесь. Проблема не в Istio, а в классе решений, которые обязаны иметь криптографию из коробки и поэтому создают её молча.
Ресурс ClusterIssuer типа selfSigned в cert-manager: kubectl get clusterissuer -o wide покажет, сколько независимых центров у вас завелось за время эксплуатации. Встроенный CA в Consul. Собственный корень HashiCorp Vault, если PKI secrets engine включали для одного сервиса и забыли. Harbor и MinIO с самоподписанными сертификатами интерфейса, которые инженеры добавили в доверенные на своих рабочих машинах и никогда оттуда не убирали. Конфигурации OpenVPN, собранные лет восемь назад по инструкции с форума. Внутренний CA самого Kubernetes, который вы контролируете ровно настолько, насколько контролируете апгрейды кластера.
Инвентаризация корней доверия — это отдельная работа, а не побочный эффект аудита одного продукта.
Что скажет аудитор
Пока речь шла об инженерном риске. Есть и вторая сторона: встроенный CA не проходит по формальным требованиям, под которые вы уже подписались. Это тот аргумент, который открывает бюджет, когда «нас могут взломать» не открывает.
| Стандарт | Требование | Встроенный CA Istio |
|---|---|---|
| PCI DSS 4.0 | Инвентарь доверенных ключей и сертификатов, защищающих передачу данных карт; закрытые ключи в защищённом устройстве или под ключом шифрования | ❌ Инвентаря нет, ключ лежит в Secret |
| 152-ФЗ и приказ ФСТЭК № 21 | Ограничение доступа к средствам защиты и ключевой информации, учёт носителей | ❌ Доступ у всех, у кого есть get на секреты в istio-system |
| ISO/IEC 27001, приложение A | Правила управления ключами на всём жизненном цикле, включая плановую смену | ❌ Реестра нет, смена только с пересборкой меша |
| Внутренний регламент ИБ | Все удостоверяющие центры в реестре активов, ключи в Vault или HSM | ❌ Центра нет в реестре, ключ в Kubernetes |
Последняя строка обычно оказывается самой рабочей. Ссылаться на международный стандарт долго, а на собственный приказ компании, подписанный в прошлом году, — быстро и без обсуждения.
Как должно быть
Позиция здесь одна, и компромиссов у неё нет. Встроенный самоподписанный CA в производственной среде недопустим.
Базовый вариант: ваш корпоративный корень подписывает промежуточный сертификат для меша, тот кладётся в секрет cacerts до установки Istio, и istiod работает как выпускающий центр внутри вашей иерархии. Срок жизни промежуточного берите от года до трёх, не больше. Процедуру замены опишите и проверьте на стейджинге раньше, чем она понадобится в проде.
Зрелый вариант: связка cert-manager и istio-csr от Jetstack. Istiod при такой схеме вообще перестаёт быть центром сертификации и превращается в прокси для запросов, а выпуск идёт через Issuer, за которым стоит Vault, AWS Private CA или ваш собственный центр. Ключ подписи при внешнем Issuer в кластер не попадает вообще, и это главное отличие от базового варианта. Оговорка: если за Issuer поставить обычный CA из самого cert-manager, ключ снова окажется в секрете Kubernetes — вы поменяете название проблемы, а не проблему.
Мониторинг ставьте на промежуточный сертификат с алертом за девяносто дней. Экспортер x509-certificate-exporter снимает даты прямо с секретов Kubernetes и отдаёт их в Prometheus, так что отдельную самописную проверку писать не нужно.
Что закрепить в процессе
Аудит закрывает сегодняшний день. Завтра команда развернёт новый кластер, и всё повторится, если проверка корней не станет частью стандартной процедуры.
Внесите в стандарт по эксплуатации Kubernetes прямой запрет на самоподписанные корни в продуктивных контурах. Добавьте проверку cacerts в чек-лист приёмки нового кластера, рядом с сетевыми политиками и настройкой EncryptionConfiguration для etcd. Опишите в реестре активов каждый удостоверяющий центр с указанием владельца, места хранения ключа и даты истечения. И запланируйте повторную инвентаризацию корней доверия раз в полгода, потому что за это время в инфраструктуре появится ещё что-нибудь, что умеет выпускать сертификаты само.
Начните с одной команды в одном кластере. Дальше станет понятно, насколько велик масштаб.