Перейти до вмісту

Модуль 5.1: Безпека образів контейнерів

Hands-On Lab Available
K8s Cluster intermediate 40-45 min
Launch Lab ↗

Opens in Killercoda in a new tab

Складність: [MEDIUM] — основна навичка CKS

Час на проходження: 40-45 хвилин

Передумови: основи Docker/контейнерів, Модуль 0.3 (Інструменти безпеки)


Що ви зможете зробити

Розділ «Що ви зможете зробити»

Після завершення цього модуля ви зможете:

  1. Створювати посилені Dockerfile із використанням багатоетапних збірок, мінімальних базових образів та користувачів без прав root.
  2. Налаштовувати політики завантаження образів та автентифікацію приватних реєстрів для кластерів.
  3. Впроваджувати прив’язку образів до дайджесту, щоб запобігти атакам на ланцюг постачання через зміну тегів.
  4. Проводити аудит образів контейнерів на наявність зайвих пакетів, бінарних файлів із setuid та вбудованих секретів.

Чому цей модуль важливий

Розділ «Чому цей модуль важливий»

Безпека образів контейнерів — це місце, де починається посилення робочих навантажень Kubernetes, адже кластер планує до запуску лише те, що вже міститься в образі. Под може мати обмежені можливості (capabilities), кореневу файлову систему лише для читання та сувору мережеву політику NetworkPolicy і водночас стартувати з образу, який містить вразливе середовище виконання, скомпрометовану залежність, витеклий токен або набір shell-інструментів, які зловмисник зможе використати після першого закріплення. CKS розглядає це як практичну навичку: вам потрібно вміти аналізувати посилання на образи, посилювати Dockerfile, налаштовувати облікові дані реєстру та пояснювати, чому тег, який виглядав безпечним під час рев’ю, може стати іншим артефактом під час виконання.

Розгляньмо компрометацію пакета ua-parser-js у жовтні 2021 року. Супровідник npm-пакета повідомив, що зловмисник захопив обліковий запис npm і опублікував три шкідливі версії: 0.7.29, 0.8.0 та 1.0.0. У бюлетені безпеки GitHub та в каталозі компрометацій CNCF TAG Security задокументовано, що уражені версії несли вбудоване шкідливе програмне забезпечення, а запис CNCF зазначає, що на той момент пакет мав понад сім мільйонів завантажень щотижня. Конвеєр збірки, який під час docker build виконував npm install, міг таким чином створити образ застосунку, що виглядав легітимним, але містив код зловмисника, навіть попри те, що YAML Kubernetes, власник Deployment та ім’я хосту реєстру виглядали звичайними. Кластер бачить одне непрозоре посилання на образ; зловмисник використав граф залежностей збірки, щоб вирішити, які саме байти потраплять до цього образу.

Інцидент із завантажувачем Bash від Codecov ілюструє CI-варіант того самого уроку — облікові дані, ключі підпису та секрети розгортання можуть бути розкриті скомпрометованим інструментом збірки ще до того, як спрацює будь-яка політика допуску Kubernetes. Канонічний розбір дивіться у DevSecOps.

Ці інциденти безпосередньо пов’язані з повсякденними операціями Kubernetes. Якщо ви розгортаєте myregistry/app:prod, kubelet резолвить тег під час старту контейнера відповідно до політики завантаження та кешу ноди. Якщо тег у реєстрі було перезаписано після рев’ю, перезапущений Под може виконати байти, які ніколи не проходили початкове сканування. Якщо Dockerfile використовує повний дистрибутивний образ, зловмисник, який скомпрометує застосунок, може виявити вже встановлені менеджери пакетів, оболонки, бінарні файли з setuid та мережеві інструменти. Якщо образ було зібрано з широким .dockerignore, файл .env або приватний ключ може все ще лежати в одному з ранніх шарів навіть після пізнішої команди RUN rm. Безпечна стратегія образів закриває ці прогалини за допомогою мінімальних базових образів, відтворюваних вхідних даних збірки, прив’язки до дайджесту, підписаних артефактів, контролю приватних реєстрів та налаштувань середовища виконання, що відповідають припущенням образу.

Для іспиту цей модуль є також вправою на швидкість. Малоймовірно, що під час CKS вам доведеться з нуля проєктувати корпоративну платформу ланцюга постачання, але вам може знадобитися діагностувати, чому Под перебуває у стані ImagePullBackOff, замінити змінний тег на дайджест, створити imagePullSecret, виявити небезпечну інструкцію Dockerfile або пояснити, чому результат роботи сканера має запускати перезбірку базового образу, а не зміну коду застосунку. Найшвидші відповіді походять із розуміння «чому»: кожне посилання на образ є рішенням про довіру, кожен рядок Dockerfile додає або прибирає поверхню атаки під час виконання, а кожен обліковий запис реєстру визначає, кому дозволено переміщувати програмне забезпечення до кластера.

Ризики безпеки образів

Розділ «Ризики безпеки образів»

Образ є водночас файловою системою і записом про ланцюг постачання. Файлова система містить ваш застосунок, середовище виконання мови, пакети операційної системи, метадані, типового користувача, відкриті порти, точку входу, мітки та іноді випадкові секрети. Запис про ланцюг постачання охоплює базовий образ, репозиторії пакетів, файли блокування залежностей, аргументи збірки, ідентичність CI, дозволи реєстру, підписи, звіти про вразливості та шлях просування. Слабке рев’ю, яке перевіряє лише YAML Deployment, пропускає більшу частину цього запису, тоді як слабкий процес, що покладається лише на сканер, пропускає небезпечні налаштування за замовчуванням, які не мають номерів CVE.

Перший ризик — це вразливе успадковане програмне забезпечення. Базовий образ на кшталт ubuntu, debian, python, node чи openjdk приносить пакети операційної системи та інструментарій мови, які можуть бути не пов’язані з вашим застосунком під час виконання. Вразливість CVE у curl може бути недосяжною через бізнес-логіку, але якщо скомпрометований процес зможе запустити curl, той стає корисним для викрадення даних та горизонтального переміщення. Вразливість CVE у спільній бібліотеці може мати значення лише тоді, коли ваш бінарний файл її дійсно завантажує, проте сканери часто повідомляють про неї, бо пакет присутній. Ваше завдання — скоротити набір пакетів настільки, щоб результат роботи сканера ставав придатним для дій, а не перетворювався на постійний беклог.

Другий ризик — це шкідливі або неочікувані вхідні дані збірки. Реєстри залежностей, дзеркала пакетів, базові образи, CI-помічники та згенеровані артефакти — усе це вхідні дані для фінального образу. Інцидент ua-parser-js має значення, бо він перетворив звичайну інсталяцію пакета на доставку шкідливого ПЗ. Інцидент Codecov є ще одним прикладом того, як довірений інструментарій CI стає вектором витоку.

Третій ризик — це привілеї, вбудовані в образ. Багато образів запускаються від імені root за замовчуванням, бо відсутня інструкція USER. Деякі містять бінарні файли з setuid або setgid, успадковані з дистрибутива. Деякі використовують точки входу у формі оболонки (shell-form), які проганяють команди через /bin/sh -c і створюють проблеми з екрануванням та обробкою сигналів. Kubernetes може перевизначити частину цього через securityContext, але покладатися лише на специфікацію Пода ненадійно. Образ має бути безпечним за замовчуванням, а специфікація Пода має підсилювати цей стандарт за допомогою runAsNonRoot, allowPrivilegeEscalation: false, відкинутих можливостей та кореневої файлової системи лише для читання там, де це можливо.

Четвертий ризик — це змінна ідентичність. Тег — це зручний для людини покажчик, а не гарантія вмісту. Власники реєстру можуть повторно опублікувати тег, автоматизовані перезбірки можуть його перемістити, а зловмисник із правами на запис може його замінити. Дайджест відрізняється тим, що іменує вміст маніфесту за хешем. Теги корисні для людей та автоматизації, але виробниче просування має фіксувати та розгортати саме той дайджест, який пройшов сканування та рев’ю. Це не усуває потреби в підписуванні, адже дайджест доводить лише стабільність вмісту, а не ідентичність автора, але він запобігає великому класу збоїв, пов’язаних із підміною тегів та плутаниною кешу.

П’ятий ризик — це сліпота сканера, спричинена контекстом, метаданими або часом. Сканер може повідомити про відомі CVE для пакетів, які він здатний ідентифікувати, але він не знає, чи був образ зібраний затвердженим конвеєром, чи був тег перезаписаний після сканування, чи витік секрет збірки до логів, чи було втрачено налаштування запуску без прав root у шаблоні Пода. Саме тому безпеку образів слід розглядати як ланцюг доказів. Образ повинен мати відомий базовий образ, вузький набір заблокованих залежностей, відтворювану команду збірки, звіт про сканування, прив’язаний до дайджесту, підпис або атестацію від ідентичності збірки та запис про розгортання, що посилається на той самий дайджест. Якщо одна з цих ланок відсутня, кластер усе одно може запустити робоче навантаження, але оператор втратив докази, потрібні, щоб йому довіряти.

Зловмисники також виграють від операційних скорочень, які виглядають безпечними під час тиску доставки. Образ гарячого виправлення (hotfix), вручну відправлений із ноутбука, може обійти підписування в CI. Тимчасовий токен реєстру, розміщений у просторі імен, може ніколи не пройти ротацію. Тег для зневадження з оболонкою може бути просунутий до виробництва, бо він усунув термінову аварію. Виняток для базового образу може бути скопійований у наступний сервіс, бо він «уже працював». Це не екзотичні збої; це нормальний дрейф робочого процесу. Захист полягає в тому, щоб зробити безпечний шлях найлегшим: золоті шаблони Dockerfile, незмінність реєстру, автоматизоване просування дайджестів, перевірки допуску та короткі ранбуки, які пояснюють, як саме відправити екстрений образ, не втрачаючи походження.

Image risk review
|
+-- Filesystem contents: packages, shells, tools, setuid files, secrets
+-- Build inputs: base image, dependencies, package mirrors, CI helpers
+-- Runtime defaults: user, entrypoint, writable paths, exposed services
+-- Registry identity: tag mutability, digest, signatures, credentials

Вибір базового образу

Розділ «Вибір базового образу»

Базовий образ — це найбільше рішення з безпеки в Dockerfile, адже кожен наступний шар його успадковує. Повний дистрибутивний образ дає звичні інструменти та легке зневадження, але він також несе оболонки, менеджери пакетів, бібліотеки, користувачів, файли конфігурації та утиліти, які вашому застосунку можуть ніколи й не знадобитися. Мінімальний базовий образ прибирає більшу частину цієї поверхні, але він може ускладнити діагностику та сумісність. Правильна відповідь — не завжди найменший можливий образ; це найменший образ, який усе ще дозволяє застосунку працювати передбачувано, отримувати оновлення, надавати достатньо метаданих для сканерів і обслуговуватися вашою командою.

Образи distroless є надійним виробничим стандартом для багатьох компільованих або специфічних для середовища виконання навантажень. Проєкт distroless від Google описує ці образи як такі, що містять застосунок та залежності середовища виконання без userland дистрибутива загального призначення. Документація Kubernetes про ефемерні контейнери наголошує на операційному наслідку: образи distroless зменшують поверхню атаки та вразливість до загроз, але їм бракує оболонки та утиліт зневадження, тому самого kubectl exec може бути недостатньо під час усунення несправностей. Це перевага, коли зловмисник опиняється всередині контейнера, і витрата, коли оператору потрібно його дослідити. Плануйте цю витрату за допомогою образів для зневадження, ефемерних контейнерів, надійних логів та проб готовності, замість того щоб постачати Bash у виробництво.

Alpine — це ще один поширений вибір, бо він малий і все ще поводиться як дистрибутив Linux із менеджером пакетів. Його реалізація musl libc може бути чудовою для простих сервісів, але може здивувати команди, які очікують поведінки glibc, особливо щодо DNS, нативних розширень та деяких середовищ виконання мов. Варіанти slim, такі як python:3.12-slim, node:22-slim чи slim-образи Debian, часто є прагматичним золотим серединним рішенням. Вони зберігають достатньо структури дистрибутива для сумісності, прибираючи документацію, інструменти збірки та широкі набори пакетів. Повні образи прийнятні для етапів-збиральників, локальної розробки чи особливих випадків, але у виробництві вони мають вимагати чіткого обґрунтування для середовища виконання.

Довіра до базового образу стосується супроводу та походження так само, як і розміру. Надавайте перевагу офіційним образам, перевіреним видавцям, внутрішнім золотим образам або образам, зібраним платформенною командою з задокументованою періодичністю перезбірки. Образ, який крихітний, але занедбаний, гірший за дещо більший образ, який отримує оновлення, публікує SBOM та підписаний. Документація з найкращих практик Docker наголошує на довірених джерелах, малих образах, багатоетапних збірках та частій перезбірці, бо образи є незмінними знімками. Ця незмінність є пасткою, якщо ви ніколи не перезбираєте: шестимісячний образ може все ще ідеально стартувати, тоді як його звіт про вразливості змінюється під ним у міру публікації нових бюлетенів.

Рішення про базовий образ також має враховувати, як ваша організація розподіляє відповідальність. Якщо кожна команда застосунків обирає власну базу, команда безпеки має розбиратися в багатьох менеджерах пакетів, багатьох розкладах перезбірки, багатьох ідентичностях підпису та багатьох процесах винятків. Програма курованих базових образів звужує цю поверхню рев’ю. Платформенна команда може опублікувати невеликий набір благословенних середовищ виконання, перезбирати їх, коли змінюються upstream-пакети, підписувати їх, документувати типового користувача та надавати компаньйони для зневадження для виробничих інцидентів. Команди застосунків усе ще володіють своїм кодом, але їм більше не потрібно самотужки вирішувати посилення дистрибутива. Це особливо корисно в Kubernetes, бо багато навантажень спільно використовують ноди; слабкий образ в одному просторі імен може перерости в інцидент рівня ноди, якщо поєднається з вадою середовища виконання чи ядра.

Є одне важливе застереження під час переходу до scratch чи дуже малих образів distroless: залежності середовища виконання стають вашою відповідальністю. Статично злінкований бінарний файл Go може щасливо працювати в scratch, але клієнту TLS усе одно потрібні кореневі сертифікати, пошук DNS усе ще залежить від поведінки резолвера, а застосунку, який зіставляє імена користувачів, може знадобитися /etc/passwd або числовий UID, який Kubernetes здатний перевірити. Образи distroless часто містять корисні частини середовища виконання, такі як CA-сертифікати та користувач без прав root, що робить їх простішими за чистий scratch для багатьох команд. Тестуйте образ під фінальним контекстом безпеки Kubernetes, а не лише за допомогою docker run, бо Под може застосовувати кореневі файлові системи лише для читання, відкинуті можливості чи обмеження на числового користувача, які виявляють відсутні припущення середовища виконання.

У завданнях CKS перекладайте вибір базового образу в спостережувані перевірки. Спочатку дивіться на рядки FROM, потім запитуйте, чи містить фінальний етап компілятори, менеджери пакетів, оболонки чи інструменти зневадження, які були потрібні лише під час збірки. Перевірте, чи має образ названого користувача без прав root або числовий UID. Перевірте, чи потребує застосунок CA-сертифікатів, даних про часові пояси, файлів DNS чи спільних бібліотек, які образ scratch пропустив би. Ретельна міграція на мінімальний образ зазвичай починається з багатоетапної збірки, копіює лише артефакт середовища виконання та потрібну конфігурацію, а потім валідує Под під тим самим контекстом безпеки, який використовуватиме виробництво.

Найкращі практики безпеки Dockerfile

Розділ «Найкращі практики безпеки Dockerfile»

Безпечний Dockerfile — це контракт збірки. Він має чітко вказувати, який вихідний образ є довіреним, які файли потрапляють до образу, які інструменти присутні лише під час збірки, який користувач запускає процес та яка команда стартує застосунок. Найпоширеніша помилка — ставитися до Dockerfile як до shell-скрипта, який випадково створює образ. Збірки Docker зберігають шари, кешують проміжні результати та записують метадані. Секрет, скопійований в один шар і прибраний у пізнішому шарі, усе одно може бути відновлений з історії образу чи кешу збірки. Пакет, встановлений для компіляції, може залишитися доступним зловмиснику, якщо фінальний етап його не виключає.

Використовуйте конкретні посилання на базу та перезбирайте обдумано. Плаваючий тег на кшталт ubuntu:latest чи node:latest ховає рухому залежність за стабільним рядком. Конкретний тег на кшталт debian:12.8-slim краще для рев’ю, тоді як дайджест краще для точної відтворюваності. Компроміс — це потік оновлень: прив’язка до дайджесту запобігає мовчазним змінам, тому ваш процес має оновлювати дайджести, коли базові образи отримують перезбірки безпеки. Сприймайте це як перевагу. Оновлення стає подією, придатною для рев’ю, із записом про сканування, збірку та розгортання, замість мовчазної зміни на боці реєстру, виявленої під час аварії.

Контролюйте контекст збірки. .dockerignore — це функція безпеки, бо Docker надсилає контекст збірки збиральнику до того, як виконаються інструкції. Якщо контекст містить .env, SSH-ключі, kubeconfig, тестові фікстури з токенами чи локальні каталоги кешу, пізніша COPY . . може помістити їх у образ або витекти до віддаленого збиральника. Надавайте перевагу копіюванню маніфестів залежностей спочатку, інсталяції залежностей з файлів блокування, а потім копіюванню лише тих файлів застосунку, які потрібні для збірки. Уникайте широкого ADD, якщо вам не потрібна його поведінка з архівами чи віддаленими URL; COPY легше осмислити, і він робить менше неявної роботи.

Встановлюйте користувача без прав root на фінальному етапі та робіть так, щоб володіння файлами відповідало цьому користувачу. Поширений збій — додати USER 65532 після копіювання файлів, що належать root, а потім виявити, що застосунку потрібно писати логи, файли кешу чи тимчасові дані. Виправлення — не повертатися до root; це створити або використати відомий UID, копіювати файли з правильним володінням там, де це підтримується, та писати лише до призначених для запису шляхів, таких як /tmp чи змонтований том. У Kubernetes підсильте образ за допомогою runAsNonRoot: true, readOnlyRootFilesystem: true та явних монтувань для запису для даних, які справді змінюються.

Використовуйте ENTRYPOINT та CMD у формі exec, щоб застосунок отримував сигнали безпосередньо й не покладався на парсинг оболонки. Команди у формі оболонки спокусливі для швидкого розкриття змінних, але вони вимагають оболонки в образі й можуть створювати заплутану поведінку завершення під час послідовних оновлень (rolling updates). Тримайте інсталяцію пакетів вузькою за допомогою --no-install-recommends на образах на базі Debian, очищайте індекси пакетів у тому самому шарі RUN та уникайте інсталяції інтерактивних інструментів, таких як редактори, SSH-клієнти та мережеві сканери, у фінальний образ. Якщо операторам потрібна діагностика, зберіть окремий образ для зневадження або використовуйте ефемерні контейнери.

Секрети під час збірки потребують особливого поводження, бо вони часто невидимі під час рев’ю фінальної файлової системи. Сучасний Docker BuildKit підтримує монтування секретів, які роблять файл доступним для однієї інструкції RUN, не запікаючи його в шар образу. Це безпечніше за ARG TOKEN=..., ENV TOKEN=... чи COPY token.txt, усі з яких можуть витекти через історію, метадані, логи чи кеш. Той самий принцип застосовується до облікових даних менеджера пакетів та доступу до приватних модулів. Дайте збиральнику вузький обліковий запис, потрібний для завантаження, зробіть інструкцію детермінованою та переконайтеся, що фінальний етап отримує лише зібрані артефакти. Рев’юер має бути здатним дослідити docker history, Dockerfile та логи збірки, не знайшовши облікових даних.

Дозволи на файли — це ще одне місце, де перетинаються контролі Dockerfile та Kubernetes. Якщо образ копіює файли застосунку як root, а потім перемикається на користувача без прав root, процес може стартувати, але пізніше зазнати збою, коли писатиме кеш, завантажуватиме плагін чи ротуватиме локальний файл. Якщо Dockerfile створює призначені для запису каталоги з широкими дозволами, застосунок може працювати, але зловмисник зможе втручатися в більшу частину файлової системи, ніж потрібно. Надавайте перевагу явному володінню та вузьким шляхам для запису. У Kubernetes монтуйте призначений для запису стан через томи й тримайте кореневу файлову систему лише для читання. Це робить спроби запису видимими як помилки під час тестування й допомагає відокремити дані застосунку від незмінних програмних файлів.

# syntax=docker/dockerfile:1
FROM golang:1.22-bookworm AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -o /out/server ./cmd/server
FROM gcr.io/distroless/static-debian12:nonroot
WORKDIR /
COPY --from=build /out/server /server
USER nonroot:nonroot
EXPOSE 8080
ENTRYPOINT ["/server"]

Зупиніться та спрогнозуйте: у завданні CKS з Dockerfile який рядок FROM ви змінили б першим і чим ви його замінили б? Запишіть базовий образ та середовище виконання фінального етапу, яких ви очікуєте, перш ніж читати розділ про багатоетапні збірки, бо екзаменаційні виправлення зазвичай починаються з успадкування, а не з коду застосунку.

Багатоетапні збірки

Розділ «Багатоетапні збірки»

Багатоетапні збірки відокремлюють «що потрібно, щоб створити програму» від «що потрібно, щоб запустити програму». Це розрізнення є суттєвим для безпеки, бо етапи збірки часто потребують компіляторів, менеджерів пакетів, тестових інструментів, помічників для облікових даних та дерев вихідного коду. Етапи середовища виконання зазвичай потребують значно меншого: бінарного файлу, інтерпретатора середовища виконання, CA-сертифікатів, активів застосунку та типових налаштувань конфігурації. Коли фінальний етап копіює лише конкретні артефакти зі збиральника, компілятор та кеш пакетів не стають частиною виробничої файлової системи.

Цінність для безпеки полягає не лише в розмірі образу. Менший фінальний образ зменшує кількість пакетів, які можуть мати CVE, але він також прибирає інструменти, які зловмисник інакше використав би після компрометації. Відсутність оболонки означає відсутність простого sh -i; відсутність менеджера пакетів означає відсутність apt install nmap; відсутність компілятора означає відсутність швидкої локальної збірки експлойт-коду. Ці видалення не є пісочницею, і скомпрометований процес усе ще може використовувати власні мережеві дозволи та змонтовані облікові дані, але шлях дій зловмисника після компрометації стає вужчим і помітнішим. Саме тому багатоетапні збірки природно поєднуються з користувачами без прав root, файловими системами лише для читання, відкинутими можливостями та обмеженим вихідним трафіком.

Багатоетапні збірки також покращують якість рев’ю. Рев’юер може дослідити фінальний етап FROM та запитати, які саме файли перетинають межу етапу. COPY --from=build /out/server /server зрозуміліше за довгий ланцюг інсталяції пакетів, компіляції, очищення та видалення в одному образі. Для середовищ виконання мов патерн схожий: інсталюйте залежності у збиральнику чи віртуальному середовищі, а потім скопіюйте виробничий набір залежностей у slim- чи distroless-образ середовища виконання. Уникайте копіювання цілих каталогів вихідного коду зі збиральника до фінального етапу, якщо середовищу виконання вони насправді не потрібні.

Головна пастка — припускати, що багатоетапна збірка автоматично робить образ безпечним. Якщо фінальний етап усе ще стартує з повного образу, запускається від root, копіює /root/.cache чи містить секрети в середовищі, поділ на етапи не вирішив проблему. Інша пастка — втратити видимість для сканера. Деякі мінімальні образи пропускають метадані бази даних пакетів, що може ускладнити зіставлення вразливостей OS-пакетів для певних інструментів. Це саме по собі не робить образ безпечнішим; це означає, що вам потрібна генерація SBOM під час збірки, підтримка сканером обраної бази та чіткий шлях оновлення для залежностей середовища виконання, які ви копіюєте.

Межі шарів також впливають на оновлення. Припустімо, збиральник завантажує модулі, компілює бінарний файл і копіює результат у фінальний образ. Якщо вразливість з’являється в образі збиральника, але жоден з його пакетів не потрапляє у виробництво, виробничий ризик відрізняється від вразливості у фінальному образі середовища виконання. Збиральник усе одно важливий, бо скомпрометоване середовище збірки може змінити артефакт, але реакцією може бути перезбірка в чистому збиральнику, а не екстрене перерозгортання кожного Пода середовища виконання. І навпаки, якщо фінальний етап використовує вразливий пакет OpenSSL для вихідного TLS, застосунку може знадобитися свіжа база середовища виконання навіть тоді, коли вихідний код не змінювався. Багатоетапні збірки роблять такі рішення точнішими, бо вони визначають, який етап володіє яким ризиком.

Для інтерпретованих мов та сама дисципліна застосовується навіть тоді, коли немає єдиного компільованого бінарного файлу. Сервіс на Python може збирати wheel-пакети на одному етапі й копіювати віртуальне середовище в distroless-Python чи slim-етап середовища виконання. Сервіс на Node може інсталювати залежності з файлом блокування, видалити пакети для розробки, зібрати статичні активи й скопіювати лише node_modules, скомпільований результат та метадані пакетів, потрібні під час виконання. Сервіс на Java може збиратися за допомогою Maven чи Gradle в образі-збиральнику й запускати JAR на меншій базі JRE. Мета — не примусити кожну мову до однакового патерну; вона в тому, щоб зробити залежності для розробки нездатними випадково стати виробничими інструментами.

В екзаменаційному сценарії шукайте інструменти збірки у фінальному образі та конвертуйте Dockerfile найменш руйнівним редагуванням. Назвіть перший етап builder, компілюйте чи інсталюйте там та зробіть другий етап мінімальним. Тримайте команди такими, щоб їх можна було запустити в тестовому середовищі. Для бінарного файлу Go фінальний етап distroless static чи scratch може бути розумним. Для Python, Java чи Node використовуйте відповідне середовище виконання distroless чи slim і копіюйте лише каталоги залежностей плюс код застосунку. Потім додайте USER та точку входу у формі exec на фінальному етапі, а не лише у збиральнику.

Теги та дайджести образів

Розділ «Теги та дайджести образів»

Теги — це зручні імена, але вони є змінними покажчиками. Тег реєстру на кшталт v1.4.2, stable чи prod може бути переміщений на інший маніфест, якщо реєстр це дозволяє й хтось має дозвіл на запис. Це може бути легітимним, коли супровідники перезбирають образ із виправленими базовими шарами, або шкідливим, коли зловмисник отримує облікові дані реєстру. Kubernetes не зберігає незмінну копію історичного значення тегу. Коли Под стартує, kubelet дотримується налаштованої політики завантаження, резолвить посилання на образ і запускає вміст, доступний на той момент або з кешу ноди.

Посилання на дайджест прив’язує вміст маніфесту за криптографічним хешем, наприклад registry.example.com/payments/api@sha256:.... Якщо тег реєстру пізніше переміститься, дайджест усе ще ідентифікує початковий вміст. Це найсильніший простий контроль проти підміни тегів, бо він перетворює просування під час розгортання на рішення, адресоване за вмістом. Звичайний робочий процес — зібрати й відправити образ із зрозумілим для людини тегом, просканувати та підписати його, зафіксувати отриманий дайджест і просунути цей дайджест у маніфести Kubernetes. Люди все ще можуть тримати теги для виявлення, але виробництво запускає дайджест, який пройшов перевірки.

Прив’язка до дайджесту має операційні компроміси. Якщо ваш Deployment посилається лише на дайджест, менш очевидно, на яке ім’я релізу дивиться людина, якщо мітки, анотації, метадані Git чи коментарі з тегом образу не зберігають цей контекст. Якщо базовий образ отримує виправлення безпеки, ваш прив’язаний образ застосунку не змінюється, доки ви не перезберете й не просунете новий дайджест. Це саме те, чого ви хочете для відтворюваності, але це означає, що автоматизація має виявляти застарілі базові дайджести й створювати pull request’и. Незмінне розгортання — це не те саме, що заморожений супровід.

Багатоархітектурні образи додають ще один тонкий момент. Тег може резолвитися в індекс OCI, який містить різні специфічні для платформи маніфести для linux/amd64, linux/arm64 та інших платформ. Дайджест індексу та дайджест маніфесту платформи пов’язані, але не ідентичні. Ноди Kubernetes завантажують потрібний їм специфічний для платформи вміст, а сканери чи інструменти підпису можуть повідомляти або дайджест індексу, або дайджест маніфесту залежно від команди та поведінки реєстру. У змішано-архітектурних кластерах переконайтеся, що дайджест, який ви просуваєте й перевіряєте, відповідає артефакту, на який очікує ваша політика. Процес релізу, який сканує лише одну архітектуру, розгортаючи іншу, залишає прогалину, навіть якщо рядок тегу однаковий.

Підписи та дайджести вирішують різні проблеми. Дайджест доводить, що посилання резолвиться в конкретний вміст; він не доводить, хто створив цей вміст. Підпис від Sigstore cosign, Notary Project notation чи іншої довіреної системи підпису прив’язує ідентичність або ключ до дескриптора артефакту. У виробництві використовуйте обидва: розгортайте за дайджестом, щоб запобігти мовчазному дрейфу вмісту, та перевіряйте підписи чи атестації, щоб забезпечити, що дайджест походить із затвердженого конвеєра збірки. Контролери допуску, такі як Kyverno, policy-controller, Ratify чи власні вебхуки, можуть забезпечувати ці правила, тоді як CKS очікує, що ви розумієте базову семантику артефактів.

Terminal window
# Capture the digest that a tag currently resolves to.
docker build -t registry.local:5000/payments-api:v1 .
docker push registry.local:5000/payments-api:v1
docker buildx imagetools inspect registry.local:5000/payments-api:v1
# Kubernetes can run the content-addressed reference directly.
kubectl set image deployment/payments-api \
api=registry.local:5000/payments-api@sha256:REPLACE_WITH_DIGEST

Приватні реєстри зменшують анонімну відкритість, але не роблять образи безпечними автоматично. Вони відповідають на питання «хто може відправляти та завантажувати тут?», а не «чи є цей образ посиленим, просканованим, підписаним та затвердженим?». Сильний дизайн реєстру відокремлює права на запис від прав на завантаження, використовує робот- чи workload-ідентичності замість спільних людських паролів, обмежує облікові дані за репозиторієм чи простором імен та записує події просування. Слабкий приватний реєстр стає тихим місцем, де змінні теги, витеклі облікові дані та широкі токени завантаження розповзаються по середовищах.

У Kubernetes облікові дані для завантаження образів зазвичай зберігаються як Secret типу kubernetes.io/dockerconfigjson і вказуються через imagePullSecrets у Пода чи ServiceAccount. Kubelet використовує ці облікові дані під час завантаження з реєстру. Secret має існувати в тому самому просторі імен, що й Под, а обліковий запис потребує доступу до імені хосту реєстру, використаного в посиланні на образ. Якщо Под перебуває у стані ImagePullBackOff, перевірте повідомлення про подію, ім’я образу, простір імен, тип Secret, ім’я Secret та значення сервера реєстру, перш ніж змінювати непов’язані налаштування безпеки.

imagePullSecrets на рівні ServiceAccount корисні, коли багато Подів у просторі імен використовують той самий реєстр. Замість повторювати секрет у кожному шаблоні Пода, прикріпіть його до виділеного ServiceAccount і змусьте навантаження використовувати цей акаунт. Уникайте розміщення потужних облікових даних для завантаження на типовому ServiceAccount у кожному просторі імен, якщо немає чіткої платформенної політики та аудиторського сліду. Доступ на завантаження може розкрити пропрієтарний код, вбудовану конфігурацію та старі вразливі образи. Сприймайте його як дозвіл ланцюга постачання, а не лише як зручність для обходу анонімних обмежень швидкості.

Облікові дані реєстру також взаємодіють із кешами нод. Історично оператори іноді припускали, що якщо образ уже закешований на ноді, Под може стартувати, не доводячи, що він усе ще має права на завантаження. У Kubernetes 1.35 KubeletEnsureSecretPulledImages має статус beta й увімкнений за замовчуванням, тож kubelet може перевірити, що облікові дані для завантаження все ще дійсні, перш ніж використати закешований образ на ноді. Операційний урок усе одно ширший за цей один контроль: не покладайтеся на кеші нод як на межу авторизації. Використовуйте секрети завантаження з обмеженням за простором імен, короткоживучі облікові дані, де це можливо, аудиторські логи реєстру та політики допуску, які обмежують дозволені імена хостів реєстру.

Топологія реєстру впливає на доступність так само, як і на безпеку. Багато виробничих кластерів використовують приватний реєстр, наскрізний кеш (pull-through cache) чи регіональне дзеркало, щоб масштабування нод не залежало від анонімних публічних завантажень. Це зменшує відкритість до обмежень швидкості Docker Hub та збоїв публічних реєстрів, але створює відповідальність дзеркалити саме той артефакт, який ви переглядали. Дзеркало, яке оновлює теги автоматично, може повторно ввести змінність, якщо просування не фіксує дайджест, а дзеркало його не зберігає. Дзеркало, яке кешує вразливі образи назавжди, може приховати події видалення чи припинення підтримки upstream. Сприймайте дзеркало як частину ланцюга постачання: обмежуйте, хто може його наповнювати, логуйте, який дайджест було задзеркалено, та періодично прибирайте образи, які більше не мають відповідального власника релізу.

Область дії облікового запису має слідувати за межею розгортання. Ідентичність конвеєра збірки може потребувати прав на запис до staging-репозиторію, але секрет завантаження kubelet має потребувати лише прав на завантаження для репозиторію релізу. Особистий токен реєстру розробника не слід розміщувати в Secret кластера, бо це прив’язує доступність навантаження до людського облікового запису й ускладнює відкликання. Для хмарних реєстрів надавайте перевагу workload- чи керованим ідентичностям, які можна централізовано ротувати та аудувати. Для статичних облікових даних надайте їм календар закінчення терміну дії та протестуйте шлях ротації, перш ніж старий токен буде вимкнено. Багато інцидентів ImagePullBackOff насправді є інцидентами життєвого циклу облікових даних із повідомленнями про помилки Kubernetes наприкінці.

Terminal window
kubectl create namespace image-lab
kubectl -n image-lab create secret docker-registry registry-creds \
--docker-server=registry.local:5000 \
--docker-username=labuser \
--docker-password=labpassword \
--docker-email=labuser@example.com
kubectl -n image-lab create serviceaccount app-runner
kubectl -n image-lab patch serviceaccount app-runner \
-p '{"imagePullSecrets":[{"name":"registry-creds"}]}'

Політики завантаження образів

Розділ «Політики завантаження образів»

Перш ніж читати далі: яку політику завантаження отримує nginx без тегу за замовчуванням і чому це ризик для ланцюга постачання, коли тег реєстру може переміщуватися? Запишіть свою відповідь, а потім порівняйте її з типовими значеннями нижче.

imagePullPolicy контролює, коли kubelet перевіряє реєстр і коли він використовує закешований локальний образ. Типові значення Kubernetes легко пропустити: якщо ви опускаєте поле й використовуєте :latest чи відсутність тегу, політикою стає Always; якщо ви опускаєте поле й використовуєте тег, відмінний від latest, політикою стає IfNotPresent; якщо ви опускаєте поле й посилаєтеся на образ лише за дайджестом (наприклад, nginx@sha256:... без тегу), політикою також стає IfNotPresent. Політика встановлюється під час створення об’єкта й не змінюється автоматично пізніше, якщо ви відредагуєте тег образу. Ця деталь важлива в екзаменаційному усуненні несправностей, бо зміна nginx:1.25 на nginx:latest не гарантує, що політика завантаження стане Always, якщо ви не встановите її явно.

Always не означає «завантажувати кожен шар щоразу». Документація Kubernetes стверджує, що kubelet резолвить ім’я в дайджест щоразу, коли запускає контейнер, а потім використовує закешований образ, якщо точний дайджест уже присутній. Це дає свіже резолвлення тегів, водночас отримуючи переваги від кешування шарів та дайджестів. Перевага для безпеки в тому, що змінні теги перевіряються повторно; ціна для надійності в тому, що старт контейнера тепер залежить від досяжності реєстру та облікових даних. Під час збою реєстру чи мережевого розділення навантаження з Always та без закешованого резолвленого дайджесту можуть не стартувати, навіть якщо попередній образ існує локально.

IfNotPresent корисний, коли ви розгортаєте незмінні посилання чи попередньо завантажені образи й хочете швидшого старту з меншою залежністю від реєстру. Він небезпечний у поєднанні зі змінними тегами, бо різні ноди можуть запускати різний закешований вміст для того самого тегу. Одна нода може вже мати вчорашній app:prod, інша нода може завантажити сьогоднішній app:prod, і обидва Поди здаються такими, що використовують ту саму специфікацію. Прив’язка до дайджесту робить IfNotPresent значно безпечнішим, бо ідентичність вмісту є явною. Якщо дайджест відсутній, kubelet завантажує його; якщо він присутній, kubelet використовує той самий вміст.

Never належить до особливих випадків: ізольовані (air-gapped) кластери, попередньо завантажені лабораторні образи чи середовища, де розповсюдження образів виконується поза звичайними завантаженнями з реєстру. З Never kubelet взагалі не звертається до реєстру. Якщо образ ще не присутній на ноді, Под зазнає збою зі станом ErrImageNeverPull / ImageNeverPull, а не ImagePullBackOff. Цей режим збою може бути корисним під час іспитів, коли завдання зазначає, що образи попередньо завантажені. Це не загальне налаштування посилення. Якщо ви встановите Never у звичайному кластері без процесу розповсюдження образів на ноди, Поди зазнають збою, бо образ відсутній локально, і жоден контроль безпеки не покращився.

Для виробництва поєднуйте явні політики завантаження з незмінними посиланнями. Використовуйте дайджести для маніфестів релізів, встановлюйте IfNotPresent, коли сам дайджест є межею свіжості, та використовуйте Always там, де змінного тегу не уникнути під час розробки чи контрольованого шляху автоматизації. Для CKS будьте готові пояснити типові значення, дослідити ефективну специфікацію Пода та виправити невідповідність, яка спричиняє або застарілий вміст, або зайву залежність від реєстру.

Збір сміття образів (garbage collection) та попереднє завантаження можуть зробити поведінку політики завантаження заплутаною. Нода може тижнями запускати дайджест із кешу, а потім раптом потребувати реєстру після того, як збір сміття kubelet видалить старі шари. DaemonSet, який попередньо завантажує образи, може покращити швидкість розгортання, але він не доводить, що образ усе ще авторизований, якщо облікові дані й політика не перевіряються під час старту навантаження. Подія автоскейлера кластера може додати свіжі ноди з порожніми кешами, оголивши проблеми з DNS реєстру, проксі, сертифікатом чи обліковими даними, які старі ноди приховували. Під час усунення несправностей порівнюйте старі та нові ноди, досліджуйте події Подів та перевіряйте, чи є збій проблемою резолвлення імен, автентифікації, авторизації, обмеження швидкості чи відсутнього вмісту.

Ізольовані (air-gapped) кластери виражають ту саму думку в суворішій формі. Безпечний шлях — імпортувати підписані, проскановані дайджести у внутрішній реєстр і розгортати ці внутрішні посилання. Ризикований шлях — зберігати довільні tar-архіви, завантажувати їх вручну на ноди й встановлювати imagePullPolicy: Never без запису про те, що було імпортовано. Лабораторні завдання CKS іноді використовують попередньо завантажені образи для зручності, але виробничі ізоляції все одно потребують походження. Тримайте маніфест імпортованих дайджестів, результатів сканування, підписів та метаданих вихідного реєстру, щоб оператор міг відповісти, що працює, без доступу до інтернету.

Реальні екзаменаційні сценарії

Розділ «Реальні екзаменаційні сценарії»

У сценарії рев’ю Dockerfile починайте з фінального етапу. Завдання може показувати FROM ubuntu:latest, довгий список apt-get install, відсутність USER, скопійований .env та ENTRYPOINT у формі оболонки. Не переписуйте весь застосунок. Прив’яжіть чи звузьте базовий образ, перенесіть інструменти збірки до етапу-збиральника, додайте покриття .dockerignore, приберіть секрети з конструкції образу, встановіть користувача без прав root та використовуйте синтаксис команд у формі exec. Потім пов’яжіть типовий стан образу з securityContext Kubernetes, щоб Поду не потрібен був root для запуску.

У сценарії з посиланнями на образи швидко знаходьте змінні теги. kubectl get pods -A -o json плюс jq можуть ідентифікувати образи, що закінчуються на :latest чи без явного тегу. Для Deployment оновіть образ до дайджесту, отриманого на кроці сканування чи інспекції реєстру, а потім встановіть відповідну політику завантаження. Якщо завдання запитує, чому запущений Под усе ще показує старий тег, дослідіть status.containerStatuses[].imageID; Kubernetes часто записує там резолвлений дайджест навіть тоді, коли специфікація містить тег. Іспит хоче, щоб ви розрізняли запитаний образ та ідентичність запущеного образу.

У сценарії з приватним реєстром читайте події, перш ніж гадати. ErrImagePull та ImagePullBackOff зазвичай походять від неправильного імені хосту реєстру, відсутнього Secret у просторі імен, неправильного типу Secret, поганих облікових даних чи ServiceAccount, який не посилається на секрет завантаження. Створіть Secret за допомогою kubectl create secret docker-registry, прикріпіть його до Пода чи ServiceAccount та перевірте, що Под використовує цей ServiceAccount. Якщо помилка залишається, порівняйте значення --docker-server із точним префіксом реєстру в імені образу. registry.example.com/team/app та https://registry.example.com не завжди трактуються так, як очікують люди.

У сценарії посилення пам’ятайте, що налаштування образу та Пода підсилюють одне одного. Образ distroless із USER nonroot усе одно може зазнати збою, якщо застосунок пише в /var/cache на кореневій файловій системі лише для читання. Под із runAsNonRoot: true може зазнати збою, якщо метадані образу не мають числового користувача й Kubernetes не може довести, що той запускається без прав root. Базовий образ без оболонки може зробити так, що kubectl exec -- sh зазнає збою, що саме по собі не є зламаним навантаженням. Використовуйте логи, проби та ефемерні контейнери для зневадження, а не послаблюйте виробничий образ.

У сценарії з підписуванням чи політикою тримайте порядок перевірок чітким. Спочатку ідентифікуйте посилання на образ і резолвіть його в дайджест. Потім перевірте, чи має цей дайджест очікуваний підпис, ідентичність сертифіката, ключ чи атестацію. Нарешті, забезпечте результат на етапі допуску чи просування. Якщо питання дає вам непідписаний образ, який уже працює, зміна лише тегу Deployment не створює походження. Вам потрібен підписаний артефакт із затвердженого шляху збірки чи виняток політики з чіткою причиною. У реальних кластерах винятки мають бути обмежені в часі та прив’язані до дайджесту, а не до змінного тегу, який пізніше міг би вказати кудись інде.

У сценарії зі сканером не сприймайте кожен рядок CVE як рівнозначний. Запитайте, який пакет володіє знахідкою, чи перебуває пакет у фінальному етапі середовища виконання, чи існує виправлена версія в обраній базі та чи досяжний вразливий код застосунком. Іспит зазвичай очікує практичного усунення, а не дебатів про теоретичну експлуатованість. Перезберіть на виправленій базі, перенесіть інструменти збірки з фінального образу чи перейдіть на менше середовище виконання, де вразливий пакет відсутній. Коли знахідка належить графу залежностей застосунку, оновіть файл блокування та перезберіть, а не намагайтеся приховати пакет від сканера.

Terminal window
kubectl get pods -A -o json | jq -r '
.items[] as $pod
| (
($pod.spec.containers // []) + ($pod.spec.initContainers // []) + ($pod.spec.ephemeralContainers // [])
)[]
| select(.image | split("/")[-1] | (test("[:@]") | not) or endswith(":latest"))
| "\($pod.metadata.namespace)/\($pod.metadata.name) \(.name) \(.image)"
'
kubectl -n image-lab describe pod private-app
kubectl -n image-lab get pod private-app -o jsonpath='{.status.containerStatuses[0].imageID}'
  • Обмеження завантажень Docker Hub можуть впливати на розгортання Kubernetes. Поточна документація Docker зазначає 100 завантажень за шість годин для неавтентифікованих користувачів за адресою IPv4 чи IPv6 /64 та 200 завантажень за шість годин для автентифікованих Personal-користувачів, тож масштабування нод може зазнати збою з причин, які виглядають як звичайні помилки завантаження образу.

  • imagePullPolicy: Always усе одно використовує кешування дайджестів. Kubelet резолвить тег у дайджест під час запуску контейнера, а потім повторно використовує закешований образ, якщо точний дайджест уже присутній, що означає, що Always є перевіркою свіжості реєстру, а не гарантованим повним завантаженням.

  • Образи distroless легше обслуговувати, коли зневадження планується окремо. Документація Kubernetes рекомендує ефемерні контейнери для випадків, коли kubectl exec недостатньо, і образи distroless є яскравим прикладом, бо вони навмисно пропускають оболонки та утиліти зневадження.

  • Дайджест не замінює підпис. Дайджест доводить ідентичність вмісту, тоді як перевірка cosign чи Notation доводить, що очікуваний ключ чи ідентичність підписали дескриптор артефакту, використаний цим вмістом.

ПомилкаЧому це трапляєтьсяВиправлення
Прив’язка виробництва до :latestТег здається зручним способом отримувати оновлення без редагування YAML.Просувайте проскановані дайджести образів у маніфести й залишайте змінні теги для розробки чи виявлення.
Запуск від root, бо типовий стан базового образу — rootБагато офіційних образів працюють «з коробки» без інструкції USER, тож привілей невидимий.Встановіть користувача без прав root у фінальному образі та підсильте це за допомогою runAsNonRoot у контексті безпеки Пода.
Включення .env, kubeconfig чи SSH-матеріалу в контекст збіркиCOPY . . простий, а чутливий файл може бути неочевидним у великому репозиторії.Підтримуйте .dockerignore, копіюйте лише потрібні шляхи та тримайте секрети збірки поза шарами образу й логами.
Використання ADD, коли достатньо COPYПриклади з ADD копіюються зі старих Dockerfile без врахування його додаткової поведінки з архівами та URL.Використовуйте COPY для локальних файлів та явні кроки завантаження з перевіркою, коли потрібен віддалений вміст.
Забування imagePullSecrets у просторі імен навантаженняSecret існує деінде, або розробник тестував із локальними обліковими даними Docker.Створіть Secret типу kubernetes.io/dockerconfigjson у цільовому просторі імен та прикріпіть його до Пода чи ServiceAccount.
Постачання інструментів збірки у фінальному образіDockerfile компілює й запускає в одному етапі, тож очищення сприймається як необов’язкове.Використовуйте багатоетапні збірки та копіюйте лише артефакти середовища виконання в мінімальний фінальний етап.
Припущення, що distroless означає відсутність операційної роботиКоманди зосереджуються на зменшенні CVE й пізніше виявляють, що зневадження на основі оболонки більше не працює.Додайте логи, проби, образи для зневадження та ранбуки для ефемерних контейнерів, перш ніж перемикати виробничі навантаження.
Встановлення imagePullPolicy: Never як скорочення для посиленняПолітика звучить обмежувально, тож її помилково сприймають як контроль безпеки.Використовуйте Never лише для попередньо завантажених чи ізольованих робочих процесів; для довіри використовуйте політику реєстру, прив’язку до дайджесту та підписи.
  1. Deployment використовує registry.example.com/payments/api:prod із imagePullPolicy: IfNotPresent. Дві репліки потрапляють на різні ноди після розгортання, і лише одна демонструє нову поведінку. Як ви діагностуєте та виправите проблему контролю образів?

    Відповідь Дослідіть `status.containerStatuses[].imageID` кожного Пода, щоб порівняти резолвлені дайджести, бо тег у специфікації може бути ідентичним, тоді як закешований вміст ноди відрізняється. Корінь проблеми — змінний тег у поєднанні з `IfNotPresent`, що дозволяє нодам повторно використовувати будь-який локальний образ, який уже відповідає тегу. Стійке виправлення — просунути точний просканований дайджест, такий як `registry.example.com/payments/api@sha256:...`, та послідовно розгортати цей дайджест. Якщо теги мають залишатися у шляху розробки, використовуйте там `Always` та тримайте виробництво на просуванні за дайджестом.
  2. Dockerfile копіює .env під час збірки й пізніше виконує rm .env перед фінальним CMD. Сканер образів не повідомляє про активний файл секрету у фінальній файловій системі. Чому це все одно небезпечно й який патерн Dockerfile це виправляє?

    Відповідь Шари образу Docker та кеш збірки можуть зберігати файли, скопійовані в ранішому шарі, навіть якщо пізніший шар прибирає їх із об'єднаної файлової системи. Секрет також міг бути надісланий віддаленому збиральнику як частина контексту збірки. Виправлення — тримати `.env` поза контекстом за допомогою `.dockerignore`, уникати копіювання широких дерев вихідного коду до фільтрації та використовувати Secret Kubernetes під час виконання чи монтування секретів збірки для значень, потрібних лише під час збірки. Багатоетапна збірка допомагає, але лише якщо секрет ніколи не перетинає межу до збереженого етапу.
  3. Под, що використовує gcr.io/distroless/static-debian12:nonroot, успішно стартує, але kubectl exec pod/app -- sh зазнає збою під час усунення несправностей. Що має зробити відповідальний, не послаблюючи виробничий образ?

    Відповідь Збій очікуваний, бо образи distroless навмисно пропускають оболонки та утиліти зневадження. Відповідальний має використовувати логи, метрики, проби готовності та ефемерний контейнер для зневадження чи окремий образ для зневадження, а не додавати оболонку до виробничого образу. Якщо потрібна інспекція процесів, увімкніть чи використайте спільне використання простору імен процесів, де доречно, та приєднайте інструментальний образ через `kubectl debug`. Суть безпеки в тому, що виробничі образи середовища виконання залишаються мінімальними, тоді як діагностика надається через контрольований аварійний (break-glass) шлях.
  4. Под приватного реєстру застряг у ImagePullBackOff. Secret registry-creds існує в просторі імен default, тоді як Под працює в просторі імен payments і використовує payments-registry.local/api:v1. Які команди та перевірки найбільш доречні?

    Відповідь Спочатку дослідіть `kubectl -n payments describe pod ` на предмет точної помилки завантаження. Потім переконайтеся, що Secret реєстру Docker існує в просторі імен `payments` зі значенням `--docker-server`, що відповідає `payments-registry.local`, та прикріпіть його через `imagePullSecrets` чи ServiceAccount Пода. Secret у `default` недоступний Подам у `payments`. Після патчингу ServiceAccount чи шаблону Пода перезапустіть Под, щоб kubelet повторив спробу з правильними обліковими даними, обмеженими простором імен.
  5. Ваша CI-система підписує образи за допомогою cosign, але маніфест Kubernetes усе ще розгортає app:stable. Зловмисник отримує права на запис до реєстру й замінює тег stable непідписаним образом. Які контролі мають заблокувати чи обмежити атаку?

    Відповідь Прив'язка до дайджесту обмежує заміну тегу, бо виробничі маніфести посилаються на точний підписаний дайджест, який пройшов CI, а не на змінний тег. Перевірка підпису обмежує атаку, бо політика допуску може відхиляти образи, чий дайджест не має довіреного підпису cosign чи очікуваної ідентичності. Дозволи реєстру та незмінні теги зменшують шанс заміни, але вони не мають бути єдиними контролями. Сильний робочий процес підписує відправлений дайджест, записує його в маніфест релізу та забезпечує перевірку до допуску.
  6. Сервіс на Go наразі збирається в golang:1.22 і постачає той самий образ у виробництво. Сканер повідомляє про багато вразливостей у компіляторах та інструментах пакетів, які бінарний файл не використовує. Яке переписування зменшує ризик, зберігаючи процес збірки незмінним?

    Відповідь Використовуйте багатоетапну збірку. Залиште `golang:1.22` як етап-збиральник, скомпілюйте бінарний файл там, а потім скопіюйте лише бінарний файл у мінімальний фінальний етап, такий як `gcr.io/distroless/static-debian12:nonroot` чи `scratch`, якщо вимоги до бінарного файлу та сертифікатів це дозволяють. Встановіть `USER nonroot:nonroot` там, де база це підтримує, та використовуйте `ENTRYPOINT` у формі exec. Це прибирає компілятори та інструменти пакетів із виробничої файлової системи, зберігаючи ті самі команди збірки на етапі-збиральнику.
  7. Рев’юер бачить imagePullPolicy: Always і каже, що кластер завантажуватиме образ під час кожного перезапуску, тож команда має перейти на IfNotPresent для продуктивності. Яку поправку ви маєте внести?

    Відповідь `Always` змушує kubelet резолвити ім'я образу в дайджест щоразу, коли він запускає контейнер, але якщо точний дайджест уже закешований локально, kubelet може використати закешований вміст. Занепокоєння щодо продуктивності реальне лише тоді, коли резолвлення реєстру, облікові дані чи відсутні шари сповільнюють старт. Рішення про політику має базуватися на тому, чи є посилання змінним. Для прив'язаних до дайджесту виробничих образів `IfNotPresent` зазвичай розумний; для змінних тегів розробки `Always` робить переміщення тегу видимим під час перезапуску.

Ця лабораторна робота використовує kind, локальний реєстр, сумісні з Docker команди збірки та Trivy чи Grype. Запускайте її на одноразовому робочому кластері, де ви можете створювати простори імен, збирати образи та відправляти до локального реєстру. Мета — зібрати посилений образ, просканувати його перед розгортанням, завантажити його зі шляху приватного реєстру та довести, що прив’язка до дайджесту захищає запущене навантаження від переміщення тегу.

  • Створіть контейнер локального реєстру з ім’ям kind-registry на порту 5001, якщо він ще не запущений: docker run -d --restart=always -p 127.0.0.1:5001:5000 --name kind-registry registry:2.
  • Створіть кластер kind із дзеркалом реєстру для localhost:5001, потім підключіть контейнер реєстру до мережі kind, щоб ноди могли завантажувати через kind-registry:5000.
kind-with-registry.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
containerdConfigPatches:
- |-
[plugins."io.containerd.grpc.v1.cri".registry]
config_path = "/etc/containerd/certs.d"
nodes:
- role: control-plane
Terminal window
kind create cluster --name image-lab --config kind-with-registry.yaml
docker network connect kind kind-registry
for node in $(kind get nodes --name image-lab); do
docker exec "$node" mkdir -p /etc/containerd/certs.d/localhost:5001
cat <<EOT | docker exec -i "$node" tee /etc/containerd/certs.d/localhost:5001/hosts.toml
server = "http://localhost:5001"
[host."http://kind-registry:5000"]
capabilities = ["pull", "resolve"]
EOT
done
  • Створіть робочий каталог із крихітним застосунком на Go чи статичним HTTP-застосунком, файлом go.mod, якщо потрібно, та .dockerignore, що виключає .git, .env, *.key, kubeconfig та локальний результат збірки.
  • Напишіть багатоетапний Dockerfile, який компілює на етапі-збиральнику й копіює лише фінальний бінарний файл чи статичні файли в gcr.io/distroless/static-debian12:nonroot чи інший мінімальний образ середовища виконання без прав root.
  • Зберіть образ як localhost:5001/cks/image-secure:v1 та дослідіть його за допомогою docker image inspect, щоб перевірити, що налаштований користувач та точка входу не є root і не у формі оболонки.
  • Просканіруйте локальний образ за допомогою trivy image localhost:5001/cks/image-secure:v1 чи grype localhost:5001/cks/image-secure:v1, потім запишіть, які знахідки походять із базового образу, а які — із залежностей застосунку.
  • Відправте образ до локального реєстру та зафіксуйте його дайджест за допомогою docker buildx imagetools inspect localhost:5001/cks/image-secure:v1 чи еквівалентної команди інспекції реєстру.
  • Створіть простір імен image-lab, потім створіть Secret реєстру Docker для localhost:5001, щоб попрактикувати робочий процес imagePullSecrets, навіть якщо локальний реєстр може не вимагати автентифікації.
  • Створіть ServiceAccount з ім’ям image-runner у image-lab та прикріпіть Secret реєстру через поле imagePullSecrets ServiceAccount.
  • Розгорніть застосунок за дайджестом через localhost:5001/cks/image-secure@sha256:REPLACE_WITH_DIGEST, встановіть imagePullPolicy: IfNotPresent та використовуйте ServiceAccount image-runner.
  • Додайте контекст безпеки Пода з runAsNonRoot: true, allowPrivilegeEscalation: false, readOnlyRootFilesystem: true та capabilities.drop: ["ALL"]; додайте монтування emptyDir, якщо застосунку потрібен призначений для запису тимчасовий шлях.
  • Дочекайтеся, доки Под стане Ready, потім перевірте, що kubectl -n image-lab get pod -o jsonpath='{.items[0].status.containerStatuses[0].imageID}' повідомляє очікуваний дайджест.
  • Перезберіть помітно інший образ і відправте його під тим самим тегом v1, але не змінюйте дайджест Deployment; перезапустіть Под і перевірте, що він усе ще запускає початково прив’язаний дайджест.
  • Запатчте Deployment до нового дайджесту, дочекайтеся розгортання та підтвердьте, що дайджест змінюється лише після зміни маніфесту.
  • Приберіть за собою за допомогою kubectl delete namespace image-lab, видаліть кластер kind та приберіть контейнер локального реєстру, якщо він був створений лише для цієї лабораторної роботи.

Перевірка засвоєння

Розділ «Перевірка засвоєння»

Дайджест доводить ідентичність вмісту, але він не доводить, хто зібрав образ; виробниче просування має поєднувати прив’язку до дайджесту з перевірками підпису чи атестації від затвердженого конвеєра.

Перш ніж рухатися далі, поясніть, чому imagePullPolicy: Never не є контролем посилення та яку помилку ви очікуєте, коли образ відсутній на ноді. Ґрунтовна відповідь називає ErrImageNeverPull / ImageNeverPull, а не ImagePullBackOff.

  • Kubernetes Images — документує імена образів, типові значення політики завантаження, резолвлення дайджестів, посилання на приватні реєстри та поведінку перевірки облікових даних.
  • Kubernetes: Pull an Image from a Private Registry — показує підтримуваний робочий процес Secret типу docker-registry та використання imagePullSecrets у Пода.
  • Kubernetes Ephemeral Containers — пояснює, чому образи distroless потребують окремого шляху зневадження, коли kubectl exec недостатньо.
  • Kubernetes Security Context — надає поля Пода та контейнера, які використовуються для підсилення типових станів образу без прав root.
  • Docker Build Best Practices — охоплює довірені базові образи, багатоетапні збірки, періодичність перезбірки, .dockerignore та рекомендації щодо інструкцій Dockerfile.
  • Docker Hub Pull Usage and Limits — перелічує поточні обмеження завантажень Docker Hub та пояснює, як працює облік завантажень і відповіді 429.
  • GoogleContainerTools Distroless — вихідний проєкт для образів середовища виконання distroless від Google та приклади.
  • Sigstore Cosign Verification — документує cosign verify та перевірки ідентичності, що використовуються для підписаних артефактів контейнерів.
  • Notary Project Quickstart — знайомить із підписуванням та перевіркою Notation для артефактів OCI.
  • CIS Docker Benchmark — джерело бенчмарку для рекомендацій із посилення демона Docker, образу, Dockerfile та середовища виконання.
  • NIST SP 800-190: Application Container Security Guide — рекомендації NIST щодо безпеки образу, реєстру, оркестратора, ОС хосту та середовища виконання контейнерів.
  • NVD CVE-2024-21626 — бюлетень runc для класу виходу з контейнера, пов’язаного з витеклими файловими дескрипторами та доступом до файлової системи хосту.
  • NVD CVE-2021-32760 — бюлетень containerd, що показує, як спеціально створене вилучення образу може вплинути на дозволи файлової системи хосту.
  • Codecov Bash Uploader Security Update — реальний інцидент ланцюга постачання CI із задокументованою шкідливою поведінкою завантажувача та ураженими часовими вікнами.
  • GitHub Advisory: Embedded malware in ua-parser-js — бюлетень для шкідливих версій ua-parser-js 0.7.29, 0.8.0 та 1.0.0.
  • CNCF TAG Security: ua-parser-js compromise — запис каталогу компрометацій, що пов’язує деталі захоплення пакета з cloud-native практикою ланцюга постачання.
  • CNCF TAG Security Software Supply Chain Best Practices v2 — рекомендації CNCF щодо вхідних даних збірки, атестацій, безпеки артефактів та політики розгортання.
  • KodeKloud CKS Exam Verification Guide — довідник із підготовки до CKS, що включає теми безпеки ланцюга постачання, сканування образів, підписування та безпеки Dockerfile.

Модуль 5.2: Сканування образів за допомогою Trivy — знаходьте відомі вразливості, неправильні конфігурації та вбудовані секрети в образах контейнерів перед розгортанням.