Модуль 4.3: Керування секретами
Складність:
[СЕРЕДНЯ]— критична навичка для CKSЧас на проходження: 45-50 хвилин
Передумови: Модуль 4.2 (Pod Security Admission), основи RBAC
Що ви зможете зробити
Розділ «Що ви зможете зробити»Після завершення цього модуля ви зможете:
- Діагностувати типове розкриття Kubernetes Secret, відстежуючи, де значення з’являються в маніфестах, специфікаціях Pod, змінних середовища, томах під керуванням kubelet, в etcd та в записах аудиту.
- Впровадити
EncryptionConfigurationдля шифрування секретів у стані спокою, включно з локальними провайдерами, KMS v2, порядком провайдерів, перевіркою та ротацією ключів. - Порівняти External Secrets Operator, Sealed Secrets, HashiCorp Vault та Secrets Store CSI Driver для різних операційних і GitOps-моделей доставки секретів.
- Спроєктувати засоби контролю доступу до секретів за принципом найменших привілеїв за допомогою RBAC, проєктованих токенів ServiceAccount, політик аудиту, незмінних секретів та робочих процесів ротації на рівні застосунку.
Чому цей модуль важливий
Розділ «Чому цей модуль важливий»Гіпотетичний сценарій: платіжний API падає під час звичайного розгортання, тому що новий Pod не може автентифікуватися у своїй базі даних. Об’єкт Secret існує, Deployment посилається на правильний ключ, а образ застосунку не змінювався. Першопричина менш драматична і набагато поширеніша: оператор змінив пароль бази даних у хмарному менеджері секретів, а Kubernetes Secret залишився застарілим, Pod спожив старе значення через змінну середовища, і застосунок не мав способу перечитати її, окрім перезапуску. Нічого не було зламано, проте збій стався через ту саму слабкість дизайну, яку використав би зловмисник: команда не знала, який компонент є джерелом істини для облікових даних.
Kubernetes Secrets корисні, але самі по собі вони не є повноцінною системою керування секретами. Secret дає вам API-об’єкт для невеликих чутливих значень, спосіб монтувати ці значення в Pod’и та систему типів для поширених корисних навантажень, таких як TLS-ключі та облікові дані реєстру. Однак за замовчуванням значення кодується у base64, а не шифрується, зберігається у сховищі API-сервера, якщо ви не налаштуєте шифрування у стані спокою, і є доступним для читання будь-яким суб’єктом із достатніми правами RBAC або непрямою можливістю створювати Pod’и у просторі імен. Цю комбінацію легко зрозуміти неправильно, бо об’єкт називається Secret, проте гарантії за замовчуванням ближчі до “відокремити це значення від образу та шаблону Pod”, ніж до “криптографічно захистити це значення від адміністраторів кластера, резервних копій та широкого кола читачів”.
Іспит CKS очікує, що ви будете міркувати про цю відмінність під тиском часу. Вам може знадобитися дослідити Pod, який витікає пароль через змінну середовища, обмежити Role, яка надає list на кожен Secret у просторі імен, налаштувати --encryption-provider-config для API-сервера або пояснити, чому проєктований токен ServiceAccount безпечніший за довготривалий токен-Secret. Промислова робота додає ще один шар: вартість, ротацію, обсяг аудиту та режими відмов. Безпечний дизайн — це не інструмент із найпотужнішою маркетинговою сторінкою; це дизайн, у якому межа сховища, межа дешифрування, шлях доставки під час виконання та шлях людського доступу — усі відповідають ризику, який ви намагаєтеся зменшити.
Діагностика типового розкриття Secret
Розділ «Діагностика типового розкриття Secret»Kubernetes Secret — це API-об’єкт, поля data якого є рядками base64. Base64 — це формат кодування, який дозволяє довільним байтам безпечно проходити через YAML, JSON та HTTP API; це не шифрування, тому що під час декодування немає ні ключа, ні рішення про доступ. Якщо хтось може прочитати об’єкт, він може декодувати значення локально, і API-сервер не дізнається, що декодування відбулося. Тому першим діагностичним кроком є відокремити “хто може прочитати об’єкт Secret” від “хто може декодувати байти після читання”.
kubectl create namespace secrets-labkubectl -n secrets-lab create secret generic app-db \ --from-literal=username=app_user \ --from-literal=password=example-password
kubectl -n secrets-lab get secret app-db -o jsonpath='{.data.password}'printf '\n'kubectl -n secrets-lab get secret app-db -o jsonpath='{.data.password}' | base64 --decodeprintf '\n'Ця демонстрація навмисно проста, тому що іспит часто перевіряє, чи помічаєте ви очевидний шлях розкриття, перш ніж тягтися до більшого інструмента. Відповідь API містить закодовані байти, а не шифротекст. Якщо в кластері не налаштовано провайдер шифрування, API-сервер також зберігає об’єкт в etcd без конвертного шифрування. Це означає, що знімки etcd, прямий доступ до etcd і погано захищені резервні копії площини управління можуть розкрити чутливі значення навіть тоді, коли звичайний доступ kubectl get secret заблоковано. RBAC захищає читання через API; шифрування у стані спокою захищає рівень сховища; одне не замінює інше.
+-------------------+ +--------------------+ +------------------+| Secret manifest | ----> | kube-apiserver | ----> | etcd storage || data: base64 text | | admission + RBAC | | plaintext unless || stringData: input | | optional encryptor | | configured |+-------------------+ +--------------------+ +------------------+ | | | v | +--------------------+ | | kubelet on node | | | projects as files | | | or env variables | | +--------------------+ v+-------------------+| kubectl output || logs, Git, chats || copied snippets |+-------------------+Шлях під час виконання важить не менше за сховище. Коли контейнер отримує Secret через env.valueFrom.secretKeyRef, значення стає частиною середовища процесу. Воно може з’явитися у вихідних даних для налагодження, звітах про збої, випадкових дампах printenv і дочірніх процесах. Коли контейнер отримує Secret через том, kubelet записує файли в том із резервуванням у пам’яті та може оновлювати ці файли після зміни Secret, хоча застосункам усе одно потрібно перечитати файли або стежити за ними. Том — це не магічна секретність, але він дає вам дозволи файлів, вужчий шлях читання та кращу історію ротації, ніж змінні середовища.
apiVersion: v1kind: Podmetadata: name: env-leak-demo namespace: secrets-labspec: containers: - name: app image: busybox command: ["sh", "-c", "sleep 3600"] env: - name: DB_PASSWORD valueFrom: secretKeyRef: name: app-db key: passwordЗупиніться та передбачте: якщо користувач не може виконати kubectl get secret app-db, але може створювати Pod’и в secrets-lab, що він міг би зробити з наведеним вище Pod? Важлива пастка полягає в тому, що створення Pod може стати непрямим доступом на читання Secret. Суб’єкт, який може створити Pod у просторі імен, зазвичай може змонтувати або інжектувати будь-який Secret цього простору імен у контейнер, який він контролює, а потім прочитати значення зсередини контейнера. Ось чому RBAC для секретів не можна розглядати окремо від дозволів на створення робочих навантажень.
Типи Secret додають валідацію та конвенцію, а не універсальну конфіденційність. Opaque — це загальний тип для довільних пар “ключ-значення”. kubernetes.io/dockerconfigjson зберігає облікові дані для завантаження з реєстру у ключі .dockerconfigjson, щоб kubelet міг використовувати їх для завантаження образів. kubernetes.io/tls очікує tls.crt і tls.key, які зазвичай розуміють ingress-контролери та робочі навантаження. kubernetes.io/service-account-token — це застарілий тип Secret для довготривалого токена ServiceAccount, який зазвичай слід замінити проєктованими, прив’язаними токенами. Секрети bootstrap-токенів, що використовують тип bootstrap.kubernetes.io/token, підтримують TLS-завантаження kubelet і мають розглядатися як короткотривалі облікові дані для приєднання до кластера, а не як секрети застосунку.
| Тип Secret | Типове корисне навантаження | Головний ризик для перевірки |
|---|---|---|
Opaque | Облікові дані застосунку та ключі API | Значення довільні, тому конвенції іменування та ротації мають надходити від вашої команди. |
kubernetes.io/dockerconfigjson | JSON автентифікації реєстру | Викрадене значення може надати доступ на завантаження образів через різні середовища або реєстри. |
kubernetes.io/tls | tls.crt і tls.key | Приватний ключ має високий вплив і не повинен зберігатися у ConfigMap чи логах. |
kubernetes.io/service-account-token | Довготривалий API-токен на пред’явника | Для сучасних робочих навантажень надавайте перевагу проєктованим токенам, прив’язаним до аудиторії та обмеженим у часі. |
bootstrap.kubernetes.io/token | Дані bootstrap-токена вузла | Встановлюйте термін дії та обмежуйте область дії вузько, бо такий токен бере участь у встановленні довіри до вузла. |
Гіпотетичний сценарій: під час збою інженер виконує kubectl describe pod payment-api у спільному записі терміналу та фіксує імена змінних середовища, які точно показують, які ключі Secret споживає контейнер. Вихідні дані не друкують значення Secret, але вони розкривають карту облікових даних, імена об’єктів Secret і достатньо структури, щоб зловмисник із пізнішим доступом до простору імен знав, де шукати. Виправлення полягає не в забороні налагодження; воно полягає в тому, щоб споживати значення з високою цінністю як файли, уникати дампів середовища, обмежувати створення Pod і тримати логування аудиту сфокусованим на тому, хто читав або змінював об’єкти Secret.
stringData — ще одне місце, де іменування може ввести в оману тих, хто навчається. Це зручне для запису поле введення, яке дозволяє вам подати відкритий текст у маніфесті або у виведенні команди, а API-сервер зберігає отримане значення під data як base64. Це робить stringData зручним для разового створення, але ризикованим для Git та транскриптів чатів, бо поданий маніфест містить необроблене значення. У переглянутій конфігурації надавайте перевагу робочим процесам генерації або шифрування, які тримають відкритий текст поза репозиторієм ще до того, як Kubernetes API його побачить.
Межа вузла теж є частиною карти розкриття. Kubelet отримує дані Secret для Pod’ів, запланованих на його вузол, і записує проєктовані файли для цих Pod’ів, тож скомпрометований вузол може стати точкою збору облікових даних для робочих навантажень, що там виконуються. Це одна з причин, чому Pod Security Admission, посилення захисту вузла, обмеження hostPath та ізоляція середовища виконання все ще важливі в модулі про секрети. Кластер може мати ідеальний RBAC API та зашифровані знімки etcd, тоді як привілейований контейнер на вузлі читає змонтовані облікові дані сусідніх робочих навантажень через доступ до хоста.
Облікові дані для завантаження з реєстру заслуговують на окремий розгляд, бо вони часто переживають робоче навантаження, яке їх використовувало. Secret типу kubernetes.io/dockerconfigjson може надати доступ до цілого простору імен реєстру, а не лише до одного образу, і цей доступ може уможливити рух у ланцюжку постачання, якщо зловмисник зможе завантажувати приватні образи або інспектувати шари. Розглядайте Secret для завантаження образів як облікові дані розгортання з власним календарем ротації, обмеженням області дії на боці провайдера та межами просторів імен. Якщо робочому навантаженню потрібні лише публічні образи, видалити Secret для завантаження простіше, ніж захищати облікові дані, яких не повинно існувати.
На іспиті швидка діагностика Secret може йти узгодженим шляхом: дослідіть, як Pod споживає значення, дослідіть, хто може читати або непрямо монтувати Secret, дослідіть, чи має API-сервер налаштоване шифрування, та дослідіть, чи копіюють логи або політика аудиту чутливі корисні навантаження. Така послідовність утримує вас від надмірного зосередження на одному рівні. Pod, що використовує монтування файлів, усе одно може бути небезпечним, якщо Role надає list secrets; кластер із зашифрованим etcd усе одно може витекти через дампи середовища; а зовнішній менеджер усе одно може синхронізувати рідний Secret, який широке коло читачів може декодувати.
Впровадження EncryptionConfiguration та KMS v2
Розділ «Впровадження EncryptionConfiguration та KMS v2»Шифрування у стані спокою налаштовується на kube-apiserver, бо API-сервер — це компонент, який серіалізує API-об’єкти в etcd. Конфігураційний файл повідомляє API-серверу, які ресурси шифрувати та які провайдери пробувати. Перший провайдер у списку використовується для нових записів, а кожен зазначений провайдер може використовуватися для дешифрування старіших даних. Це правило порядку є серцем ротації: спочатку додайте новий ключ, тримайте старі ключі нижче для читання, перезапишіть об’єкти, перевірте і лише потім видаліть виведені з експлуатації ключі.
apiVersion: apiserver.config.k8s.io/v1kind: EncryptionConfigurationresources: - resources: - secrets providers: - aescbc: keys: - name: key-2026-05 secret: REPLACE_WITH_32_BYTE_BASE64_KEY - identity: {}Локальні провайдери мають різні компроміси. aescbc — це поширений локальний провайдер для кластерів, які поки що не можуть інтегруватися із зовнішнім KMS, і він потребує ключа завдовжки 32 байти, закодованого у base64. aesgcm швидший за aescbc, бо автентифікація та дешифрування GCM прискорені на сучасних чипах x86_64, але він накладає суворі обмеження на кількість записів для одного ключа. Офіційна таблиця провайдерів вимагає ротації ключа aesgcm кожні 200 000 записів — це операційна відповідь на іспиті та у промислових інструкціях. Застосовується також окрема криптографічна межа: за випадкового 96-бітного nonce межа дня народження для колізій починає мати значення приблизно на 2^32 (~4 мільярди) записів на ключ, після чого зловмисник, що спостерігає колізії, міг би вивести відкритий текст. Документація Kubernetes рекомендує secretbox замість aesgcm для нових кластерів, бо конструкція secretbox XSalsa20-Poly1305 використовує 192-бітний nonce, уникає вимоги ротації на 200 000 записів і повністю обходить обмеження колізій nonce. secretbox використовує конструкцію NaCl secretbox і також потребує 32-байтового ключа. identity не виконує шифрування і зазвичай має з’являтися лише як тимчасовий резерв для читання старих значень у відкритому тексті під час міграції, бо якщо помістити його першим, нові записи залишаться незашифрованими.
apiVersion: apiserver.config.k8s.io/v1kind: EncryptionConfigurationresources: - resources: - secrets - configmaps providers: - aescbc: keys: - name: key-2026-06 secret: REPLACE_WITH_NEW_32_BYTE_BASE64_KEY - name: key-2026-05 secret: REPLACE_WITH_OLD_32_BYTE_BASE64_KEY - identity: {}Увімкнення файлу — лише половина роботи. kube-apiserver має отримати --encryption-provider-config, а площина управління зі статичними Pod’ами має змонтувати хостовий шлях, що містить цей файл, у контейнер API-сервера. Поширена помилка на іспиті — створити коректний файл на вузлі та забути записи volumeMounts і volumes, через що API-сервер перезапускається з помилкою “шлях не знайдено”. Після того як API-сервер стає справним, новостворені секрети використовують перший провайдер, але наявні секрети залишаються у попередній формі сховища, доки їх не перезапишуть через API.
Підключення конфігураційного файлу до kube-apiserver
Розділ «Підключення конфігураційного файлу до kube-apiserver»Відредагуйте /etc/kubernetes/manifests/kube-apiserver.yaml на вузлі площини управління, щоб статичний Pod міг бачити файл конфігурації шифрування. Kubelet стежить за цим каталогом маніфестів і перезапускає статичний Pod після коректної правки, тож API-сервер автоматично підхопить нове підключення команди/тому. Найпоширеніша помилка — створити коректний encryption-config.yaml, але забути змонтувати його в контейнер kube-apiserver.
# In spec.containers[0].command, add:- --encryption-provider-config=/etc/kubernetes/enc/encryption-config.yaml
# In spec.containers[0].volumeMounts, add:- name: enc-config mountPath: /etc/kubernetes/enc readOnly: true
# In spec.volumes, add:- name: enc-config hostPath: path: /etc/kubernetes/enc type: DirectoryOrCreateУсі три частини — прапорець, монтування і том — мають бути присутніми, коли ви маєте намір увімкнути шифрування. Розрізняйте три режими відмов на іспиті:
--encryption-provider-configвідсутній — API-сервер ніколи не завантажує файл провайдера; нові записи залишаються у відкритому тексті. Це легко пропустити під час аудиту, бо кластер продовжує працювати нормально.- Прапорець присутній, але шлях монтування неправильний або файл конфігурації недійсний — API-сервер не запускається і входить у цикл збоїв (НЕ тихий відкат до identity).
- Конфігурацію завантажено з
identityна першому місці — шифрування фактично вимкнене для нових записів, навіть попри встановлений прапорець.
kubectl get secrets --all-namespaces -o json | kubectl replace -f -Перевірка має доводити як конфігурацію, так і перетворення даних. Прапорець API-сервера доводить, що процес знає, де міститься файл провайдера, але не доводить, що старіші записи було перезаписано. Зразок на рівні сховища має показувати префікс зашифрованого сховища для новоствореного Secret, тоді як звичайний kubectl get secret усе одно має повертати придатний об’єкт, бо дешифрування відбувається прозоро під час читання через API. Якщо перевірка відповідності лише запитує, чи існує прапорець, вона перевіряє намір, а не результат. Якщо вона перевіряє лише один новостворений Secret, вона може пропустити історичні дані у відкритому тексті.
ETCDCTL_API=3 etcdctl \ --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/server.crt \ --key=/etc/kubernetes/pki/etcd/server.key \ get /registry/secrets/secrets-lab/app-db | strings | head -5Після увімкнення шифрування значення має починатися з k8s:enc:aescbc:v1: (або :aesgcm:v1: / :secretbox:v1: / :kms:v2: залежно від провайдера). До шифрування, або коли identity використовується для шляхів запису, поля у відкритому тексті все ще видимі, і такий префікс не з’являється. Це канонічний крок доведення: верифікатор є авторитетним, коли ви можете прочитати необроблене значення etcd і побачити префікс k8s:enc:..., бо саме такий формат сховища записує API-сервер, коли шифрування активне.
Ротація ключів — це вправа з порядком провайдерів, а не однорядкова заміна. Додайте новий ключ над старим, перезапустіть або перезавантажте API-сервер відповідно до вашої моделі площини управління, перезапишіть захищені ресурси, перевірте, що нові записи сховища посилаються на новий ключ, і лише потім видаліть старий ключ зі списку провайдерів. Видалення старого ключа надто рано може зробити старіші збережені об’єкти нечитабельними. Залишення виведених з експлуатації ключів назавжди послаблює мету ротації, бо витеклий старий ключ залишається корисним для будь-якого об’єкта, який ніколи не перезаписувався.
Керований Kubernetes змінює того, хто виконує механічний крок, а не концепцію, яку вам потрібно зрозуміти. Деякі керовані сервіси надають прапорець або API для конвертного шифрування секретів, тоді як самокеровані кластери потребують прямого налаштування kube-apiserver. Для CKS вам усе одно слід розуміти формат файлу, бо середовище іспиту зазвичай нагадує площину управління в стилі kubeadm. У промисловій експлуатації запитайте, чи шифрує керований сервіс лише секрети або всі ресурси API, чи підтримуються ключі, керовані клієнтом, і яка операційна подія повторно шифрує історичні об’єкти після зміни ключа.
KMS v2 — це версія цього патерну промислового рівня для випадків, коли ви хочете тримати кореневий ключ шифрування поза кластером. У конвертному шифруванні Kubernetes шифрує кожен збережений об’єкт ключем шифрування даних, а потім просить зовнішній плагін KMS зашифрувати або дешифрувати цей ключ даних за допомогою ключа шифрування ключів, керованого AWS KMS, Google Cloud KMS, Azure Key Vault, Vault або іншим провайдером. API-сервер спілкується з плагіном через локальний доменний сокет Unix за протоколом провайдера KMS. Межа плагіна дозволяє кластеру використовувати керований ключ із підтримкою HSM, не навчаючи API-сервер кожного специфічного для хмари методу автентифікації.
apiVersion: apiserver.config.k8s.io/v1kind: EncryptionConfigurationresources: - resources: - secrets providers: - kms: name: cloud-kms-v2 apiVersion: v2 endpoint: unix:///var/run/kmsplugin/socket.sock timeout: 3s - aescbc: keys: - name: emergency-local-key secret: REPLACE_WITH_32_BYTE_BASE64_KEY - identity: {}KMS v2 є GA/стабільним з Kubernetes 1.29 і має перевагу над KMS v1, який є застарілим з 1.28 і вимкненим за замовчуванням з 1.29 (вмикайте лише за допомогою --feature-gates=KMSv1=true для застарілої міграції). Протокол v2 покращує звітування про справність, спостережуваність, обробку версій ключів і характеристики продуктивності, тож API-сервер може визначити, чи готовий плагін і яка версія ключа захистила значення. Це не знімає операційної відповідальності. Якщо плагін повільний, недоступний або неправильно налаштований під час записів і дешифрувань, шлях API-сервера, який обробляє зашифровані ресурси, може стати повільним або зазнати збою. Розглядайте плагін KMS як залежність площини управління, відстежуйте його затримку та частоту помилок і перевіряйте, що відбувається, коли зовнішній KMS обмежує запити (throttling) або ротує ключі.
Ідентичність плагіна KMS є частиною межі довіри. На хмарній платформі плагін або інтеграція площини управління потребує дозволу на використання конкретного ключа, і цей дозвіл має бути вужчим за загальне адміністрування менеджера секретів. На самокерованих кластерах шлях до файлу сокета та процес плагіна потребують захисту файлової системи, бо API-сервер залежить від цієї локальної кінцевої точки для операцій шифрування. Скомпрометований хост плагіна або надмірно повноважна хмарна ідентичність можуть знівелювати перевагу винесення кореневого ключа з etcd.
Версіонування ключів — це практична причина, чому в обговореннях KMS v2 так часто згадують спостережуваність. Провайдер може ротувати зовнішній ключ шифрування ключів, поки Kubernetes продовжує читати старіші об’єкти, але операторам потрібно знати, яка версія ключа активна, які об’єкти було перезаписано і чи зростає кількість збоїв дешифрування. Без цього сигналу ротація стає стрибком віри. Справна інструкція записує зовнішню версію ключа перед ротацією, спочатку перезаписує невеликий простір імен, перевіряє метрики API-сервера та плагіна і лише потім запускає ширшу міграцію.
Аварійне відновлення заслуговує на пробний прогін перед справжнім інцидентом. Зашифрований знімок etcd не корисний, якщо кластер відновлення не може дістатися до того самого KMS, не може автентифікуватися як ідентичність плагіна або втратив локальні ключі провайдера. Для локального aescbc створюйте резервну копію конфігурації шифрування окремо від etcd із суворим контролем доступу, бо конфігурація містить ключ, потрібний для дешифрування даних. Для KMS v2 задокументуйте, як відновити плагін, шлях до сокета, хмарну ідентичність і дозволи на ключі, перш ніж оголошувати стратегію резервного копіювання завершеною.
Інтеграція хмарного KMS змінює модель вартості так само, як і модель загроз. Керовані сервіси KMS та менеджери секретів зазвичай тарифікують збережені ключі або секрети, виклики API, а іноді й побічні ефекти між регіонами або від збору логів. KMS v2 зменшує непотрібні зовнішні виклики порівняно з менш ефективними дизайнами, але короткі терміни життя кешу, часті перезаписи, багато кластерів та агресивні політики аудиту все одно можуть створити помітну місячну вартість. External Secrets Operator додає ще один тарифікований патерн, бо кожен інтервал оновлення може викликати API хмарного секрету, тоді як логування читань Secret в аудит у великому обсязі може перенести вартість на ваш бекенд логування. Контроль вартості походить від довших інтервалів оновлення там, де це безпечно, вузького відбору ресурсів, справності локального плагіна та чіткого розуміння, яким кластерам справді потрібен кожен зовнішній секрет.
Перш ніж запускати це в реальному кластері, який вивід ви очікуєте від kubectl get --raw /readyz?verbose одразу після зміни маніфесту площини управління? Очікувана відповідь — не “він завжди успішний”. Перезапуски статичних Pod’ів створюють коротке вікно, коли API-сервер недоступний, а погана конфігурація шифрування може розтягнути це вікно, доки маніфест не буде виправлено. На іспиті вносьте одну контрольовану зміну, зберігайте резервну копію маніфесту та перевіряйте справність API перед перезаписом кожного Secret.
Порівняння моделей доставки зовнішніх секретів
Розділ «Порівняння моделей доставки зовнішніх секретів»External Secrets Operator, який зазвичай скорочують до ESO, розв’язує проблему джерела істини, а не проблему шифрування у стані спокою. Оператор стежить за ресурсами ExternalSecret, читає значення із зовнішніх провайдерів, таких як Vault, AWS Secrets Manager, AWS Systems Manager Parameter Store, Google Secret Manager, Azure Key Vault, 1Password та інші підтримувані бекенди, а потім записує звичайні об’єкти Kubernetes Secret для споживання робочими навантаженнями. Ця сумісність і є головною перевагою: наявні Helm-чарти та застосунки можуть продовжувати посилатися на рідні секрети, тоді як чутливе значення створюється та ротується поза кластером.
apiVersion: external-secrets.io/v1kind: SecretStoremetadata: name: team-vault namespace: paymentsspec: provider: vault: server: https://vault.example.com path: kv version: v2 auth: kubernetes: mountPath: kubernetes role: payments-reader serviceAccountRef: name: eso-payments---apiVersion: external-secrets.io/v1kind: ExternalSecretmetadata: name: payment-db namespace: paymentsspec: refreshInterval: 1h secretStoreRef: name: team-vault kind: SecretStore target: name: payment-db creationPolicy: Owner data: - secretKey: password remoteRef: key: payments/database property: passwordЗауважте: ExternalSecret перейшов з external-secrets.io/v1beta1 на external-secrets.io/v1 у ESO 0.10. Кластери, що працюють на ESO 0.9.x, усе ще використовують v1beta1 — замініть поле apiVersion відповідно.
Рішення між SecretStore та ClusterSecretStore — це рішення з керування (governance). SecretStore живе в одному просторі імен і зазвичай належить команді, яка володіє тамтешніми робочими навантаженнями. ClusterSecretStore має область дії кластера і корисний, коли платформна команда хоче, щоб одна конфігурація провайдера була спільною для багатьох просторів імен. Варіант із областю дії кластера потужний, тому поєднуйте його з політикою допуску, списками дозволених просторів імен та авторизацією на боці провайдера. Інакше власник простору імен може отримати чистий шлях через Kubernetes API до віддалених секретів, до яких він ніколи не повинен дотягуватися.
Ідентичність провайдера — це місце, де дизайни ESO часто успішні або зазнають невдачі. В AWS оператор може використовувати IAM Roles for Service Accounts; у Google Cloud — Workload Identity; в Azure — шлях керованої ідентичності; а з Vault він часто автентифікується через метод автентифікації Kubernetes. Ці ідентичності провайдера мають бути обмежені точними шляхами або іменами секретів, які оператор повинен читати. Якщо контролер ESO має доступ на читання за шаблоном (wildcard) до цілого облікового запису провайдера, межі просторів імен Kubernetes стають менш значущими, бо одне скомпрометоване визначення ExternalSecret може запросити набагато більше, ніж належить робочому навантаженню.
Інтервали оновлення — це не просто налаштування свіжості. Дуже короткий інтервал зменшує час між ротацією у провайдера та синхронізацією з Kubernetes, але також збільшує кількість викликів API провайдера, роботу контролера, події аудиту та ймовірність того, що збій провайдера стане видимим як постійний шум узгодження. Довгий інтервал знижує вартість і шум, але може залишити ротовані облікові дані застарілими до наступної синхронізації. Для облікових даних із високим впливом поєднуйте інтервал із явною подією ротації або процесом ручного узгодження, щоб ротація була навмисною, а не очікуванням, поки цикл опитування її помітить.
ESO все одно створює об’єкти Kubernetes Secret, тож він не усуває потреби у шифруванні etcd, RBAC, засобах контролю під час виконання або політиках аудиту. Його цінність у тому, що ротація та створення відбуваються в керованій зовнішній системі, а не в Git чи разових командах kubectl create secret. Компроміс — це залежність від контролера: якщо ESO втрачає доступ до провайдера, останній синхронізований Kubernetes Secret може залишатися придатним, тоді як нові значення перестають надходити. Це може бути саме тим, чого ви хочете для доступності, але це також може приховати невдалі ротації, доки пароль не сплине. Відстежуйте умови ExternalSecret та помилки провайдера як промислові сигнали, а не як необов’язковий шум оператора.
Шаблонізація ESO корисна, коли провайдер зберігає атомарні значення, але застосунку потрібен конкретний файл або тип Secret. Наприклад, провайдер може зберігати сертифікат, приватний ключ і пакет CA як окремі властивості, тоді як робоче навантаження очікує Secret типу kubernetes.io/tls або єдиний конфігураційний файл. Шаблонізацію слід тримати невеликою та придатною для перегляду, бо вона стає рівнем перекладу між даними провайдера та поведінкою під час виконання. Складні перетворення краще обробляти в застосунку або у виділеному конвеєрі підготовки ресурсів, ніж приховувати всередині синхронізації Secret.
Sealed Secrets — це інший патерн, орієнтований на GitOps. Клієнт kubeseal шифрує Secret за допомогою публічного ключа контролера, створюючи спеціальний ресурс SealedSecret, безпечний для коміту в Git для цільового кластера та області дії. Контролер усередині кластера тримає приватний ключ і дешифрує ресурс у рідний Secret. Це підходить командам, які хочуть GitOps на основі pull і не мають — або не хочуть мати — залежності під час виконання від хмарного менеджера секретів для кожного застосунку. Це не підходить для секретів, які потребують динамічного видавання, коротких оренд (leases) або централізованих слідів аудиту провайдера через багато кластерів.
Область дії Sealed Secrets — це функція безпеки, яку слід зрозуміти перед переміщенням зашифрованого YAML між середовищами. Запечатане значення може бути прив’язане до імені та простору імен, що не дозволяє комусь узяти шифротекст, призначений для одного Secret, і відтворити його під привілейованішим іменем деінде. Цей захист корисний, але він також означає, що перейменування або переміщення Secret може потребувати повторного запечатування. Ретельно створюйте резервну копію приватного ключа контролера, бо його втрата може зробити закомічені ресурси SealedSecret такими, що не піддаються дешифруванню, під час відновлення кластера.
Ключі Sealed Secrets ротуються автоматично кожні 30 днів, коли встановлено прапорець контролера --key-renew-period=720h (значення за замовчуванням), але старіші ключі зберігаються безстроково, тож наявні ресурси SealedSecret продовжують дешифруватися. Щоб примусово виконати ручну ротацію, створіть новий ключовий Secret у kube-system із міткою sealedsecrets.bitnami.com/sealed-secrets-key: active, і контролер використає його для нових запечатувань під час наступного узгодження; наявні ресурси SealedSecret не потрібно повторно запечатувати, бо кожен несе у своїх анотаціях відбиток ключа шифрування. Контролер дешифрує тим ключем, на який вказує відбиток, навіть після ротації.
apiVersion: bitnami.com/v1alpha1kind: SealedSecretmetadata: name: app-db namespace: productionspec: encryptedData: password: AgBy3i4OJSWK+...g8VPo3vP template: metadata: name: app-db namespace: production type: OpaqueЗначення encryptedData запечатано проти публічного ключа контролера (через kubeseal); контролер у kube-system дешифрує їх під час застосування та створює відповідний звичайний Secret.
+------------------+ public key +-------------------+| developer laptop | --------------------> | SealedSecret YAML || kubeseal client | | committed to Git |+------------------+ +-------------------+ | v +--------------------+ | cluster controller | | private key holder | +--------------------+ | v +--------------------+ | native Secret | | consumed by Pods | +--------------------+HashiCorp Vault на Kubernetes дає вам багатшу модель безпеки під час виконання, коли вам потрібні динамічні секрети, оренда, поновлення та відкликання. Мутаційний вебхук Vault Agent injector може додати агента-sidecar або init-контейнер, який автентифікується у Vault за допомогою Kubernetes-ідентичності робочого навантаження, записує секрети у спільний том та поновлює оренди там, де тип секрету підтримує поновлення. Vault CSI provider та Secrets Store CSI Driver можуть монтувати значення як томи без попереднього створення рідних Kubernetes Secret, залежно від конфігурації. Ці патерни зменшують термін життя статичних облікових даних, але вони вимагають, щоб застосунок або sidecar обробляли читання файлів, тайминг поновлення та поведінку при відмові, коли Vault недоступний.
Динамічні облікові дані бази даних — найясніша причина прийняти складність Vault. Замість зберігання одного спільного пароля бази даних місяцями, Vault може створити користувача бази даних з орендою для робочого навантаження, поновлювати її, поки робоче навантаження справне, і відкликати, коли оренда закінчується. Ця модель зменшує цінність викраденого облікового запису, але вона також змінює реагування на інциденти. Вам потрібно знати, як відкликати оренди, як зливати старі з’єднання і як поводиться застосунок, якщо поновлення не вдається, поки трафік усе ще йде.
apiVersion: v1kind: Podmetadata: name: vault-agent-demo namespace: payments annotations: vault.hashicorp.com/agent-inject: "true" vault.hashicorp.com/role: "payments-reader" vault.hashicorp.com/agent-inject-secret-db.txt: "database/creds/payments"spec: serviceAccountName: payments-api containers: - name: app image: busybox command: ["sh", "-c", "while true; do cat /vault/secrets/db.txt >/dev/null; sleep 30; done"]Гіпотетичний сценарій: критичний сервіс запускається без свого очікуваного файлу, сформованого Vault, бо анотацію інжекції було скопійовано в шаблон Deployment, але ServiceAccount не було прив’язано до ролі Vault. Образ контейнера справний, планування Kubernetes справне, а помилка застосунку виглядає як звичайна проблема відсутнього файлу. Урок безпеки полягає в тому, що доставка на основі інжектора створює залежність під час допуску та контракт файлу під час виконання. Ваша проба готовності (readiness probe) має зазнавати невдачі, якщо сформований файл відсутній, а ваш конвеєр розгортання має валідувати як анотації, так і прив’язки ServiceAccount до ролі Vault, перш ніж трафік переміститься.
# One-time setup inside the Vault pod (after vault is unsealed):kubectl exec -n vault vault-0 -- /bin/sh -c ' vault auth enable kubernetes vault write auth/kubernetes/config \ kubernetes_host="https://kubernetes.default.svc.cluster.local:443" \ kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \ token_reviewer_jwt=@/var/run/secrets/kubernetes.io/serviceaccount/token \ issuer="https://kubernetes.default.svc.cluster.local"'У цьому налаштуванні token_reviewer_jwt — це JWT ServiceAccount, який Vault використовує під час виклику Kubernetes TokenReview для валідації токенів сервісного акаунта клієнтських Pod’ів. issuer має збігатися з видавцем JWT кластера, налаштованим на API-сервері через --service-account-issuer, інакше запити на валідацію токенів відхиляються.
Secrets Store CSI Driver займає проміжне місце між споживанням рідного Kubernetes Secret та прямими викликами застосунку до зовнішнього провайдера. Kubelet монтує дані провайдера в Pod як том CSI, а необов’язкові функції синхронізації також можуть створити Kubernetes Secret, коли робоче навантаження чи чарт цього потребує. Це привабливо, коли ви хочете, щоб ротація з’являлася як оновлення файлів, і коли ви хочете, щоб зовнішній провайдер залишався справжнім сховищем. Драйвер запускає компоненти вузла, плагіни провайдера та конфігурацію SecretProviderClass, тож операційне володіння має охоплювати покриття вузлів, облікові дані провайдера та поведінку перезавантаження застосунку.
Прямі виклики застосунку до API хмарного секрету іноді є правильною відповіддю, але вони виносять площину управління Kubernetes зі шляху доставки і покладають більше відповідальності на код застосунку. Це може бути чудово для платформи із сильною ідентичністю робочих навантажень та бібліотеками, які кешують, оновлюють і повідомляють про помилки узгоджено. Це може бути погано для різнорідних команд, де кожен сервіс винаходить інший клієнт секретів. Рідна для Kubernetes доставка менш чиста з погляду zero-copy, але вона централізує патерни, які можуть інспектувати рецензенти, оператори та екзаменатори.
| Модель | Найкраще підходить | Головний компроміс |
|---|---|---|
| Рідний Secret плюс шифрування | Малі кластери, екзаменаційні завдання, прості застосунки | Kubernetes залишається межею сховища та RBAC. |
| External Secrets Operator | Застосунки очікують рідні секрети, провайдер зовнішній | Синхронізований Secret усе одно існує в etcd і має бути захищений. |
| Sealed Secrets | GitOps із зашифрованими маніфестами на кластер | Статичні корисні навантаження, резерв приватного ключа контролера, робочий процес повторного запечатування. |
| Vault injector або agent | Динамічні облікові дані, поновлення оренди, шаблонізація | Залежність під час виконання, потреби перезавантаження застосунку, операційна вартість Vault. |
| Secrets Store CSI Driver | Файлові монтування від зовнішніх провайдерів | Залежність від плагіна вузла та семантика ротації тома. |
Проєктування RBAC, токенів ServiceAccount, логування аудиту та ротації
Розділ «Проєктування RBAC, токенів ServiceAccount, логування аудиту та ротації»RBAC для секретів слід читати як документ про радіус ураження. get на іменований Secret — це вузьке читання. list на секрети — це дозвіл на масове викрадення даних, бо відповідь може містити кожен об’єкт у просторі імен. watch — це безперервне викрадення, бо він транслює майбутні зміни. create Pod’ів може стати непрямим доступом на читання, як обговорювалося раніше. update або patch на секрети можуть бути такими ж небезпечними, як і доступ на читання, бо зловмисний суб’єкт може замінити облікові дані, інжектувати довірені сертифікати або змінити облікові дані для завантаження з реєстру, щоб спрямувати робочі навантаження на образи, контрольовані зловмисником.
apiVersion: rbac.authorization.k8s.io/v1kind: Rolemetadata: name: payment-api-secret-reader namespace: paymentsrules: - apiGroups: [""] resources: ["secrets"] resourceNames: ["payment-db"] verbs: ["get"]---apiVersion: rbac.authorization.k8s.io/v1kind: RoleBindingmetadata: name: payment-api-secret-reader namespace: paymentssubjects: - kind: ServiceAccount name: payment-api namespace: paymentsroleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: payment-api-secret-readerЦя Role навмисно вузька, але це не вся політика. Якщо той самий ServiceAccount може створювати довільні Pod’и, виконувати exec у сусідні Pod’и або читати кожен Secret через іншу прив’язку, вузька Role мало допомагає. resourceNames корисний для get, update і patch, але широкі дозволи list і watch потребують окремої уваги, бо їх часто надають контролерам заради зручності. Контролер, якому справді потрібно стежити за секретами, має працювати у вузькому просторі імен, використовувати виділений ServiceAccount і мати операційну причину, задокументовану в імені Role або в анотації власника.
Контроль допуску (admission control) є супутником RBAC, коли ризиком є непрямий доступ. Role може казати, хто може створювати Pod’и, тоді як політика допуску може казати, які секрети ці Pod’и можуть монтувати, чи дозволено env.valueFrom.secretKeyRef для просторів імен із високою цінністю, і чи дозволено автомонтування токена ServiceAccount за замовчуванням. Це особливо корисно в кластерах із кількома орендарями, де адміністратори просторів імен законно створюють робочі навантаження, але не повинні мати змоги перетворити кожен Secret на читабельний файл. CKS зосереджується на вбудованих засобах контролю, проте промисловий урок полягає в тому, щоб блокувати небезпечні шляхи споживання Secret, перш ніж вони стануть специфікаціями робочих навантажень.
Токени ServiceAccount заслуговують на особливу увагу, бо Kubernetes історично представляв їх як довготривалі об’єкти Secret. Сучасні кластери використовують проєктовані токени ServiceAccount через API TokenRequest. Ці токени обмежені у часі, прив’язані до аудиторії та монтуються як проєктовані томи, що означає: токен, призначений для Kubernetes API, не є автоматично дійсним для кожного зовнішнього сервісу і не повинен жити вічно як Secret. Встановлюйте automountServiceAccountToken: false для Pod’ів, які не викликають API, і запитуйте явні проєктовані токени з вузькою аудиторією, коли робочому навантаженню потрібен токен ідентичності для конкретного отримувача.
Вибір аудиторії — це рішення з безпеки, а не мітка. Токен, викарбуваний для https://kubernetes.default.svc, призначений для Kubernetes API, тоді як токен, викарбуваний для Vault або внутрішнього брокера ідентичності, має прийматися лише цим отримувачем. Якщо сервіс приймає будь-який виданий Kubernetes токен без перевірки аудиторії та терміну дії, він відтворює стару проблему токена на пред’явника з новішою механікою. Під час перегляду запитайте, який компонент валідує токен, яку аудиторію він очікує і чи коротший термін життя токена за ймовірне вікно виявлення та реагування для скомпрометованого Pod.
apiVersion: v1kind: Podmetadata: name: projected-token-demo namespace: paymentsspec: serviceAccountName: payment-api automountServiceAccountToken: false containers: - name: app image: busybox command: ["sh", "-c", "sleep 3600"] volumeMounts: - name: api-token mountPath: /var/run/secrets/tokens readOnly: true volumes: - name: api-token projected: sources: - serviceAccountToken: path: api-token audience: https://kubernetes.default.svc expirationSeconds: 3600Логування аудиту — це те, як ви відповідаєте на питання “хто прочитав Secret?” постфактум, але його треба налаштовувати обережно, бо логи аудиту можуть стати ще одним місцем, де витікають чутливі дані. Для читань Secret у більшості випадків використовуйте рівень Metadata, бо RequestResponse може записувати тіла відповідей для запитів API, а це останнє, чого ви хочете для Secret. Наведена нижче політика логує дієслова в стилі читання щодо ресурсів Secret без захоплення корисного навантаження Secret. У площині управління зі статичними Pod’ами політику аудиту та шлях до логу треба змонтувати в контейнер kube-apiserver так само, як і файл конфігурації шифрування.
apiVersion: audit.k8s.io/v1kind: PolicyomitStages: - RequestReceivedrules: - level: Metadata verbs: ["get", "list", "watch"] resources: - group: "" resources: ["secrets"] - level: Metadata verbs: ["create", "update", "patch", "delete"] resources: - group: "" resources: ["secrets"] - level: None nonResourceURLs: - /healthz* - /readyz* - /livez*Аналіз аудиту має відрізняти нормальні контролери від дивних людей чи робочих навантажень. ESO, cert-manager, ingress-контролери та автоматизація образів можуть законно читати або оновлювати певні секрети, але ці звернення мають надходити від передбачуваних ServiceAccount у передбачуваних просторах імен. Користувач-людина, який виконує get secrets у проді, обліковий запис CI, який перелічує кожен Secret, або ServiceAccount робочого навантаження, який стежить за секретами поза своїм простором імен, — це інший сигнал. Будуйте списки дозволених з очікуваних ідентичностей контролерів і розслідуйте відхилення, замість того щоб розглядати кожну подію аудиту Secret як однаково термінову.
Ротація — це область проєктування, де багато технічно правильних систем секретів зазнають невдачі у проді. Kubernetes Secret, змонтований як том, може оновитися у файловій системі контейнера після зміни об’єкта, але змінна середовища не оновлюється, доки контейнер не перезапуститься. Secret, змонтований із subPath, також не отримує живих оновлень через звичайний механізм проєктованого тома. Незмінні секрети (immutable Secrets), стабільні з Kubernetes 1.21, покращують безпеку та продуктивність kubelet для значень, які не повинні змінюватися на місці, але вони змушують застосовувати патерн “створити нове ім’я та розгорнути” для ротації. Це функція, коли ви хочете навмисних розгортань, і проблема, коли ви очікували тихої заміни.
apiVersion: v1kind: Secretmetadata: name: payment-db-2026-05 namespace: paymentstype: Opaqueimmutable: truestringData: username: app_user password: example-passwordПоведінка застосунку завершує історію ротації. Якщо обліковими даними є пароль бази даних, застосунку може знадобитися злив пулу з’єднань, спостерігач за файлами (file watcher), SIGHUP, HTTP-кінцева точка перезавантаження або перезапуск Deployment. Якщо обліковими даними є TLS-сертифікат, серверному процесу може знадобитися перезавантажити свій слухач (listener), а клієнтам — поширення пакета довіри. Якщо обліковими даними є динамічний матеріал Vault з орендою, застосунок має вміти замінити його до закінчення терміну дії або терпіти, що sidecar робить це на диску. Хороший дизайн Secret називає систему сховища, механізм доставки, тригер перезавантаження, шлях відкату та сигнал аудиту.
Практична інструкція ротації має два годинники. Перший годинник — це годинник сховища секретів: коли значення провайдера змінюється, коли старе значення перестає працювати і коли відкат стає неможливим. Другий годинник — це годинник робочого навантаження: коли Pod’и отримують нове значення, коли процеси перевідкривають або перезавантажують його, і коли перевірки справності доводять, що нові облікові дані справді використовуються. Узгодьте ці годинники перед вікном зміни. Якщо провайдер анулює старий пароль до того, як Pod’и перезавантажать новий, збій є самозаподіяним, навіть попри те, що кожен окремий інструмент поводився як задокументовано.
Розбори екзаменаційних патернів CKS
Розділ «Розбори екзаменаційних патернів CKS»Коли іспит просить вас визначити розкритий Secret у специфікації Pod, починайте з режиму споживання та дозволів, а не з імен інструментів. Шукайте env.valueFrom.secretKeyRef, широкі імпорти envFrom, монтування Secret через subPath, які не оновлюватимуться, та токени ServiceAccount за замовчуванням на Pod’ах, яким не потрібен доступ до API. Потім перевірте Role простору імен на list і watch для секретів і пам’ятайте, що створення Pod може бути непрямим шляхом читання. Відповідь часто є невеликою правкою YAML плюс коротким поясненням радіуса ураження.
Коли іспит просить EncryptionConfiguration, спочатку напишіть найменшу дійсну конфігурацію та розширюйте її, лише якщо завдання вимагає більше ресурсів. Поширена прохідна форма — це apiVersion: apiserver.config.k8s.io/v1, kind: EncryptionConfiguration, resources: ["secrets"], шифрувальний провайдер на кшталт aescbc та identity останнім. Прапорець API-сервера та монтування тома статичного Pod є частиною відповіді, бо файл на хості невидимий для контейнера, доки його не змонтовано. Після відновлення API-сервера створіть новий Secret і перезапишіть старий, щоб довести шлях міграції.
Коли іспит просить обмежити доступ до Secret за допомогою RBAC, уникайте дозволів на читання в межах усього простору імен, якщо питання явно не описує контролер, якому вони потрібні. Іменоване правило get із resourceNames зазвичай є правильним примітивом для одного робочого навантаження, що читає один Secret. Валідуйте за допомогою kubectl auth can-i, використовуючи повний рядок суб’єкта ServiceAccount, потім перевірте негативний випадок для list secrets. Якщо той самий обліковий запис може створювати довільні Pod’и, згадайте, що створення робочих навантажень також має бути обмежене, бо інакше іменоване правило читання не є єдиним шляхом до значення.
Коли іспит дає вам логи аудиту, зосередьтеся на дієслові, користувачі, просторі імен, ресурсі та статусі відповіді. Успішний get або list щодо секретів від неочікуваного користувача важливіший за відхилений запит від сканера. Повторюваний watch від невідомого ServiceAccount може вказувати на компрометацію контролера або скопійований ClusterRoleBinding. Не рекомендуйте логування рівня RequestResponse для читань Secret рефлекторно, бо воно може скопіювати захищене значення в бекенд логування. Записи аудиту рівня Metadata зазвичай є безпечнішим джерелом доказів.
Коли екзаменаційний сценарій включає зовнішнє оснащення для секретів, назвіть рідний ризик Kubernetes, що залишається. ESO синхронізує значення провайдера в Kubernetes Secret, тож шифрування etcd і RBAC усе одно застосовуються. Sealed Secrets захищає сховище Git, але після дешифрування створює рідний Secret. Інжекція Vault та монтування CSI можуть уникнути синхронізованого рідного Secret, але вони додають залежності під час виконання та вимоги до перезавантаження застосунку. Це порівняння — спосіб уникнути поширеної хибної відповіді, що встановлення інструмента автоматично розв’язує кожен рівень керування секретами.
Заключна екзаменаційна звичка — записати життєвий цикл облікових даних одним реченням перед зміною YAML. Наприклад: “Пароль походить із Vault, ESO синхронізує його в рідний Secret щогодини, Pod монтує його як файл, застосунок перезавантажується при розгортанні, а логи аудиту фіксують читання Secret на рівні метаданих.” Це речення швидко виявляє відсутні ланки. Якщо ви не можете назвати джерело, механізм доставки, споживача під час виконання, тригер перезавантаження та точку аудиту, у вас іще немає повної відповіді. У проді те саме речення стає основою для інструкції та контрольного списку рецензента.
Патерни та антипатерни
Розділ «Патерни та антипатерни»| Патерн | Коли використовувати | Чому це працює |
|---|---|---|
| Шифрувати рідні секрети у стані спокою та обмежувати дієслова читання | Будь-який кластер, що зберігає Kubernetes Secret | Це відокремлює авторизацію API від компрометації сховища та розкриття резервних копій. |
| Використовувати ESO для секретів, що належать провайдеру та споживаються звичайними чартами | Команди вже використовують AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, Vault або 1Password | Це зберігає сумісність із рідними секретами, виносячи створення, ротацію та аудит провайдера за межі кластера. |
| Використовувати проєктовані токени ServiceAccount із явними аудиторіями | Pod’и, яким потрібні токени API або ідентичності | Короткотривалі токени, прив’язані до аудиторії, зменшують цінність крадіжки токена порівняно зі застарілими токенами-Secret. |
| Використовувати незмінні версіоновані секрети для навмисних розгортань | Облікові дані, що ротуються контрольованими хвилями розгортання | Нове ім’я Secret робить стан розгортання видимим і уникає випадкової мутації на місці. |
| Антипатерн | Чому команди до цього вдаються | Краща альтернатива |
|---|---|---|
Надання list і watch на секрети групам людей для усунення несправностей | Це здається зручним дозволом лише на читання | Надавайте іменований get лише там, де можливо, і створюйте аварійний доступ (break-glass) з переглядом аудиту. |
| Зберігання base64-маніфестів Secret напряму в Git | Закодоване значення виглядає менш очевидним, ніж відкритий текст | Використовуйте Sealed Secrets, SOPS, ESO або посилання на провайдера замість необроблених значень Secret. |
| Припущення, що ESO усуває потребу в шифруванні etcd | Джерело істини вийшло за межі Kubernetes | ESO усе одно записує об’єкти рідного Secret, якщо ви не оберете дизайн лише з монтуванням. |
| Ротація Secret без перезапуску або перезавантаження застосунку | Оновлення об’єкта вдалося, тож розгортання вважається завершеним | Перевіряйте шлях перезавантаження застосунку та включайте проби успіху, які доводять використання нових облікових даних. |
Каркас прийняття рішень
Розділ «Каркас прийняття рішень»Обирайте рідні Kubernetes Secret із шифруванням у стані спокою, коли кластер є правильною операційною межею, значення прості, а ваша модель ризику стосується переважно ізоляції просторів імен, захисту резервних копій та посилення в стилі іспиту. Обирайте ESO, коли інша система вже є джерелом істини, а робочим навантаженням усе одно потрібні рідні об’єкти Secret. Обирайте Sealed Secrets, коли Git має нести зашифровані декларативні маніфести, а секрети досить статичні, щоб повторне запечатування було прийнятним. Обирайте Vault або доставку під час виконання на основі CSI, коли короткотривалі облікові дані, оренди, відкликання або уникнення синхронізованих рідних секретів варті додаткової складності площини управління та застосунку.
Start | vDo applications already require native Kubernetes Secret objects? |-- yes --> Is the source of truth outside the cluster? | |-- yes --> Use ESO, then protect synced Secrets. | |-- no --> Use native Secrets with encryption and RBAC. | |-- no --> Do you need dynamic leased credentials? |-- yes --> Use Vault Agent, Vault CSI, or provider CSI. |-- no --> Is Git the delivery system? |-- yes --> Use Sealed Secrets or SOPS. |-- no --> Use CSI mount or direct provider SDK.Рішення щодо вартості йде тим самим шляхом. Рідні секрети додають мало прямих хмарних витрат, але підвищують важливість захисту резервних копій etcd та сховища аудиту. Дизайни ESO та CSI додають виклики API провайдера, компоненти контролера чи вузла та логи. Vault додає вартість сховища, розпечатування (unseal), HA, резервного копіювання та операційного персоналу в обмін на динамічні секрети та сильніший централізований контроль. Дизайн, який надто дорогий або крихкий, обходитимуть, тож безпечна відповідь — це та, яку ваша команда може експлуатувати під час ротації, збою та реагування на інциденти.
Чи знали ви?
Розділ «Чи знали ви?»- KMS v2 є GA/стабільним з Kubernetes 1.29 і є рекомендованим провайдером для нових розгортань. KMS v1 є застарілим з 1.28 і вимкненим за замовчуванням з 1.29; його вмикання потребує
--feature-gates=KMSv1=trueлише для застарілих або міграційних контекстів. Дизайн протоколу плагіна v1 на основі gRPC-стрімінгу має відомі проблеми з продуктивністю та відмовами при розділенні (partition), які розв’язує модель “запит/відповідь” v2. - Незмінні секрети стали стабільними в Kubernetes 1.21. Вони не лише захисний бар’єр для безпеки; вони також зменшують навантаження на watch у kubelet для кластерів із багатьма змонтованими об’єктами Secret і ConfigMap.
- Токен ServiceAccount можна запитати для конкретної аудиторії та терміну дії. Це сучасна альтернатива поводженню з довготривалим токеном-Secret як із багаторазовим паролем для кожного внутрішнього сервісу.
- Читання Secret, залоговане на рівні
RequestResponse, може розкрити корисне навантаження в бекенді аудиту. Для більшості завдань моніторингу доступу до Secret рівеньMetadataдає актора, дієслово, ресурс, простір імен та позначку часу без копіювання чутливого значення.
Типові помилки
Розділ «Типові помилки»| Помилка | Чому вона трапляється | Як її виправити |
|---|---|---|
| Сприйняття base64 як шифрування | YAML виглядає перетвореним, тож рецензенти припускають наявність криптографічної межі. | Декодуйте одне значення під час перегляду, поясніть відмінність та увімкніть шифрування у стані спокою для збережених об’єктів. |
Розміщення identity першим у EncryptionConfiguration | Оператори додають його як резерв, але пропускають семантику порядку провайдерів. | Ставте шифрувальний провайдер першим і тримайте identity останнім, лише поки старі дані у відкритому тексті мають бути читабельними. |
Надання list на секрети для усунення несправностей | Ролі лише для читання копіюються з широких шаблонів. | Надавайте іменований get із resourceNames, використовуйте тимчасові прив’язки break-glass та проводьте аудит кожного звернення. |
| Використання змінних середовища для облікових даних, що ротуються | Приклади фреймворків часто показують просту інжекцію через env. | Надавайте перевагу монтуванню файлів для чутливих значень та реалізуйте логіку перезавантаження чи розгортання для ротацій. |
| Припущення, що ESO означає, що Kubernetes не зберігає даних секретів | Зовнішній провайдер видимий на діаграмі архітектури. | Пам’ятайте, що ESO матеріалізує об’єкти рідного Secret, якщо не обрано патерн лише з монтуванням. |
| Залишення токенів ServiceAccount за замовчуванням змонтованими всюди | Значення за замовчуванням зручне, а багато Pod’ів ніколи не викликають API. | Встановлюйте automountServiceAccountToken: false за замовчуванням і додавайте проєктовані токени лише там, де потрібно. |
| Увімкнення логів аудиту на рівні корисного навантаження для читань Secret | Команди хочуть максимальної криміналістичної деталізації під час інциденту. | Логуйте читання Secret на рівні Metadata і захищайте зберігання, доступ та конвеєри експорту логів аудиту. |
Тест
Розділ «Тест»-
Ваш верифікатор виявляє, що Secret з іменем
api-tokenзберігається якYXBpLXRva2Vuу маніфесті, закомічений у Git. Розробник каже, що ніхто не може його прочитати, бо він закодований. Що ви робите першим і яка зміна дизайну запобігає повторенню?Відповідь
Розглядайте це як розкриття облікових даних, бо base64 є зворотним без ключа. Негайна дія — ротувати базові облікові дані, видалити необроблене значення з майбутніх комітів і припустити, що будь-який клон чи форк може зберегти старе значення. Зміна дизайну залежить від робочого процесу: використовуйте Sealed Secrets або SOPS для зашифрованих у Git маніфестів, або використовуйте ESO, щоб Git містив посилання на шлях зовнішнього провайдера, а не корисне навантаження. Це відображає діагностичний результат, бо проблема не в декодуванні Kubernetes; вона в людському доступі та доступі репозиторію до закодованого значення. -
Ви увімкнули провайдер шифрування
aescbcі створили новий Secret. Пряма інспекція etcd показує, що новий Secret зашифровано, але старіший Secret усе ще виглядає читабельним. Чи зламано шифрування?Відповідь
Шифрування, ймовірно, не зламано; увімкнення провайдера шифрування впливає на нові записи й оновлення, а не на історичні записи etcd. Наявні секрети треба перезаписати через API-сервер, зазвичай за допомогою `kubectl get secrets --all-namespaces -o json | kubectl replace -f -`, щоб перший налаштований провайдер міг їх перетворити. Тримайте старіші провайдери або `identity` доступними, доки перезапис і перевірка не завершаться. Це ключовий екзаменаційний патерн, бо налаштування провайдера та міграція даних — це окремі кроки. -
Платформна команда хоче використовувати AWS KMS, Google Cloud KMS або Azure Key Vault як межу кореневого ключа для шифрування Kubernetes Secret. Який шлях провайдера Kubernetes їм слід оцінити та яку операційну залежність він вводить?
Відповідь
Їм слід оцінити провайдер KMS v2, який дозволяє API-серверу викликати локальний плагін, що інтегрується із зовнішнім KMS. Операційна залежність полягає в тому, що читання та записи зашифрованих ресурсів тепер залежать від справного плагіна, дійсної хмарної ідентичності, розумної затримки та доступності зовнішнього KMS. KMS v2 покращує поведінку справності, версій ключів та спостережуваності порівняно зі старішими шляхами провайдерів, але він усе одно належить до моніторингу площини управління та тестів аварійного відновлення. Збій провайдера може стати симптомом API-сервера, а не лише симптомом менеджера секретів. -
Команда застосунку запитує, що краще підходить для її GitOps-репозиторію — ESO чи Sealed Secrets. Їхні облікові дані вже живуть в Azure Key Vault, ротуються щомісяця і мають бути доступними для готового Helm-чарта, який читає рідні Kubernetes Secret. Який варіант ви обираєте та чому?
Відповідь
ESO підходить краще, бо джерело істини вже живе у зовнішньому провайдері, а Helm-чарт очікує рідні об'єкти Kubernetes Secret. ESO може синхронізувати значення провайдера в кластер, зберігаючи інтерфейс чарта, а інтервал оновлення можна узгодити з процесом ротації. Sealed Secrets перемістив би зашифровані статичні корисні навантаження в Git, що дублює джерело істини провайдера та додає роботу з повторного запечатування після кожної ротації. Синхронізований Secret усе одно потребує RBAC і шифрування у стані спокою, бо ESO не усуває рідне зберігання Secret. -
Role простору імен надає CI-ServiceAccount
createна Pod’и, але жодних дієслів для секретів. Рецензент із безпеки все одно каже, що обліковий запис може мати змогу читати секрети застосунку. Що помічає рецензент?Відповідь
Рецензент помічає непрямий доступ до Secret через створення Pod. Якщо ServiceAccount може створити Pod у просторі імен, він може створити контейнер, який монтує або інжектує Secret, а потім прочитати значення зсередини цього контейнера. Видалення `get secrets` недостатньо, коли створення робочих навантажень залишається широким. Виправлення — обмежити створення Pod, використовувати політику допуску, відокремити CI-простори імен від простору імен виконання та переглядати доступ до Secret разом із дозволами на робочі навантаження. -
Ваш бекенд аудиту швидко зростає після того, як ви додали моніторинг Secret, а зразкова подія містить дані відповіді для читання Secret. Що слід змінити в політиці аудиту?
Відповідь
Правила читання Secret зазвичай мають логувати на рівні `Metadata`, а не `RequestResponse`, бо тіло відповіді може містити чутливе корисне навантаження. Metadata все одно фіксує, хто зробив запит, яке дієслово було використано, який простір імен і ресурс було торкнуто та коли це сталося. Вам також слід обмежити правило дієсловами та ресурсами Secret, замість того щоб піднімати рівень аудиту глобально. Це зменшує і ризик витоку, і вартість збору логів, зберігаючи криміналістичний сигнал, потрібний для перегляду доступу. -
Пароль бази даних ротується у Vault, файл, змонтований через CSI, оновлюється в Pod, але застосунок продовжує зазнавати невдачі автентифікації зі старим паролем. Якого рівня, найімовірніше, бракує?
Відповідь
Відсутній рівень — це поведінка перезавантаження застосунку. Оновлення змонтованого файлу не гарантує, що процес перечитає файл або перестворить з'єднання з базою даних. Виправленням може бути спостерігач за файлами, `SIGHUP`, HTTP-кінцева точка перезавантаження, злив пулу з'єднань або контрольоване розгортання, запущене після ротації. Доставка Secret і споживання застосунком — це окремі обов'язки, а повний план ротації охоплює обидва.
Практична вправа
Розділ «Практична вправа»Ця вправа розрахована на одноразовий лабораторний кластер, де ви контролюєте конфігурацію API-сервера. Ви діагностуєте типове розкриття, напишете безпечну EncryptionConfiguration, посилите один шлях читання Secret та обміркуєте ротацію. Якщо ваш лабораторний провайдер не надає доступу до файлової системи площини управління, виконайте правки маніфестів статичного Pod як завдання на перегляд, а не застосовуйте їх до керованої площини управління.
Завдання 1: Доведіть, що base64 — це не шифрування
Розділ «Завдання 1: Доведіть, що base64 — це не шифрування»Створіть простір імен і Secret, потім декодуйте збережене значення з відповіді API. Мета — зробити типову межу видимою, перш ніж додавати інструменти.
kubectl create namespace secrets-labkubectl -n secrets-lab create secret generic app-db \ --from-literal=username=app_user \ --from-literal=password=example-password
kubectl -n secrets-lab get secret app-db -o jsonpath='{.data.password}' | base64 --decodeprintf '\n'Нотатки до розв'язку
Декодований вивід має збігатися з буквальним значенням, яке ви надали. Це доводить, що API-об’єкт лише закодовано для транспортного та сховищного представлення. Якщо ви можете прочитати об’єкт Secret, ви можете декодувати його значення поза Kubernetes без іншої перевірки дозволів. Це причина, чому RBAC, шифрування у стані спокою та вибір доставки під час виконання — усі мають бути переглянуті.
Завдання 2: Складіть конфігурацію провайдера шифрування
Розділ «Завдання 2: Складіть конфігурацію провайдера шифрування»Згенеруйте ключ у лабораторії та створіть конфігураційний файл, який шифрує лише секрети. Тримайте identity останнім, щоб старі записи у відкритому тексті все ще можна було прочитати під час міграції.
Якщо ваша лабораторія надає доступ до файлової системи площини управління, також підключіть правку маніфесту з розділу “Підключення конфігураційного файлу до kube-apiserver” до /etc/kubernetes/manifests/kube-apiserver.yaml, потім зачекайте, доки kubelet перезапустить статичний Pod API-сервера.
mkdir -p /tmp/cks-4-3head -c 32 /dev/urandom | base64 > /tmp/cks-4-3/aescbc.keyENC_KEY="$(cat /tmp/cks-4-3/aescbc.key)"
cat > /tmp/cks-4-3/encryption-config.yaml <<EOFapiVersion: apiserver.config.k8s.io/v1kind: EncryptionConfigurationresources: - resources: - secrets providers: - aescbc: keys: - name: key-2026-05 secret: ${ENC_KEY} - identity: {}EOF
sudo mkdir -p /etc/kubernetes/encsudo cp /tmp/cks-4-3/encryption-config.yaml /etc/kubernetes/enc/encryption-config.yaml
# Edit /etc/kubernetes/manifests/kube-apiserver.yaml to mount and pass the new config:# --encryption-provider-config, matching volumeMount, and volume entries.# Then verify the static Pod has restarted and is healthy before rewriting Secrets.
# Only run after the apiserver flag, mount, and volume are verified healthy:kubectl get secrets --all-namespaces -o json | kubectl replace -f -
ETCDCTL_API=3 etcdctl \ --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/server.crt \ --key=/etc/kubernetes/pki/etcd/server.key \ get /registry/secrets/secrets-lab/app-db | strings | head -5Нотатки до розв'язку
Важливі перевірки — це версія API, ім’я ресурсу, порядок провайдерів та довжина ключа. Нові записи використовують перший провайдер, тож aescbc має йти перед identity. Резерв identity прийнятний під час міграції, бо він дозволяє API-серверу читати старіші незашифровані записи, але він не повинен бути першим провайдером. У реальній площині управління зі статичними Pod’ами цей файл має бути змонтований у контейнер API-сервера та зазначений за допомогою --encryption-provider-config.
Завдання 3: Напишіть вузьку Role для читання Secret
Розділ «Завдання 3: Напишіть вузьку Role для читання Secret»Створіть ServiceAccount, який може читати лише Secret app-db за іменем. Не надавайте list чи watch.
kubectl -n secrets-lab create serviceaccount app-reader
cat <<'EOF' | kubectl apply -f -apiVersion: rbac.authorization.k8s.io/v1kind: Rolemetadata: name: app-db-reader namespace: secrets-labrules: - apiGroups: [""] resources: ["secrets"] resourceNames: ["app-db"] verbs: ["get"]---apiVersion: rbac.authorization.k8s.io/v1kind: RoleBindingmetadata: name: app-db-reader namespace: secrets-labsubjects: - kind: ServiceAccount name: app-reader namespace: secrets-labroleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: app-db-readerEOF
kubectl auth can-i get secret/app-db \ --as=system:serviceaccount:secrets-lab:app-reader \ -n secrets-lab
kubectl auth can-i list secrets \ --as=system:serviceaccount:secrets-lab:app-reader \ -n secrets-labНотатки до розв'язку
Перша перевірка авторизації має повернути yes, а друга — no. Це різниця між іменованим читанням та інвентаризацією в межах усього простору імен. У реальному перегляді ви також перевірили б, чи може той самий ServiceAccount створювати Pod’и, виконувати exec у Pod’и чи отримувати доступ до Secret через іншу RoleBinding. RBAC є кумулятивним, тож одна вузька Role не скасовує ширшу.
Завдання 4: Порівняйте споживання через env та том
Розділ «Завдання 4: Порівняйте споживання через env та том»Запустіть Pod, який монтує Secret як файл, потім прочитайте змонтоване значення. Це завдання використовує busybox, який містить sh і cat, тож команда exec відповідає набору бінарників образу.
cat <<'EOF' | kubectl apply -f -apiVersion: v1kind: Podmetadata: name: secret-reader namespace: secrets-labspec: containers: - name: app image: busybox command: ["sh", "-c", "sleep 3600"] volumeMounts: - name: db mountPath: /etc/db readOnly: true volumes: - name: db secret: secretName: app-db defaultMode: 0400EOF
kubectl -n secrets-lab wait --for=condition=Ready pod/secret-reader --timeout=60skubectl -n secrets-lab exec secret-reader -- cat /etc/db/passwordНотатки до розв'язку
Змонтований файл має містити декодоване значення пароля. Монтування файлу дає вам дозволи файлів і кращий шлях ротації, ніж змінна середовища, але запущений процес усе одно може прочитати значення. Якщо ви виправите Secret, проєктований том може оновитися, але застосунок має перевідкрити файл або перезавантажити свою конфігурацію. Процес, який закешував значення під час запуску, може продовжувати використовувати старі облікові дані.
Завдання 5: Перегляньте політику аудиту для читань Secret
Розділ «Завдання 5: Перегляньте політику аудиту для читань Secret»Створіть фрагмент політики, який записує читання Secret на рівні метаданих. Вам не потрібно його застосовувати, якщо ваша лабораторія не надає доступу до маніфесту статичного Pod площини управління.
cat > /tmp/cks-4-3/audit-policy.yaml <<'EOF'apiVersion: audit.k8s.io/v1kind: PolicyomitStages: - RequestReceivedrules: - level: Metadata verbs: ["get", "list", "watch"] resources: - group: "" resources: ["secrets"]EOFНотатки до розв'язку
Ця політика фіксує, хто читав або стежив за ресурсами Secret, не логуючи тіла відповідей. Якщо ви використали RequestResponse для get secrets, бекенд аудиту міг би отримати корисне навантаження Secret і стати ще одним сховищем чутливих даних. У площині управління зі статичними Pod’ами в стилі kubeadm пам’ятайте, що і шлях до політики аудиту, і шлях до логу аудиту мають бути змонтовані в контейнер API-сервера. Також плануйте зберігання логів, бо контролери з великою кількістю секретів можуть генерувати багато подій аудиту.
Критерії успіху
Розділ «Критерії успіху»- Ви декодували значення Secret із відповіді API і можете пояснити, чому це не шифрування.
- Ваша
EncryptionConfigurationрозміщує шифрувальний провайдер передidentity. - Ваша перевірка RBAC дозволяє іменований
getна один Secret і забороняєlistу межах усього простору імен. - Ваш приклад під час виконання монтує Secret як файл і використовує образ, що містить виконуваний бінарник.
- Ваша політика аудиту логує доступ до Secret на рівні
Metadata, а не копіює корисні навантаження в логи.
Перевірка засвоєного
Розділ «Перевірка засвоєного»RBAC захищає читання через API; шифрування у стані спокою захищає рівень сховища; одне не замінює інше.
Перш ніж рухатися далі, поясніть, що відбувається, коли --encryption-provider-config відсутній, на противагу тому, коли прапорець встановлено, але шлях монтування неправильний. Ґрунтовна відповідь називає нові записи у відкритому тексті, цикли збоїв API-сервера та випадок, коли identity зазначено першим у списку провайдерів.
Джерела
Розділ «Джерела»- Kubernetes Secrets
- Good practices for Kubernetes Secrets
- Encrypting Confidential Data at Rest
- Using a KMS provider for data encryption
- Kubernetes v1.29 release notes: KMS v2 stable
- Configure Service Accounts for Pods
- Kubernetes Service Accounts
- Kubelet TLS bootstrapping
- Kubernetes RBAC reference
- Kubernetes Auditing
- External Secrets Operator documentation
- External Secrets Operator SecretStore API
- External Secrets Operator ClusterSecretStore API
- Bitnami Sealed Secrets
- HashiCorp Vault on Kubernetes
- Vault Agent Injector
- Secrets Store CSI Driver
- Secrets Store CSI Driver documentation
Наступний модуль
Розділ «Наступний модуль»Модуль 4.4: Пісочниця середовища виконання — захистіть свої Pod’и за межами RBAC, дізнавшись, як gVisor та Kata Containers додають сильніші межі ізоляції робочих навантажень.