- Регистрация
- 23 Август 2023
- Сообщения
- 2 942
- Лучшие ответы
- 0
- Реакции
- 0
- Баллы
- 51
Offline

Привет, Хабр! Сегодня мы рассмотрим ту историю, когда удалённый секрет вдруг оказазывается вовсе не уничтоженным. Разберёмся, что происходит с вашими конфиденциальными данными внутри etcd, и почему простого удаления секрета недостаточно, чтобы навсегда вычеркнуть его из истории.
Секреты Kubernetes внутри etcd: база данных всё помнит
Начнём с основ: Kubernetes хранит вообще всё своё состояние (включая ваши Secrets) в etcd, распределённом key‑value хранилище. Когда вы создаёте Kubernetes Secret (например, через kubectl create secret или манифестом), Kubernetes сохраняет его как запись в etcd. При этом данные секрета не шифруются по умолчани, они лишь закодированы в base64. А Base64 это не шифрование, а всего лишь способ представить бинарные данные текстом. То есть любой, кто получит доступ к данным etcd, сможет легко раскодировать секреты..
Представим, мы создали секрет с паролем к базе данных:
$ kubectl create secret generic db-secret --from-literal=password=SuperSecret123
secret/db-secret created
Если запросить этот секрет и посмотреть YAML, то значение будет закодировано в base64:
$ kubectl get secret db-secret -o yaml
apiVersion: v1
kind: Secret
metadata:
name: db-secret
namespace: default
type: Opaque
data:
password: U3VwZXJTZWNyZXQxMjM=
Выглядит уже не так очевидно, верно? Однако достаточно раскодировать строку U3VwZXJTZWNyZXQxMjM=:
$ echo "U3VwZXJTZWNyZXQxMjM=" | base64 --decode
SuperSecret123
Вот и пароль в чистом виде. Это подтверждает простую истину: хранение секретов в base64 не делает их безопасными. Если у злоумышленника есть доступ к хранилищу etcd (или к бэкапу etcd), он прочитает все секреты из кластера: пароли, ключи API.
Почему Kubernetes так делает? Просто по дефолту нет прозрачного шифрования данных в etcd — об этом должен позаботиться администратор (включить encryption at rest). Многие кластеры (особенно самосборные) работают без шифрования etcd, полагаясь лишь на сеть и доступы. Имейте это в виду: ваши Secrets в etcd хранятся почти в открытом виде!
MVCC: удалил, но не исчезло
Теперь переходим к самому интересному. Допустим, вы осознали риск и решили убрать какой‑то чувствительный секрет из кластера. Вы выполняете kubectl delete secret,Kubernetes подтверждает удаление, в etcd ключ удаляется. Можно выдохнуть? Как бы не так! В etcd устроено не так, как в привычной базе данных. Etcd не сразу стирает данные, он работает по принципу MVCC (многоверсионное хранилище данных). Проще говоря, etcd хранит историю изменений для каждого ключа.
В итоге когда вы обновляете или удаляете ключ, etcd не перетирает старое значение, а помечает его устаревшим, сохраняя в хранилище в виде предыдущей версии. У удаления тоже есть версия, так называемый tombstone. При удалении ключа etcd создаёт tombstone‑запись, отмечая, что «в этой ревизии ключ удалён». Но старые данные при этом никуда мгновенно не деваются, они остаются в истории ревизий! И их можно достать.
Для эксперимента сохраним секрет непосредственно через etcdctl (предположим, у нас есть доступ к etcd):
# Сохраняем секрет в etcd вручную (для наглядности)
$ ETCDCTL_API=3 etcdctl put /myapp/secret "s3cr3tP@ss"
OK # допустим, это ревизия 10
# Проверяем, что ключ сохранился
$ etcdctl get /myapp/secret
/myapp/secret
s3cr3tP@ss
# Теперь "удаляем" секрет
$ etcdctl del /myapp/secret
1 # один ключ удалён (ревизия 11)
Мы удалили ключ, и при обычном запросе его уже нет:
$ etcdctl get /myapp/secret
# (пусто, ключ не найден)
Однако etcd позволяет запросить значение на предыдущей ревизии. Попробуем получить данные секрета по ревизии, предшествующей удалению:
$ etcdctl get --rev=10 /myapp/secret
/myapp/secret
s3cr3tP@ss
Как видим, секрет прекрасно читается по старой ревизии, даже после удаления! В данном случае мы явно указали номер ревизии (10), на которой ключ ещё существовал. Etcd вернул нам сохранённое значение.
Получается, что команда delete в Kubernetes (или etcdctl del) лишь помечает данные как удалённые, но не стирает их немедленно из хранилища. Все предыдущие версии ключа по‑прежнему живут внутри etcd и могут быть прочитаны, пока не произойдёт специальная процедура очистки — compaction.
compaction: заметает следы… со временем
Конечно, нельзя допустить бесконечного роста базы etcd за счёт старых версий. Для этого и существует механизМ. compaction — это операция, которая удаляет устаревшие ревизии из etcd. Администратор может вызвать её вручную через etcdctl compact, либо Kubernetes‑настройки могут запускать автокомпакцию. Например, kube‑apiserver по умолчанию инициирует компакцию etcd каждые ~5 минут.
Как работает компакция: вы указываете ревизию N, и etcd удаляет все предыдущие данные (до ревизии N включительно) из хранилища истории. Таким образом, старые версии ключей до этой ревизии уничтожаются логически. Если продолжить наш пример: секрет был удалён на ревизии 11. Компактируем журнал изменений вплоть до ревизии 11:
$ etcdctl compact 11
compacted revision 11
Теперь etcd забыл все, что было до 11-й ревизии включительно. Если попробовать снова запросить старую версию секрета, получим ошибку:
$ etcdctl get --rev=10 /myapp/secret
Error: etcdserver: mvcc: required revision has been compacted
Etcd говорит: извини, друг, данные по ревизии 10 я уже выкинул. Именно так и должно быть после успешной компакции, старые значения и tombstone для ключа /myapp/secret удалены из долговременного хранилища. Выглядит будто проблема решена: удалил секрет и скомпактовал — больше его нет нигде.
Но с системой хранения данных не всё так просто.
Фрагменты данных на диске: физическое удаление vs логическое
Компакция в etcd убирает старые записи с логической точки зрения, но физически файлы базы данных etcd сразу не сжимаются. Дело в том, что etcd использует внутри себя хранилище на основе B+Tree (библиотека BoltDB). Удаляя записи, etcd просто помечает страницы данных как свободные, но файл не уменьшает размер — внутри остаются «дыры» от удалённых записей. Эти пустоты могут всё ещё содержать фрагменты ваших секретов до тех пор, пока не будут перезаписаны новыми данными или пока база не будет сжата физически.
Для освобождения места и окончательного выбрасывания битов данных из файла существует операция дефрагментации. etcdctl defrag перепаковывает файл базы данных, убирая пустоты, и возвращает неиспользуемое место операционной системе. Только дефрагментация окончательно стирает следы старых значений из хранилища на уровне диска. В нашем случае после compact 11 файл etcd все еще содержит данные старого секрета, просто помеченные удалёнными. Выполнение etcdctl defrag запишет поверх этих битов и сократит размер файла.
Если кто‑то завладел резервной копией etcd или отключённым узлом etcd до дефрагментации, то, возможно, он сумеет извлечь оттуда удалённый секрет. Существуют утилиты для извлечения содержимого из снапшотов etcd. К примеру, etcd‑extractor способный вытащить Secrets и ConfigMap«ы из дампа etcd. Таким образом, удаление секрета без компакции/дефрагментации не спасёт от утечки, если бэкап уже сделан или диск etcd попадёт в чужие руки.
Подведём итог этой части: удаление ресурса в Kubernetes — это логическая операция, которая не мгновенно стирает данные. Etcd помнит прошлое: старые версии ключей хранятся до компакции, а физические биты до дефрагментации. «Удалил» вовсе не означает «исчез навсегда»!
Безопасная работа с секретами: рекомендации
Что же делать, чтобы конфиденциальные данные не превратились в тени внутри etcd?
Включите шифрование секретов в etcd. Kubernetes позволяет настроить Encryption at Rest — шифрование содержимого Secret«ов (и других ресурсов) перед сохранением в etcd. Это делается через EncryptionConfiguration и ключ шифрования. При включённом шифровании даже если кто‑то получит файл etcd, без ключа он не прочитает значения секретов (они будут зашифрованы). Учтите, что администратор кластера всё равно сможет расшифровать (ведь ключ хранится на мастере), но риск утечки при компрометации backup или диска существенно снижается.
Ограничьте доступ к etcd и секретам. Никто, кроме компонентов кластера и самых необходимых админов, не должен иметь прямого доступа к API etcd. Настройте строгие RBAC‑политики: минимум прав на чтение Secrets в Kubernetes API. Даже разработчикам обычно не нужны прямые чтения секретов — пусть приложение получает их через монтирование или переменные окружения. Помните, что человек с правами etcdctl get фактически может вычитать все секреты, если etcd не шифрован.
Регулярно компактируйте базу etcd. Kubernetes сам пытается это делать (каждые несколько минут сдвигает «окно» ревизий). Но убедитесь, что автокомпакция включена (параметры --auto-compaction-* у etcd или соответствующие настройки managed‑кластера). Компактация удаляет старую историю и сокращает окно, в течение которого секреты «висят» после удаления. Например, если компактация каждые 5 минут, то максимум через 5 минут удалённый секрет станет недоступен через API etcd. Без компакции же секрет будет храниться вечно (или до переполнения базы).
Дефрагментация. В рабочих кластерах имеет смысл периодически выполнять etcdctl defrag (скажем, раз в сутки или неделю, в зависимости от интенсивности изменений). Это не столько про безопасность, сколько про производительность и место, но побочно вы знаете, что на диске не болтаются старые биты. Планируйте короткие окна для дефрагментации, потому что на время операции etcd узел приостанавливается.
Используйте внешние хранилища секретов. Если требования безопасности очень высокие, рассмотрите подход, при котором секреты вообще не хранятся в etcd. Например, Secrets Store CSI Driver позволяет монтировать секреты прямо из внешнего хранилища (HashiCorp Vault, AWS KMS, GCP Secret Manager и так далее) непосредственно в поды в виде тома. При этом в etcd секрет не создаётся — Kubernetes лишь ссылается на внешний источник. В таком сценарии etcd не знает конфиденциальных данных (или хранит минимум метаданных), и проблема «удалил ≠ не уничтожил» смещается на внешнее хранилище (у которого свои механизмы удаления). Этот подход сложнее в настройке, зато существенно повышает безопасность: компрометация etcd не приведёт к краже секретов, которых там нет.
Ротация и минимизация. Всегда исходите из предположения, что секрет мог утечь. Храните пароли и ключи не дольше необходимого, регулярно меняйте (ротируйте) их. Если понимаете, что какой‑то секрет случайно оказался в Git или в открытом доступе, то даже удалив его из кластера, немедленно замените в системе (например, поменяйте пароль у БД). Потому что, как мы выяснили, в кластере он мог где‑то остаться. Лучше перестраховаться, чем потом гадать, откуда пришёл взлом.
Напоследок — важный вывод. В распределённых системах, таких как Kubernetes, очень легко упустить из виду какие‑то тонкие хвосты данных. Etcd — надёжное хранилище, но оно сделано с упором на консистентность и историю, а не на «право быть забытым». Мы, должны сами позаботиться о гигиене данных. Если нужно что‑то стереть навсегда — убедитесь, что прошлись по всем слоям: приложению, Kubernetes и самому etcd.
Берегите себя и ваши секреты. До встречи в следующих статьях.
На курсе «Безопасность в Kubernetes» вы разберёте реальные сценарии защиты контейнерной среды: управление секретами, политика доступа, защита сети и контроль образов. Программа построена вокруг практических кейсов и показывает, как внедрять безопасные процессы в живом кластере. Пройдите бесплатное тестирование по курсу, чтобы оценить свои знания и навыки.