awesome-everything EN
↑ Обратно к восхождению

Деплой и инфра

Секреты при деплое: где они входят и где утекают

Суть Секреты должны входить при деплое/рантайме, а не запекаться в образ. Kubernetes Secret закодирован в base64, а не зашифрован — любой с доступом на чтение или утёкшим манифестом читает его за секунду.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на junior-высоте — поверхность
◷ 16 min

Коллега коммитит secret.yaml в GitOps-репозиторий. Выглядит безопасно — поле пароля это cGFzc3dvcmQxMjM=, непонятный блоб. Через полгода аудит безопасности запускает base64 -d и читает password123 меньше чем за секунду. Репозиторий приватный, но в нём 40 контрибьюторов, три форка и полная git-история, которую не вычистит никакой force-push. «Зашифрованный» секрет никогда не был зашифрован. base64 — это кодирование, а не замок.

Главное правило: никогда не запекай секреты в образ

Первый провал случается ещё до деплоя. Dockerfile с ENV API_KEY=sk-live-... или COPY .env . запекает секрет в слой образа. Этот слой адресуется по содержимому, кэшируется и пушится в реестр — а docker history плюс docker save воспроизведут содержимое каждого слоя. Удаление секрета в более позднем слое его не убирает; ранний слой всё ещё существует и доступен на pull любому с доступом на чтение реестра. То же с build-аргументами: --build-arg TOKEN=... оседает в метаданных образа.

Правило, которое сеньор не нарушает: секреты входят при деплое или рантайме, а не на этапе сборки. Образ — это почти публичный артефакт, относись к нему так, будто он утечёт. Конфигурация, меняющаяся от окружения к окружению (dev, staging, prod), и всё секретное инъектируется при старте контейнера, снаружи образа. Поэтому же один и тот же образ чисто продвигается из staging в prod: он не несёт окружение внутри себя.

Ловушка Kubernetes Secret: base64 — это не шифрование

Kubernetes Secret хранит значения, закодированные в base64. Новые инженеры читают непонятную строку и думают, что она защищена. Это не так. base64 — обратимое транспортное кодирование: echo cGFzcw== | base64 -d разворачивает его за секунду без всякого ключа. Любой, кто может прочитать объект Secret или получит копию манифеста, читает открытый текст. Модель угроз широкая: утёкший YAML в тикете, бэкап, неправильно настроенная RBAC-роль или git-история GitOps-репозитория.

Хуже того, по умолчанию значение лежит нешифрованным в etcd, key-value хранилище кластера. Любой с доступом к etcd — компрометация ноды, украденный бэкап — читает каждый секрет в кластере как открытый текст. Шифрование at rest по умолчанию выключено; нужно явно настроить EncryptionConfiguration, чтобы API-сервер шифровал перед записью в etcd. И даже тогда есть ловушка: включение не перешифровывает существующие секреты. Надо переписать каждый Secret (например, kubectl get secrets -A -o json | kubectl replace -f -) после включения, иначе старые значения остаются открытыми. На проде шифрование подкрепляют KMS-провайдером (AWS KMS, GCP KMS), чтобы ключ жил вне кластера.

УбеждениеРеальностьПоследствие
«base64 прячет значение»Обратимо за <1с, без ключаУтёкший манифест = утёкший пароль
«etcd шифрует мои Secret’ы»По умолчанию открытый текстКража бэкапа etcd = полный дамп секретов
«Я включил шифрование»Старые Secret’ы не перешифрованыУже существующие значения остаются открытыми
«Env-переменные нормально»Утечка через crash dump и /procСекреты индексируются в трекерах ошибок

Env-переменные vs файловые маунты: стиль инъекции важен

Когда секрет уже снаружи образа, инъектировать его можно двумя способами, и выбор имеет реальный вес для безопасности. Переменные окружения просты — process.env.DB_PASSWORD — но они утекают. Их наследует каждый дочерний процесс, который ты порождаешь, их можно прочитать в /proc/<pid>/environ всем с нужным доступом, и убийственное: crash dump захватывает всё окружение целиком. Observability-SDK (Sentry, Datadog) рутинно сериализуют состояние процесса при ошибке, так что стектрейс оказывается с твоим живым API-ключом, проиндексированным открытым текстом в стороннем лог-хранилище. Многие фреймворки ещё и печатают окружение на странице фатальной ошибки.

Файловые маунты — безопаснее по умолчанию. Секрет ложится файлом (например, /run/secrets/db-password, режим 0400, на tmpfs, который никогда не попадает на диск). Его нет в окружении, его не наследуют дочерние процессы, его нет в /proc/.../environ. Решающий операционный выигрыш: файловые маунты обновляются на месте при ротации секрета, тогда как env-переменные заморожены на старте контейнера и для нового значения нужен рестарт пода. Для креденшела, который ротируешь каждые 30–90 дней, эта разница — между rolling restart и тихой подменой на месте.

Почему это работает

Env-переменные кажутся безопасными, потому что невидимы в коде. Но «невидимо мне» — не «невидимо атакующему». Ровно те свойства, что делают env-переменные удобными — глобальность для процесса, наследование детьми, дамп при краше — это и есть свойства, которые их сливают. Файл, который ты читаешь один раз и больше не экспортируешь, имеет куда меньший радиус взрыва.

Реальный инструментарий: тяни из менеджера, шифруй до коммита

Ручное управление Secret’ами не масштабируется. Продакшен-ответ — держать источник истины в выделенном менеджере секретов (HashiCorp Vault, AWS/GCP Secrets Manager) и тянуть из него при деплое/рантайме:

  • External Secrets Operator (ESO) — контроллер, который следит за внешним менеджером и синхронизирует значения в нативные Kubernetes Secret по refreshInterval. Манифесты ссылаются на ExternalSecret, а не на сырое значение.
  • Secrets Store CSI Driver — монтирует секреты из менеджера прямо в под файлами, минуя etcd целиком; значение живёт только в tmpfs пода.
  • Sealed Secrets — для GitOps, где надо что-то закоммитить: он асимметрично шифрует секрет так, что расшифровать может только контроллер внутри кластера. Теперь блоб в git реально безопасно коммитить, в отличие от base64-Secret.
  • Vault dynamic secrets — Vault генерирует короткоживущий per-app креденшел по запросу (юзер БД, который существует час, потом авто-отзывается). Нечему долгоживущему утекать; украденный креденшел истекает раньше, чем станет полезен.
Выбери лучший вариант

GitOps-команде надо закоммитить секретный конфиг в приватный репозиторий, чтобы Argo CD его применил. Выбери подход.

Викторина

В поле data у Kubernetes Secret видно cGFzc3dvcmQ=. Насколько защищено значение?

Викторина

Почему файловые маунты секретов часто предпочитают переменным окружения на проде?

Расставь шаги по порядку

Расставь решения по работе с секретами, которые принимает сеньор, от самого безопасного:

  1. 1 Никогда не запекай секрет в образ — ни ENV, ни COPY .env, ни --build-arg
  2. 2 Держи источник истины в менеджере секретов (Vault / облачный Secrets Manager)
  3. 3 Тяни при деплое/рантайме через ESO или CSI-драйвер (или sealed для GitOps)
  4. 4 Предпочитай файловые маунты env-переменным, чтобы сжать поверхность утечки
  5. 5 Ротируй по расписанию; динамические короткоживущие креды убирают долгоживущие секреты вовсе
Вспомните перед уходом
  1. 01
    Коллега говорит: «наши Kubernetes Secret безопасны, потому что значения закодированы в base64». Поправь его точно.
  2. 02
    Почему многие команды инъектируют секреты файловыми маунтами, а не переменными окружения, и что это даёт?
Итог

Безопасно завести секреты в задеплоенное приложение начинается с одного правила: они входят при деплое или рантайме, а не запекаются в образ, потому что слои образа кэшируются, пушатся в реестр и воспроизводятся через docker history — секрет, удалённый в позднем слое, всё ещё живёт в раннем. Ловушка Kubernetes Secret в том, что base64 — это кодирование, а не шифрование: утёкший манифест это утёкший пароль, и по умолчанию значение лежит в etcd открытым текстом, так что надо явно включить шифрование at rest (на KMS) и потом перешифровать существующие Secret’ы. Стиль инъекции тоже важен — переменные окружения утекают через дочерние процессы, /proc и crash dump, которые индексируются трекерами ошибок, тогда как файловые маунты сжимают эту поверхность и, что критично, ротируются на месте без рестарта. На масштабе ответ — реальный инструментарий: держи источник истины в Vault или облачном Secrets Manager, тяни в рантайме через External Secrets Operator или CSI-драйвер, шифруй-до-коммита через Sealed Secrets для GitOps и предпочитай короткоживущие динамические креденшелы, чтобы украденный секрет истёк раньше, чем станет полезен.

Продолжить восхождение ↑Secrets at deploy: тест с выбором ответа
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.