Секреты в Kubernetes
Секрет Kubernetes — это base64, а не шифрование: открытый текст для любого с etcd, узлом или RBAC-чтением. Почему это важно и как управлять секретами без расползания копий по неймспейсам и логам CI.
Джуниор из вашей команды дебажит падающий под и вставляет манифест в тикет: kubectl get secret db-creds -o yaml. Поле data: — это стена из base64, поэтому он думает, что делиться им безопасно: оно выглядит зашифрованным. Вы декодируете одну строку в уме: cG9zdGdyZXM6Ly9hZG1pbjpodW50ZXIyQA== — это просто postgres://admin:hunter2@. Пароль от боевой базы теперь в комментарии Jira, проиндексирован поиском, доступен каждому подрядчику с правами просмотра. Никакого CVE, никакого эксплойта. Просто base64, принятый за замок.
К концу этого урока вы будете точно знать, почему секрет Kubernetes не зашифрован, кто может его прочитать и как управлять секретами без расползания, превращающего одну утечку в компрометацию всего парка.
Base64 — это кодирование, а не шифрование
Это самое дорогое заблуждение в безопасности Kubernetes, поэтому скажу прямо: base64 — это обратимое перекодирование без ключа. Кто угодно и где угодно может выполнить base64 -d и получить исходные байты обратно. В декодировании не участвует никакого секретного материала — в этом весь смысл base64: он существует, чтобы двоичные данные пережили текстовый канал, а не чтобы их защитить. Называть секрет «зашифрованным» из-за того, что в YAML показано aHVudGVyMg== вместо hunter2, — всё равно что называть окно «запертым», потому что вы заклеили его бумагой.
Что Secret в Kubernetes реально даёт по сравнению с ConfigMap — это операционные, а не криптографические свойства: он помечен так, что по умолчанию не печатается в открытых логах; его можно смонтировать как tmpfs-том (в памяти), а не писать на диск; доступ к нему управляется через RBAC как к отдельному типу ресурса. Это реальные, полезные свойства. Ни одно из них не является конфиденциальностью на диске. Байты лежат в etcd, и по умолчанию они лежат там открытым текстом.
Кто на самом деле может прочитать ваш секрет
Зрелый вопрос — никогда «это base64?», а «какова поверхность чтения?». Секрет открыт удивительно широкому радиусу поражения, и каждый путь — это реальная атака, которую мы видели в постмортемах:
| Путь чтения | Кто получает доступ | Что это останавливает |
|---|---|---|
| etcd на диске | Любой с узлом, бэкапом или украденным снимком etcd | EncryptionConfiguration (KMS-провайдер), а не base64 |
RBAC get/list secrets | Любой пользователь/SA с этим глаголом в неймспейсе | RBAC по минимуму прав; никогда не выдавать list secrets широко |
| Смонтирован в под | Любой, кто может сделать exec в под или прочитать его ФС | Сузить SA; монтировать только то, что нужно нагрузке |
| Токен дефолтного ServiceAccount | Скомпрометированный под, дотянувшийся до API с авто-смонтированными правами | automountServiceAccountToken: false |
| Логи CI / Git | Любой, кто читает лог пайплайна или коммитит манифест | Внешнее хранилище секретов; никогда не коммитить отрендеренный Secret |
Два пути удивляют людей сильнее всего: etcd на диске и RBAC list secrets. По умолчанию kube-apiserver пишет объекты Secret в etcd без шифрования — поэтому бэкап etcd в S3-бакете или украденный снимок тома — это полный дамп учётных данных. А глагол list для секретов в RBAC катастрофичен именно потому, что возвращает содержимое, а не только имена: единственная слишком широкая Role с list по секретам в неймспейсе отдаёт каждую учётку этого неймспейса тому, кто ею владеет. Это верхняя позиция в OWASP Kubernetes Top 10 не просто так.
Шифрование на диске: включаем замок
Фикс для пути через etcd — это шифрование на диске (encryption at rest), настраиваемое на API-сервере через EncryptionConfiguration. Урок здесь в том, какого провайдера вы выбираете, потому что очевидный вариант — это ловушка.
Ловушка — это провайдеры aescbc/aesgcm с ключом в файле конфигурации: да, они шифруют etcd, но ключ шифрования лежит на узле control-plane открытым текстом, поэтому атакующий, способный прочитать данные etcd, обычно может прочитать и файл ключа. Это шаг вбок, а не фикс. Сильный выбор — KMS-провайдер, выполняющий envelope-шифрование: каждый секрет шифруется ключом данных (DEK), DEK шифруется ключом шифрования ключей (KEK), который живёт только внутри внешнего KMS (AWS KMS, GCP KMS, Vault), и API-сервер обязан сделать сетевой вызов к KMS для разворачивания. Теперь украденный снимок etcd — это инертный шифртекст. Учтите одну ловушку: включение шифрования шифрует только секреты, записанные после включения, — вы должны переписать каждый существующий секрет (kubectl get secrets -A -o json | kubectl replace -f -), чтобы реально запечатать то, что уже лежит.
▸Почему это работает
Почему нельзя просто включить шифрование на диске и считать дело сделанным? Потому что шифрование на диске защищает только один из пяти путей чтения из таблицы — путь украденного etcd. Оно ничего не делает против слишком широкого RBAC list, против kubectl exec в под со смонтированным секретом или против учётки, вставленной в лог CI. Шифрование на диске необходимо и его часто пропускают, но это единственный слой. Поверхность чтения — вот что вы реально управляете, и большинство реальных утечек секретов происходит через RBAC и человеческое копирование, а не через украденные диски.
Расползание секретов: настоящий операционный сбой
Даже при включённом шифровании на диске и плотном RBAC сбой, который реально кусает команды, — это расползание (sprawl): один и тот же секрет скопирован в десять неймспейсов, вшит в три Docker-образа, вставлен в два хранилища переменных CI и закоммичен один раз в Git «просто чтобы разблокировать деплой». Расползание — вот что превращает одну ротацию в недельные археологические раскопки, а одну утечку — в инцидент масштаба всего парка, потому что вы больше не можете ответить «где живёт эта учётка?».
Зрелый паттерн — единый источник истины вне кластера плюс синхронизация внутрь, а не копирование внутрь. Канонический секрет вы держите в выделенном хранилище (Vault, AWS Secrets Manager, GCP Secret Manager), а контроллер — External Secrets Operator (ESO) или Secrets Store CSI Driver — подтягивает его в кластер как нативный Secret по требованию. Манифесты ссылаются на ExternalSecret, указывающий на хранилище; сама учётка никогда не лежит в Git, никогда в образе, а ротация происходит в одном месте и распространяется. Секрет в кластере становится кэшем, а не базой-источником записи.
Ограничь чтение секрета одним объектом
1/3У вас один пароль БД, используемый сервисами в шести неймспейсах. Сегодня это вручную созданный Secret, скопированный в каждый. Выберите подход, который лучше всего предотвращает расползание и делает ротацию операцией в одном месте.
Коллега говорит: «наши секреты в etcd в безопасности, потому что Kubernetes кодирует их в base64». Какая точная поправка?
Вы включаете шифрование на диске с KMS-провайдером на кластере, где уже есть 200 секретов. Что ещё нужно сделать и почему?
Упорядочьте от самого слабого к самому сильному как способ защитить пароль БД в Kubernetes:
- 1 Base64-Secret, закоммиченный в Git (открытый текст в истории)
- 2 Обычный Secret в etcd, без шифрования на диске
- 3 Шифрование на диске с локальным ключом aescbc на узле
- 4 Шифрование на диске через внешний KMS (envelope-шифрование)
- 5 Внешнее хранилище + синхронизация ESO, плотный RBAC, без копий в Git/образах
- 01Объясните, почему секрет Kubernetes не зашифрован, и перечислите, кто реально может его прочитать.
- 02Что такое расползание секретов, почему это реальный сбой и какой паттерн его предотвращает?
Секрет Kubernetes закодирован в base64, а это кодирование без ключа, а не шифрование — base64 -d обращает его мгновенно, поэтому секрет в комментарии Jira или закоммиченном манифесте — это утечка открытой учётки. По сравнению с ConfigMap секрет даёт операционные свойства (не печатается в открытых логах, монтирование tmpfs, отдельный RBAC), но никогда — конфиденциальность на диске. Его поверхность чтения широка: по умолчанию открытый текст в etcd, RBAC list (который возвращает содержимое), монтирование в под, авто-смонтированные токены ServiceAccount и CI/Git. Закрытие пути через etcd — это включение шифрования на диске, причём с выбором KMS-провайдера для настоящего envelope-шифрования, а не локального ключа aescbc, лежащего на том же узле, и с памятью о том, что нужно переписать существующие секреты, ведь шифрование применяется только при записи. Но сбой, который реально вызывает инциденты, — это расползание: копии одной учётки, разбросанные по неймспейсам, образам, CI и Git, делающие ротацию невозможной и превращающие одну утечку в компрометацию всего парка. Зрелый фикс — один внешний источник истины, синхронизируемый внутрь через External Secrets Operator или CSI-драйвер, — ссылка, а не копия, — чтобы секрет в кластере был кэшем, а ротация происходила ровно в одном месте. В следующий раз, увидев data:, полное base64, ваш рефлекс должен быть: кто может читать etcd, у кого есть list и сколько копий этого существует?
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.
Примени это
Примени этот урок в реальном проекте.