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

Модуль 5.3: Безпека середовища виконання

Складність: [СЕРЕДНЯ] — базові знання

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

Передумови: Модуль 5.2: Спостережуваність безпеки

Результати навчання

Розділ «Результати навчання»

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

  1. Впроваджувати профілі seccomp та контексти безпеки Kubernetes, що зменшують поверхню атаки на ядро Linux для робочих навантажень Kubernetes 1.35+.
  2. Порівнювати моделі примусу AppArmor та SELinux, а потім обирати підхід Mandatory Access Control, що відповідає операційній системі робочого вузла.
  3. Діагностувати збої, спричинені скиданням можливостей, файловими системами лише для читання, засобами контролю ескалації привілеїв та примусом профілів середовища виконання.
  4. Оцінювати стандартні середовища виконання OCI, gVisor та Kata Containers для ізоляції довірених, недовірених і багатоорендних робочих навантажень.
  5. Проєктувати запобіжники допуску та політик, що тримають засоби контролю безпеки середовища виконання узгодженими між командами, не приховуючи операційних компромісів.

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

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

Щойно всередині кластера запускається ворожий код, слабкі засоби контролю середовища виконання дають зловмисникам простір для виконання операцій, споживання ресурсів та злиття зі звичайною активністю робочих навантажень. Хрестоматійний випадок 2018 року у великій автомобільній компанії задокументовано у модулі безпеки GUI напрямку CKS : відкрита адміністративна консоль дозволила зловмисникам розгорнути шкідливі Поди, що запускали програмне забезпечення для майнінгу та споживали хмарні обчислювальні ресурси, водночас намагаючись лишатися непомітними. Публічні повідомлення зосередилися на відкритій консолі, але урок щодо середовища виконання був не менш важливим — засоби контролю допуску та образів не можуть втрутитися, коли процес уже запущено на робочому вузлі.

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

Засоби контролю середовища виконання — це різниця між «зловмисник дістався одного вебпроцесу» та «зловмисник може трасувати сусідні процеси, монтувати файлові системи хоста, завантажувати функції ядра або зловживати спільною помилкою ядра». У цьому модулі ви почнете з примітивів Linux, які надає Kubernetes, а потім поєднаєте їх із політикою допуску та вибором середовища виконання. Суть не в тому, щоб запам’ятати кожне поле безпеки у специфікації Пода, а в тому, щоб навчитися будувати багаторівневу межу, яка все одно дозволяє виробничому програмному забезпеченню працювати.

Стек безпеки середовища виконання

Розділ «Стек безпеки середовища виконання»

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

┌─────────────────────────────────────────────────────────────┐
│ RUNTIME SECURITY STACK │
├─────────────────────────────────────────────────────────────┤
│ │
│ LAYER 4: KUBERNETES ADMISSION │
│ ├── Validates pods before scheduling │
│ ├── Pod Security Standards │
│ └── Policy engines (OPA, Kyverno) │
│ │
│ LAYER 3: CONTAINER RUNTIME │
│ ├── OCI runtime (runc, crun) │
│ ├── Sandboxed runtimes (gVisor, Kata) │
│ └── Runtime security configuration │
│ │
│ LAYER 2: LINUX SECURITY MODULES │
│ ├── seccomp (syscall filtering) │
│ ├── AppArmor (file/network restrictions) │
│ ├── SELinux (mandatory access control) │
│ └── Capabilities (privilege restrictions) │
│ │
│ LAYER 1: KERNEL │
│ ├── Namespaces (isolation) │
│ ├── cgroups (resource limits) │
│ └── Core security features │
│ │
└─────────────────────────────────────────────────────────────┘

Стек виконується знизу вгору, але оператори налаштовують його згори вниз. Розробник подає маніфест Пода до API-сервера, контролери допуску вирішують, чи прийнятний цей маніфест, планувальник призначає Под вузлу, а kubelet просить середовище виконання контейнерів створити контейнер. Лише тоді середовище виконання застосовує seccomp, AppArmor або SELinux, можливості, прапори файлової системи, вибір просторів імен та обраний обробник середовища виконання — перш ніж стартує процес застосунку.

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

Перш ніж використовувати приклади, один раз визначте у своїй оболонці звичний скорочений запис Kubernetes через alias k=kubectl. У модулі k використовується як псевдонім для kubectl, а команди на кшталт k apply -f runtime-lab.yaml припускають, що цей псевдонім доступний.

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

Безпека середовища виконання — це також місце, де Kubernetes змушує вас мислити одночасно як інженера платформи й як оператора Linux. API Kubernetes дає вам поля на кшталт securityContext, runtimeClassName та мітки Pod Security, але фактичний примус відбувається у програмному забезпеченні вузла, яке може різнитися за операційною системою, збіркою образу, конфігурацією середовища виконання та версією ядра. Тому маніфест, який виглядає переносним, може поводитися по-різному в різних пулах вузлів, якщо платформа не публікує чіткі класи середовища виконання, мітки вузлів та базові рівні безпеки.

Найбезпечніша ментальна модель — розглядати кожен шар як окреме питання. Допуск запитує, чи має кластер прийняти об’єкт. Середовище виконання запитує, як саме слід запустити процес. Модулі безпеки Linux запитують, які операції ядра, шляхи файлів, мітки та привілеї лишаються доступними після запуску. Збій на будь-якому одному шарі не повинен спокушати вас вимкнути весь стек, бо шари навмисно вузькі й стають сильними лише в поєднанні.

Фільтрація системних викликів за допомогою Seccomp

Розділ «Фільтрація системних викликів за допомогою Seccomp»

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

┌─────────────────────────────────────────────────────────────┐
│ SECCOMP (SECURE COMPUTING MODE) │
├─────────────────────────────────────────────────────────────┤
│ │
│ PURPOSE: │
│ • Filter which system calls a process can make │
│ • Block dangerous syscalls (mount, ptrace, etc.) │
│ • Reduce kernel attack surface │
│ │
│ HOW IT WORKS: │
│ Application → Syscall → Seccomp Filter → Allow/Deny/Log │
│ │
│ PROFILE TYPES: │
│ ├── RuntimeDefault - Container runtime's default profile │
│ ├── Unconfined - No filtering (dangerous) │
│ └── Localhost - Custom profile from node │
│ │
│ KUBERNETES 1.27+: │
│ RuntimeDefault is the default for new clusters │
│ │
│ BLOCKED BY DEFAULT (RuntimeDefault): │
│ • mount, umount │
│ • ptrace │
│ • reboot │
│ • Most kernel module operations │
│ │
└─────────────────────────────────────────────────────────────┘

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

Профіль seccomp RuntimeDefault від середовища виконання контейнерів є практичним базовим рівнем для більшості робочих навантажень Kubernetes. Він блокує родини системних викликів, що зазвичай використовуються для маніпуляцій із ядром, створення просторів імен, трасування, завантаження модулів та інших дій, яких звичайні застосункові контейнери рідко потребують. Кластери Kubernetes 1.35 мають розглядати явний RuntimeDefault як мінімально прийнятне налаштування в маніфестах — навіть попри те, що сучасні випуски Kubernetes застосовують безпечніші типові налаштування для нових Подів, ніж старіші кластери, — бо явні маніфести переживають міграції та полегшують перевірку.

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

apiVersion: v1
kind: Pod
metadata:
name: secure-nginx
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: nginx
image: nginx:1.27
securityContext:
# You can also define it at the container level
# which overrides the pod-level setting
seccompProfile:
type: RuntimeDefault

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

Коли стається відмова seccomp, симптом залежить від системного виклику, дії профілю та обробки помилок застосунком. Деякі програми отримують EPERM і журналюють зрозумілу проблему з дозволами, тоді як інші падають під час ініціалізації, бо бібліотека припускає, що системний виклик завжди буде доступним. Саме тому добрий план розгортання включає репрезентативний трафік, тести запуску та перегляд журналів аудиту замість єдиної перевірки «контейнер запустився». Профіль, який переживає лише першу перевірку готовності, не є виробничим доказом.

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

{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
],
"syscalls": [
{
"names": [
"accept4",
"bind",
"close",
"epoll_ctl",
"epoll_pwait",
"listen",
"read",
"write"
],
"action": "SCMP_ACT_ALLOW"
}
]
}

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

apiVersion: v1
kind: Pod
metadata:
name: custom-seccomp-pod
spec:
securityContext:
seccompProfile:
type: Localhost
localhostProfile: custom-profiles/strict-web.json
containers:
- name: web
image: my-secure-app:v2

Посилання Localhost є відносним до кореня профілів seccomp kubelet на вузлі, зазвичай під /var/lib/kubelet/seccomp/. Саме тому власні профілі найлегше застосовувати в керованих пулах вузлів і важче — у кластерах, де вузли часто замінюються через автомасштабування. Якщо Под може потрапити на вузол, де немає профілю, kubelet не зможе застосувати обіцяну межу, тож маркування вузлів, керування конфігурацією середовища виконання та обмеження планування стають частиною проєктування безпеки.

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

Зупиніться та подумайте: Якщо зловмисник експлуатує вразливість віддаленого виконання коду у вашому застосунку та намагається завантажити шкідливе навантаження через curl, чи завадить завантаженню суворий профіль seccomp, який дозволяє лише read, write, open та close? Чому так або чому ні?

Найточніша відповідь полягає в тому, що seccomp, імовірно, заблокував би мережеві операції, потрібні curl, але він не блокував би «завантаження шкідливого ПЗ» як людську концепцію. Він блокує конкретні системні виклики, як-от створення сокета, встановлення з’єднання, доступ до файлів, пов’язаних із DNS, або виконання процесу — залежно від профілю. Ця відмінність має значення під час написання політик, бо засоби контролю середовища виконання найсильніші тоді, коли ви виражаєте їх як конкретні поведінки ядра, а не як широкі цілі зловмисника.

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

Mandatory Access Control: AppArmor та SELinux

Розділ «Mandatory Access Control: AppArmor та SELinux»

Seccomp обмежує дієслова, які процес може використовувати, спілкуючись із ядром, але Mandatory Access Control обмежує об’єкти, до яких цей процес може торкатися. У звичайному дискреційному контролі доступу Linux процес, що працює від root, часто може обходити перевірки прав доступу до файлів або вносити зміни, недоступні процесу не від root. Системи MAC додають примусовий політичний шар на рівні ядра, який діє навіть тоді, коли процес працює від root, — і саме тому вони важливі у контейнерних середовищах, де «root всередині контейнера» легко створити випадково.

┌─────────────────────────────────────────────────────────────┐
│ APPARMOR │
├─────────────────────────────────────────────────────────────┤
│ │
│ PURPOSE: │
│ • Mandatory Access Control (MAC) │
│ • Restrict file access, network, capabilities │
│ • Program-specific security policies │
│ │
│ AVAILABLE ON: │
│ • Ubuntu, Debian, SUSE │
│ • NOT on RHEL/CentOS (use SELinux instead) │
│ │
│ PROFILE MODES: │
│ ├── Enforce - Block violations │
│ ├── Complain - Log but allow (learning mode) │
│ └── Unconfined - No restrictions │
│ │
│ EXAMPLE RESTRICTIONS: │
│ • Deny write to /etc/* │
│ • Allow read from /var/log/* │
│ • Deny network raw access │
│ • Deny mount operations │
│ │
└─────────────────────────────────────────────────────────────┘

AppArmor орієнтований на шляхи й зазвичай відчувається доступним, бо профіль може казати, що процес може читати один каталог, писати в інший, забороняти виконання оболонки та забороняти сире мережеве з’єднання. Це робить його привабливим для парків Ubuntu, Debian і SUSE, де операційна система вузла вже його підтримує. Його операційна перевага — читабельність, але обмеження полягає в тому, що політика на основі шляхів потребує ретельного обмірковування навколо змонтованих томів, символьних посилань, згенерованих файлів та компонувань робочих навантажень, що змінюються між версіями образу.

Найлегший спосіб неправильно застосувати AppArmor — вважати скопійований профіль універсальною відповіддю. Профіль, спроєктований для статичного контенту NGINX, може бути розумним для одного образу контейнера й болісно неправильним для іншого образу, який пише кеш-файли, завантажує модулі або викликає оболонку під час запуску. Команди платформи мають версіонувати профілі як код, тестувати їх з образами, які вони захищають, і тримати назву профілю достатньо змістовною, щоб рецензент міг пов’язати анотацію Пода з власним артефактом політики.

#include <tunables/global>
profile custom-nginx flags=(attach_disconnected) {
#include <abstractions/base>
#include <abstractions/nameservice>
# Allow read access to web content
/usr/share/nginx/html/** r,
# Allow read/write to specific log and cache directories
/var/log/nginx/** rw,
/var/cache/nginx/** rw,
/run/nginx.pid rw,
# Explicitly deny access to sensitive files
deny /etc/passwd r,
deny /etc/shadow r,
deny /etc/kubernetes/** r,
# Deny the ability to execute shells
deny /bin/sh x,
deny /bin/bash x,
# Deny raw sockets (prevents certain types of network spoofing)
deny network raw,
}

Профілі мають бути завантажені в ядро робочого вузла, перш ніж Поди зможуть їх використовувати. Багато команд розповсюджують профілі AppArmor через привілейований DaemonSet або конфігурацію образу вузла, а потім посилаються на завантажений профіль із Пода. Kubernetes рухає AppArmor у бік структурованих полів securityContext, але багато виробничих кластерів усе ще містять приклади на основі анотацій, бо вони підтримували цю функцію роками й лишаються поширеними в runbook’ах платформ.

apiVersion: v1
kind: Pod
metadata:
name: apparmor-protected
annotations:
container.apparmor.security.beta.kubernetes.io/web: localhost/custom-nginx
spec:
containers:
- name: web
image: nginx:alpine

Hypothetical scenario: під час навчання red-team у Java-сервісі була помилка обходу шляху, що дозволяла зловмисникам запитувати файли поза призначеним каталогом контенту. Застосунок працював від root у своєму контейнері, тож звичайної власності на файли було б недостатньо для захисту. Профіль AppArmor забороняв читання з чутливих шляхів хоста та системи, тож експлойт усе ще успішно маніпулював рядками, але ядро відхиляло файлову операцію та продукувало докази аудиту, які захисники могли розслідувати.

Ця історія також нагадує, що MAC — це не лише про запобігання. Влучно названа відмова AppArmor чи SELinux дає захисникам подію з високим сигналом, бо звичайні застосунки не повинні намагатися читати облікові дані вузла Kubernetes, конфігурацію хоста чи файли тіньових паролів. Спостережуваність безпеки середовища виконання з попереднього модуля стає кориснішою, коли примусовий профіль достатньо специфічний, аби порушення казало щось змістовне про поведінку робочого навантаження.

┌─────────────────────────────────────────────────────────────┐
│ SELINUX │
├─────────────────────────────────────────────────────────────┤
│ │
│ PURPOSE: │
│ • Mandatory Access Control (MAC) │
│ • Label-based security │
│ • Every file, process has a security context │
│ │
│ AVAILABLE ON: │
│ • RHEL, CentOS, Fedora │
│ • NOT on Ubuntu/Debian (use AppArmor) │
│ │
│ MODES: │
│ ├── Enforcing - Block violations │
│ ├── Permissive - Log but allow │
│ └── Disabled - No SELinux │
│ │
│ CONTEXT FORMAT: │
│ user:role:type:level │
│ system_u:system_r:container_t:s0 │
│ │
│ KUBERNETES USES: │
│ • seLinuxOptions in securityContext │
│ • Labels for pod processes and volumes │
│ │
└─────────────────────────────────────────────────────────────┘

SELinux реалізує ту саму широку ідею через мітки, а не через шляхи. Процеси, файли, каталоги та подеколи порти отримують контексти безпеки з компонентами користувача, ролі, типу й рівня. Контейнерні платформи на системах родини RHEL зазвичай маркують контейнерні процеси типом контейнера та призначають мітки Multi-Category Security, щоб два контейнери зі схожими дозволами все одно не могли невимушено читати приватний вміст томів одне одного.

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

apiVersion: v1
kind: Pod
metadata:
name: selinux-custom-pod
spec:
securityContext:
seLinuxOptions:
level: "s0:c99,c100"
containers:
- name: worker
image: busybox
command: ["sleep", "3600"]
securityContext:
seLinuxOptions:
type: "container_t"
user: "system_u"
role: "system_r"

Практична відмінність помітна під час усунення несправностей. Збої AppArmor зазвичай ведуть вас до назв профілів і заборонених шляхів, тоді як збої SELinux ведуть до міток процесів, міток файлів та повідомлень аудиту, що пояснюють відмову типового примусу. Оператор Kubernetes не обирає між ними для кожного Пода так, як обирає тег образу; операційна система робочого вузла та модуль безпеки ядра вирішують, яка система MAC доступна, а маніфести Подів мають відповідати цій реальності.

Коли команда повідомляє про «відмову в доступі» від монтування тому, утримайтеся від спокуси виправити лише власність Unix. Ідентифікатор користувача, ідентифікатор групи, режим файлової системи, мітка SELinux, профіль AppArmor та поведінка постачальника сховища можуть усі брати участь у фінальному рішенні. Добра діагностика запитує, який шар відмовив у доступі, бо зміна fsGroup може вирішити проблему дискреційних дозволів, але нічого не зробити для невідповідності типу SELinux.

Який підхід ви обрали б тут і чому: пул вузлів Ubuntu, що запускає інгрес NGINX, відкритий в інтернет, чи пул вузлів RHEL, що запускає регульовані фінансові робочі навантаження з наявним досвідом операцій SELinux? Правильна відповідь стосується менше того, який акронім звучить сильніше, і більше того, яку модель примусу ваша команда платформи може розгорнути, спостерігати, налаштовувати та лагодити під час збою.

Для практики рівня KCSA зосередьтеся на розпізнаванні межі, яку примушує кожна система MAC, та операційної передумови, якої вона потребує. AppArmor потребує завантажених профілів і сумісного ядра вузла. SELinux потребує правильних міток і підтримки політики на вузлі. Kubernetes надає гачки для обох, але він не може змусити непідтримуваний модуль ядра примушувати політику й не може зробити неправильно позначений каталог хоста безпечним мрійливим проєктуванням маніфеста.

Розкладання root на можливості (Capabilities)

Розділ «Розкладання root на можливості (Capabilities)»

Можливості Linux існують тому, що стара модель «root або не-root» була надто грубою. Історично процес, якому потрібна була одна привілейована операція, як-от прив’язка до низькономерного порту, часто мусив працювати від root і водночас отримував багато непов’язаних повноважень. Можливості розбивають цей пакет на вужчі привілеї, тож Kubernetes може прибрати більшість повноважень root навіть тоді, коли образ усе ще запускає процес із нульовим ідентифікатором користувача.

┌─────────────────────────────────────────────────────────────┐
│ LINUX CAPABILITIES │
├─────────────────────────────────────────────────────────────┤
│ │
│ PURPOSE: │
│ • Break down root privileges into discrete units │
│ • Grant specific privileges without full root │
│ • Reduce privilege if container is compromised │
│ │
│ DEFAULT CAPABILITIES (container runtime): │
│ • CHOWN, DAC_OVERRIDE, FSETID, FOWNER │
│ • MKNOD, NET_RAW, SETGID, SETUID │
│ • SETFCAP, SETPCAP, NET_BIND_SERVICE │
│ • SYS_CHROOT, KILL, AUDIT_WRITE │
│ │
│ DANGEROUS CAPABILITIES: │
│ • CAP_SYS_ADMIN - Near-root privileges │
│ • CAP_NET_ADMIN - Network configuration │
│ • CAP_SYS_PTRACE - Debug any process │
│ • CAP_DAC_READ_SEARCH - Bypass read permissions │
│ │
│ BEST PRACTICE: │
│ Drop all, add only what's needed │
│ │
└─────────────────────────────────────────────────────────────┘

Середовища виконання контейнерів зазвичай запускають контейнери з обмеженим типовим набором можливостей, а не з кожним можливим привілеєм root. Цей типовий набір безпечніший за необмежений процес root, але він усе одно ширший, ніж потрібно багатьом застосункам. CAP_NET_RAW може підтримувати створення пакетів та мережеву підробку; CAP_DAC_OVERRIDE може обходити звичайні перевірки прав доступу до файлів; CAP_SYS_ADMIN настільки широка, що команди мають ставитися до неї як до попередження про привілейований режим, якщо немає дуже конкретної причини, пов’язаної з ядром.

apiVersion: v1
kind: Pod
metadata:
name: capability-tuned-pod
spec:
containers:
- name: application
image: my-golang-service:1.0
securityContext:
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE

Найбезпечнішим типовим вибором є drop: ["ALL"], після чого йдуть явні додавання лише після того, як застосунок доведе, що вони потрібні. Сервіс, що слухає на порту 8080, не потребує NET_BIND_SERVICE, бо цей порт не є привілейованим. Контейнер для усунення несправностей із захопленням пакетів може потребувати NET_RAW, а часто й NET_ADMIN, але це має бути тимчасовим, ізольованим інструментом налагодження, а не набором можливостей, скопійованим у кожен застосунковий деплоймент.

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

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

Збої можливостей часто діагностуються поєднанням помилок застосунку, подій середовища виконання та невеликого відтворення. Якщо процес повідомляє «operation not permitted» під час прив’язки до порту 80, кандидатом є NET_BIND_SERVICE. Якщо процес не може змінити таблиці маршрутизації, імовірнішою є NET_ADMIN. Якщо інструмент не може оглянути інший процес, може бути задіяна SYS_PTRACE, але додавання її до звичайного застосункового контейнера створило б серйозний ризик бічного огляду.

Складність у тому, що багато помилок дозволів виглядають однаково для власників застосунків. Розробник може не знати, чи виник EPERM через відсутню можливість, відмову seccomp, правило AppArmor, мітку SELinux чи файлову систему лише для читання. Команди платформи можуть зменшити плутанину, документуючи контрольний список сортування: перевірити події Пода, переглянути відповіді допуску, перевірити журнали аудиту ядра, подивитися на контекст безпеки контейнера й відтворити з найменшим тимчасовим винятком, перш ніж змінювати виробничу політику.

Супутній засіб контролю — allowPrivilegeEscalation: false, який встановлює поведінку Linux no_new_privs для процесу контейнера. Це налаштування запобігає тому, щоб процес здобував більше привілеїв через двійкові файли setuid чи подібні переходи виконання. Воно не замінює скидання можливостей, виконання не від root чи використання seccomp, але закриває поширений шлях, де зловмисник шукає в образі забутий допоміжний двійковий файл і намагається перетворити невеликий плацдарм на сильніший.

Саме тому посилені образи й посилені специфікації Подів слід переглядати разом. Видалення двійкових файлів setuid з образу краще, ніж просто покладатися на no_new_privs, але allowPrivilegeEscalation: false захищає вас, коли образ містить щось несподіване. Так само виконання не від root зменшує стартовий привілей, тоді як скидання можливостей зменшує спеціальні повноваження ядра, що можуть лишитися. Засоби контролю перекриваються за призначенням, але не за точним примусом.

Рушії політик та запобіжники допуску

Розділ «Рушії політик та запобіжники допуску»

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

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

┌─────────────────────────────────────────────────────────────┐
│ OPA GATEKEEPER │
├─────────────────────────────────────────────────────────────┤
│ │
│ WHAT IT IS: │
│ • Policy engine for Kubernetes │
│ • Admission controller │
│ • Uses Rego language for policies │
│ │
│ USE CASES: │
│ • Require labels on resources │
│ • Block privileged containers │
│ • Enforce resource limits │
│ • Restrict registries │
│ • Custom organizational policies │
│ │
│ COMPONENTS: │
│ ├── ConstraintTemplate - Define policy type │
│ ├── Constraint - Apply policy with parameters │
│ └── Gatekeeper controller - Enforce policies │
│ │
└─────────────────────────────────────────────────────────────┘

OPA Gatekeeper потужний, коли організація хоче повторно використовувані обмеження, підкріплені логікою Rego. ConstraintTemplate визначає механіку політики, а Constraint застосовує цю механіку до обраних видів ресурсів, просторів імен чи параметрів. Цей поділ цінний для команд платформи, яким потрібні узгоджені примітиви політики, але він також означає, що команда має підтримувати експертизу в Rego й ретельно тестувати політики, перш ніж вони стануть виробничою залежністю допуску.

Gatekeeper добре підходить, коли логіка політики потребує виразного оцінювання довільних ресурсів Kubernetes або коли організація вже використовує Open Policy Agent деінде. Наприклад, команда платформи може примушувати списки дозволених реєстрів, мітки власності просторів імен або власні зв’язки між анотаціями робочих навантажень і сервісними акаунтами. Ці політики можуть доповнювати безпеку середовища виконання, але їх слід тестувати як код застосунку, бо зламаний валідаційний вебхук може блокувати легітимні зміни кластера.

┌─────────────────────────────────────────────────────────────┐
│ KYVERNO │
├─────────────────────────────────────────────────────────────┤
│ │
│ WHAT IT IS: │
│ • Kubernetes-native policy engine │
│ • No new language (uses YAML) │
│ • Admission controller │
│ │
│ POLICY TYPES: │
│ ├── Validate - Check and reject │
│ ├── Mutate - Automatically modify │
│ ├── Generate - Create new resources │
│ └── Verify Images - Signature verification │
│ │
│ ADVANTAGES: │
│ • Familiar YAML syntax │
│ • Kubernetes-native │
│ • Easy to get started │
│ │
└─────────────────────────────────────────────────────────────┘

Kyverno використовує YAML у стилі Kubernetes і часто легший для спільного читання застосунковими та платформними командами. Він може валідувати, що Под встановлює runAsNonRoot, мутувати відсутній seccompProfile на місце, генерувати супутні ресурси та перевіряти підписи образів. Ця доступність не усуває потреби в дисципліні, бо мутування полів безпеки може здивувати розробників, якщо платформа не документує, що змінюється і як запитувати винятки.

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-root-user
spec:
validationFailureAction: Enforce
background: true
rules:
- name: validate-runAsNonRoot
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Running as root is not allowed. Set runAsNonRoot to true."
pattern:
spec:
securityContext:
runAsNonRoot: true
containers:
- =(securityContext):
=(runAsNonRoot): true

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

Мутування потребує ще більшої обережності, ніж валідація. Автоматичне додавання RuntimeDefault може бути розумним типовим значенням платформи, бо для більшості робочих навантажень це поле має низький ризик, але мутування ідентифікаторів користувачів, монтувань томів чи можливостей може ламати застосунки у спосіб, який дивує власників. Якщо ви мутуєте налаштування безпеки, відкривайте фінальний відрендерений об’єкт через інструменти перевірки або звіти політик, щоб розробники бачили контекст безпеки, який насправді отримав їхній Под.

Перед запуском, який вивід ви очікуєте від k apply -f root-pod.yaml, якщо політику Kyverno вже примушено? Ви маєте очікувати відхилення допуску від API-сервера, а не Под, що запускається й потім падає, бо рушій політик оцінює об’єкт ще до того, як kubelet створює контейнер.

Не використовуйте Kyverno і Gatekeeper разом для примусу того самого поля, якщо у вас немає конкретного плану міграції або причини, пов’язаної з доменом збоїв. Дубльовані перевірки створюють два джерела істини, два стилі повідомлень про помилки та два місця, де винятки можуть розходитися. Глибинний захист зазвичай краще виражати нашаруванням різних засобів контролю, як-от Pod Security Admission для широкої базової поведінки, один рушій політик для специфічних для організації правил та примус ядра для запущеного процесу.

RuntimeClass та середовища виконання-пісочниці

Розділ «RuntimeClass та середовища виконання-пісочниці»

Seccomp, можливості та MAC — усі вони зменшують те, що процес може зробити зі спільним ядром, але стандартні середовища виконання OCI все одно поділяють це ядро. Для довірених внутрішніх сервісів це зазвичай розумний компроміс, бо runc і crun швидкі, добре зрозумілі та сумісні з широкою екосистемою Kubernetes. Для виконання недовіреного коду, клієнтських плагінів, автоматизації браузерів у масштабі чи багатоорендних робочих навантажень CI припущення про спільне ядро заслуговує значно більшого розгляду.

┌─────────────────────────────────────────────────────────────┐
│ SANDBOXED RUNTIME COMPARISON │
├─────────────────────────────────────────────────────────────┤
│ │
│ STANDARD RUNTIME (runc) │
│ ├── Direct syscalls to host kernel │
│ ├── Fastest performance │
│ ├── Kernel vulnerability = container escape │
│ └── Use for: Trusted workloads │
│ │
│ gVisor (runsc) │
│ ├── User-space kernel (Sentry) │
│ ├── Intercepts and emulates syscalls │
│ ├── Implements a large portion of the Linux syscall │
│ │ interface (see gVisor compatibility notes) │
│ ├── Performance overhead (varies by workload) │
│ └── Use for: Untrusted workloads, multi-tenant │
│ │
│ Kata Containers │
│ ├── Lightweight VM per container │
│ ├── Separate kernel (not shared) │
│ ├── Typically KVM (also Firecracker, Cloud Hypervisor) │
│ ├── Higher overhead than gVisor │
│ └── Use for: Maximum isolation, compliance │
│ │
│ CHOOSING A RUNTIME: │
│ Trusted internal workloads → runc │
│ Untrusted/multi-tenant → gVisor │
│ Maximum isolation → Kata │
│ │
└─────────────────────────────────────────────────────────────┘

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

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

Kata Containers обирає інший шлях, запускаючи Поди всередині легких віртуальних машин із власними гостьовими ядрами. Це дає сильнішу межу ізоляції для команд, яким потрібна апаратна віртуалізація як частина їхньої моделі ризику, ціною вищих накладних витрат на запуск і ресурси, ніж у звичайному контейнері runc. Тому питання проєктування полягає не в тому, «яке середовище виконання найбезпечніше» само по собі; воно в тому, чи виправдовує рівень довіри до робочого навантаження операційну й продуктивнісну ціну.

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

apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: gvisor
# The handler matches the runtime configured in containerd/CRI-O on the node
handler: runsc
---
apiVersion: v1
kind: Pod
metadata:
name: untrusted-customer-script
spec:
runtimeClassName: gvisor
containers:
- name: runner
image: python:3.11-alpine

RuntimeClass дозволяє Kubernetes обрати обробник середовища виконання, налаштований у середовищі виконання контейнерів вузла. Самого маніфеста недостатньо; containerd чи CRI-O має знати, що означає runsc чи обробник Kata, на вузлі має бути встановлене потрібне середовище виконання, а планування має тримати ці Поди на сумісних вузлах. Якщо платформа пропонує застосунковим командам пісочницю як сервіс, вона має публікувати класи середовища виконання з чіткими назвами, задокументованими обмеженнями та прикладами маніфестів, що включають також звичайні засоби контролю контексту безпеки.

Планування є частиною межі середовища виконання. Якщо gVisor підтримують лише деякі вузли, RuntimeClass може включати підказки планування, або платформа може поєднати його з селекторами вузлів, taints і перевірками допуску. Без такої координації команди можуть бачити заплутані збої, де маніфест дійсний, але жоден сумісний вузол насправді не може запустити Под. Тому ізоляція середовища виконання є функцією платформи, а не просто рядком у YAML робочого навантаження.

Корисне правило — резервувати середовища виконання-пісочниці для межі довіри, а не для невиразного відчуття важливості. Платіжний API, який ваша організація пише, сканує, розгортає та моніторить, може краще обслуговуватися runc плюс суворими найменшими привілеями. Хостований виконавець збірок, що виконує довільні клієнтські скрипти, має використовувати сильнішу ізоляцію, бо бізнес-модель із самого початку припускає ворожий ввід.

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

Патерни та антипатерни

Розділ «Патерни та антипатерни»

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

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

ПатернКоли використовуватиЧому це працюєМіркування щодо масштабування
Позиція скидання можливостей за замовчуваннямБільшість застосункових навантажень, що не потребують привілеїв адміністрування ядраСкидання всіх можливостей перетворює привілей на явний вибір проєктування, а не на успадкування під час виконанняТримайте приклади винятків невеликими й переглянутими, бо скопійовані блоки можливостей швидко розповзаються через значення Helm
RuntimeDefault скрізьЗагальні навантаження на кластерах Kubernetes 1.35+Підтримуваний базовий рівень seccomp середовища виконання блокує небезпечні точки входу в ядро без потреби у власному профілюванніПримушуйте через допуск, щоб забуті контексти безпеки не ставали звичним випадком
Профілі MAC для високоризикових шляхівКонтролери інгресу, агенти вузлів та сервіси, що оброблюють недовірені файлові шляхиAppArmor чи SELinux усе одно обмежує процеси root, коли звичайних дозволів недостатньоУзгодьте модель профілю з ОС вузла й тримайте журнали аудиту доступними для пошуку під час розгортання
RuntimeClass за рівнем довіриВиконання клієнтського коду, хостований CI, системи плагінів та чутливі до відповідності орендаріСередовища виконання-пісочниці відводять недовірений код від прямого покладання на ядро хостаПублікуйте нотатки сумісності та плануйте лише на вузли зі встановленим обробником
АнтипатернЩо йде не такЧому команди в це впадаютьКраща альтернатива
Встановлення Unconfined після одного збоюНавантаження запускається, але широка межа системних викликів зникає для кожного майбутнього експлойтуПомилка виглядає як збій застосунку, а дедлайн близькоПрофілюйте заборонений системний виклик, використайте вузький профіль Localhost або скоригуйте дизайн застосунку
Додавання CAP_SYS_ADMIN як універсального виправленняПривілей, близький до root, потрапляє в контейнер і знецінює велику частину моделі найменших привілеївБагато непов’язаних операцій падають зі схожими повідомленнями про дозволиВизначте точну можливість або перепроєктуйте навантаження, щоб уникнути адміністрування хоста
Змішування дубльованих рушіїв політик для того самого поляРозробники бачать заплутані повідомлення про відхилення, а латентність допуску API зростає«Глибинний захист» тлумачать як повторення ідентичних перевірокПризначте чітку власність, як-от Kyverno для безпеки Подів і Gatekeeper для власних правил організації
Ставлення до середовища виконання-пісочниці як до срібної куліПроблеми сумісності, зростання вартості та слабкі контексти безпеки Подів лишаються нерозв’язанимиСильна ізоляція звучить так, ніби вона має замінити нижчорівневі засоби контролюВикористовуйте пісочницю для меж довіри, зберігаючи seccomp, можливості та MAC на місці

Фреймворк прийняття рішень

Розділ «Фреймворк прийняття рішень»

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

START
|
v
Is the workload executing untrusted customer or tenant code?
|-- yes --> Use RuntimeClass for gVisor or Kata, then still enforce least privilege.
|
|-- no --> Does it need host namespaces, privileged mode, or broad capabilities?
|-- yes --> Treat as node-adjacent infrastructure; isolate nodes and review manually.
|
|-- no --> Use RuntimeDefault, drop all capabilities, run as non-root, and enforce by policy.
РішенняВіддавайте перевагу цьомуУникайте цьогоОбґрунтування
Базовий рівень системних викликівRuntimeDefault для більшості ПодівUnconfined як скорочення заради сумісностіТипове значення середовища виконання прибирає небезпечні точки входу в ядро з низькою операційною ціною
Власний профіль системних викликівLocalhost після профілювання та розповсюдження на вузлиНаписані вручну списки заборон без тестового покриттяПрофілі за принципом «відмова за замовчуванням» крихкі, якщо не випробувані під реалістичним трафіком
Вибір MACAppArmor на дистрибутивах із підтримкою AppArmor, SELinux на дистрибутивах родини RHELМаніфести, що припускають неправильний модуль безпеки вузлаЯдро не може примусити модуль, який вузол не запускає
Можливостіdrop: ["ALL"] плюс точні додаванняТипові можливості, скопійовані в кожен чартНайменші привілеї легше перевіряти, коли додавання рідкісні й названі
Вибір середовища виконанняrunc для довірених навантажень, gVisor чи Kata для недовіреного кодуОдне середовище виконання для кожного рівня ризикуНакладні витрати середовища виконання мають відповідати цінності межі ізоляції
ДопускОдин чіткий власник політики на домен безпекиДубльовані правила в різних рушіях без власностіОператорам потрібні передбачувані повідомлення про відхилення та шляхи винятків

Фреймворк також дає вам послідовність налагодження. Якщо API-сервер відхиляє об’єкт, спершу огляньте політику допуску та налаштування Pod Security. Якщо Под запускається, а процес отримує EPERM, огляньте seccomp, можливості, allowPrivilegeEscalation та журнали аудиту MAC. Якщо робоче навантаження поводиться по-різному лише на деяких вузлах, огляньте мітки вузлів, конфігурацію обробника середовища виконання, розповсюдження профілів та модулі безпеки операційної системи.

Коли ви не впевнені, який засіб контролю посилити першим, віддавайте перевагу широким типовим налаштуванням із низьким ризиком перед власною інженерією. Запускайте не від root, вимикайте ескалацію привілеїв, скидайте можливості, використовуйте RuntimeDefault, робіть кореневу файлову систему лише для читання там, де застосунок це дозволяє, та блокуйте простори імен хоста. Щойно це стане рутиною, додайте профілі MAC для навантажень зі значущим ризиком доступу до файлів та середовища виконання-пісочниці для навантажень, що перетинають межі довіри. Ця послідовність дає командам помітні перемоги, не змушуючи перше розгортання залежати від власної політики ядра для кожного сервісу.

  • Kubernetes увімкнув seccomp RuntimeDefault за замовчуванням у версії 1.27, що означає: оператори Kubernetes 1.35+ успадковують безпечніший базовий рівень, ніж старіші кластери, але все одно мають оголошувати його явно заради придатності до перевірки.
  • Sentry від gVisor написаний на Go й реалізує суттєву поверхню ядра у просторі користувача, тож багато системних викликів застосунку обробляються, перш ніж вони можуть напряму дістатися ядра хоста.
  • Мітки SELinux зазвичай містять чотири поля у формі user:role:type:level, причому type і рівень MCS особливо важливі для ізоляції контейнерного процесу та томів.
  • CAP_SYS_ADMIN навмисно достатньо широка, щоб документація ядра давно описувала її як перевантажену, тож її додавання до контейнера має викликати таку саму прискіпливість, як і привілейований режим.
ПомилкаЧому це стаєтьсяЯк це виправити
Залишення Подів фактично необмеженимиКоманди покладаються на типові налаштування кластера чи старі маніфести й ніколи не перевіряють підсумковий профіль seccompВстановіть seccompProfile: type: RuntimeDefault явно та примушуйте базовий рівень через політику допуску
Збереження типових можливостейЗастосунок працює під час розробки, тож ніхто не помічає успадкований набір привілеївВикористайте drop: ["ALL"], додавайте лише доведені вимоги й документуйте кожне додавання під час перевірки навантаження
Застосування маніфестів AppArmor на вузлах SELinuxІнженери копіюють приклади з кластерів Ubuntu у середовища родини RHELУзгодьте засоби контролю MAC з ОС вузла та використовуйте seLinuxOptions там, де SELinux є примусовим модулем
Примус власних профілів без розгортання в режимі аудитуСуворий профіль блокує рідкісний, але легітимний шлях, який тести не покрилиПочніть із поведінки аудиту чи скарги, де можливо, перегляньте журнали, а потім примушуйте після репрезентативного трафіку
Використання одного середовища виконання для кожного орендаряСтандартизація відчувається простішою, ніж підтримка класів середовища виконання та пулів вузлівВизначте класи середовища виконання за рівнем довіри й плануйте недовірені навантаження лише на сумісні вузли-пісочниці
Додавання CAP_SYS_ADMIN для усунення невиразних помилок дозволівБагато операцій ядра падають зі схожими повідомленнями «operation not permitted»Відтворіть збій, визначте точну можливість чи системний виклик і віддавайте перевагу перепроєктуванню застосунку, де можливо
Покладання лише на простори імен для стримуванняІзоляція просторів імен виглядає сильною, бо вигляд процесів обмеженийНашаруйте простори імен із seccomp, можливостями, MAC, файловими системами лише для читання та політикою допуску
Дублювання тієї самої валідації в Kyverno і GatekeeperКоманди плутають повторювані перевірки із сильнішим захистомПризначте кожен домен політики одному рушію й використовуйте моніторинг плюс тести, щоб перевірити, що примус працює
1. Ваша команда розгортає Go API на Kubernetes 1.35 із `runAsNonRoot`, `readOnlyRootFilesystem` та скинутими можливостями, але маніфест пропускає `seccompProfile`. Рецензент запитує, чи прийнятно це, бо сучасні кластери вже застосовують seccomp безпечніше за замовчуванням. Як вам відповісти?

Навантаження може отримати безпечніше типове значення на сучасному кластері, але маніфест усе одно має оголошувати seccompProfile: type: RuntimeDefault. Явна конфігурація робить намічену межу видимою під час перевірки, переживає міграцію на кластери з іншими типовими налаштуваннями та дає політиці допуску чітке поле для валідації. Інші налаштування контексту безпеки цінні, але вони не замінюють фільтрацію системних викликів, бо можливості та прапори файлової системи контролюють різні поведінки ядра.

2. Пул вузлів на базі RHEL ігнорує анотацію AppArmor, що працювала в попередньому кластері Ubuntu, і контейнер усе ще може читати файли, які команда очікувала заборонити. Яка найімовірніша архітектурна проблема та як виправити дизайн?

Імовірна проблема в тому, що пул вузлів використовує SELinux, а не AppArmor, як свою систему Mandatory Access Control. Анотації AppArmor не можуть примусити політику шляхів, якщо ядро вузла не запускає AppArmor для контейнерів, тож маніфест дає хибне відчуття захисту. Виправте дизайн, використавши мітки SELinux і seLinuxOptions, або запланувавши навантаження на пул вузлів із підтримкою AppArmor та завантаженими потрібними профілями.

3. Застарілий застосунок падає після переходу з необмеженого профілю seccomp на `RuntimeDefault`, а докази аудиту вказують на заблокований системний виклик `unshare`. Бізнес хоче повернути застосунок онлайн без повернення до повністю необмеженого виконання. Яку стратегію вам запропонувати?

Почніть із підтвердження, що застосунку справді потрібен unshare і що цю поведінку не можна прибрати чи ізолювати в безпечніший помічник. Якщо вимога реальна, побудуйте власний профіль seccomp на основі типового значення середовища виконання, додайте лише потрібний системний виклик, розповсюдьте цей профіль на призначені вузли та посилайтеся на нього через type: Localhost. Це зберігає більшість захистів типового значення середовища виконання, водночас роблячи виняток явним і придатним для тестування.

4. Хостований сервіс CI запускає довільні клієнтські shell-скрипти у Подах, використовуючи runc, `RuntimeDefault`, скинуті можливості та користувачів не від root. Оголошено нову локальну вразливість ескалації привілеїв ядра. Чи адекватно ізольовані ці клієнтські Поди від ядра хоста?

Вони краще посилені, ніж типові Поди, але вони не ізольовані адекватно для довільного ворожого коду, якщо загрозою є вразливість ядра хоста. З runc контейнери все одно поділяють ядро робочого вузла, тож успішний експлойт ядра може пройти попід межами seccomp і можливостей. Сервіс, що навмисно виконує недовірений клієнтський код, має оцінити gVisor чи Kata через RuntimeClass, зберігаючи наявні налаштування найменших привілеїв як додаткові шари.

5. Розробник додає `CAP_SYS_ADMIN`, бо його контейнер отримує «operation not permitted» під час запуску інструмента, інтенсивного щодо файлової системи. Под тепер працює, і команда хоче змержити зміну. Що вам перевірити перед схваленням?

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

6. Політика Kyverno відхиляє Под ще до планування, але застосункова команда продовжує шукати пояснення в журналах контейнера й нічого не знаходить. Як перенаправити розслідування?

Журналів контейнера не буде, бо Под ніколи не досяг kubelet і жоден контейнерний процес не запустився. Розслідування має перейти до відповіді API-сервера, звіту політики Kyverno, журналів контролера допуску та полів маніфеста, які зіставлені правилом. Ця відмінність важлива операційно, бо збої допуску — це збої контракту конфігурації, тоді як відмови середовища виконання лишають докази в подіях kubelet, помилках застосунку чи журналах аудиту ядра.

7. Посилений API працює від користувача 10001 зі скинутими всіма можливостями, але пентест знаходить двійковий файл-помічник setuid усередині образу. Яке налаштування контексту безпеки обмежує небезпеку, і чому його самого недостатньо?

allowPrivilegeEscalation: false обмежує небезпеку, встановлюючи no_new_privs, що запобігає тому, щоб процес та його нащадки здобували додаткові привілеї через setuid чи подібні переходи виконання. Самого його недостатньо, бо образ усе ще містить непотрібне ризиковане програмне забезпечення, а інші шляхи атаки можуть не залежати від поведінки setuid. Правильна реакція — зберегти налаштування, прибрати помічник з образу, працювати не від root, скинути можливості та використати обмеження файлової системи й системних викликів.

Практична вправа: Інженерія посиленого контексту безпеки

Розділ «Практична вправа: Інженерія посиленого контексту безпеки»

Ви мігруєте критичний backend-сервіс API на виробничий кластер Kubernetes 1.35. Сервіс написаний на Go, слухає на порту 8080, читає конфігурацію зі змонтованого тому, пише тимчасові дані обробки й не потребує просторів імен хоста, сирих сокетів, трасування, адміністрування файлової системи чи прив’язки до низькономерних портів. Ваша мета — побудувати маніфест Пода, що виражає найменші привілеї достатньо чітко для рецензента й достатньо суворо, щоб зловмисник знайшов дуже мало простору після компрометації.

Використайте чорновий файл з назвою runtime-lab.yaml та застосуйте його через k apply -f runtime-lab.yaml, коли будете готові тестувати. Якщо у вашому кластері увімкнено політики Pod Security Admission чи Kyverno, порівняйте відповідь API-сервера зі своїми очікуваннями, перш ніж змінювати маніфест, бо відхилення може означати, що ваша платформа вже примушує частину базового рівня.

Вправа навмисно зосереджена на звичайному сервісі, а не на привілейованому демоні. Більшість роботи з безпеки Kubernetes стосується не рідкісних агентів вузлів, яким справді потрібен незвичний доступ; вона стосується того, щоб зробити типовий застосунковий шлях нудно безпечним. Якщо ви можете посилити цей API, не ламаючи його базових потреб, ви зможете застосувати ту саму логіку, переглядаючи значення Helm, згенеровані маніфести чи pull request’и від застосункових команд.

Контрольний список вимог:

  • Застосунок має працювати від користувача не-root з ідентифікатором користувача 10001 та ідентифікатором групи 10001.
  • Застосунку має бути явно заборонено ескалацію привілеїв через setuid чи подібні переходи.
  • Коренева файлова система має бути лише для читання, з придатним до запису тимчасовим каталогом, змонтованим у /tmp/workdir.
  • Усі можливості Linux мають бути скинуті, бо сервіс слухає на порту 8080 й не потребує прив’язки до привілейованого порту.
  • Типовий профіль seccomp середовища виконання має бути оголошений явно.
  • Простори імен процесів, мережі та IPC хоста мають лишатися вимкненими.

Завдання 1: Накресліть структуру Пода, назвіть контейнер і встановіть hostNetwork, hostPID та hostIPC так, щоб Под не міг бачити чи приєднуватися до просторів імен хоста.

Завдання 2: Налаштуйте securityContext на рівні Пода з ідентичністю не-root, груповою власністю для змонтованих файлів та явним профілем seccomp середовища виконання.

Завдання 3: Налаштуйте securityContext на рівні контейнера з allowPrivilegeEscalation: false, privileged: false, кореневою файловою системою лише для читання та набором можливостей, що скидає все.

Завдання 4: Додайте том emptyDir і змонтуйте його в /tmp/workdir, щоб застосунок мав навмисне придатне до запису розташування, поки файлова система образу лишається незмінною.

Завдання 5: Застосуйте маніфест, огляньте підсумковий Под через k describe pod hardened-api-service та поясніть, чи виник збій через допуск, завантаження образу або примус середовища виконання.

Переглянути рішення та пояснення
apiVersion: v1
kind: Pod
metadata:
name: hardened-api-service
labels:
app: api-backend
spec:
# Task 1: Prevent host namespace access
hostNetwork: false
hostPID: false
hostIPC: false
# Task 2: Pod-level security context
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
fsGroup: 10001
# Apply standard syscall filtering
seccompProfile:
type: RuntimeDefault
containers:
- name: api-service
image: docker.io/library/python:3.12-slim
command: ["python", "-m", "http.server", "8080", "--bind", "0.0.0.0"]
ports:
- containerPort: 8080
# Task 3: Container-level security context
securityContext:
# Prevent privilege escalation (no_new_privs)
allowPrivilegeEscalation: false
# Ensure container is not privileged
privileged: false
# Enforce immutability
readOnlyRootFilesystem: true
# Drop all capabilities. Since the app listens on 8080
# (an unprivileged port > 1024), we do not need NET_BIND_SERVICE.
capabilities:
drop:
- ALL
# Task 4: Provide explicit writable areas
volumeMounts:
- name: tmp-workdir
mountPath: /tmp/workdir
volumes:
- name: tmp-workdir
emptyDir: {}

Ця конфігурація нашаровує кілька незалежних захистів. Процес працює від користувача 10001, а не від root, не може здобути нові привілеї через виконання setuid, не може писати у файлову систему образу й не має жодних можливостей Linux для витрати після компрометації. Под також оголошує типовий фільтр seccomp середовища виконання та відмовляється від просторів імен хоста, тож зловмисник, який дістанеться процесу Python, не зможе невимушено оглядати процеси хоста, використовувати широкі привілеї ядра чи зберігати зміни в шарі образу контейнера.

Маніфест навмисно суворий, але не чарівний. Якщо застосунок очікує писати під /var/cache, він даватиме збій, поки ви не зміните шлях застосунку або не змонтуєте туди навмисний придатний до запису том. Якщо образ потребує root під час запуску, налаштування не-root виявить цю проблему пакування. Ці збої корисні, бо вони переносять рішення про привілеї в придатний до перевірки YAML, замість того щоб лишати їх прихованими всередині скрипту запуску контейнера.

Критерії успіху:

  • k apply -f runtime-lab.yaml створює Под або повертає повідомлення політики, яке ви можете точно пояснити.
  • k get pod hardened-api-service -o yaml показує seccompProfile.type: RuntimeDefault.
  • Контекст безпеки контейнера показує allowPrivilegeEscalation: false, readOnlyRootFilesystem: true та capabilities.drop, що містить ALL.
  • Специфікація Пода показує hostNetwork: false, hostPID: false та hostIPC: false.
  • Ви можете пояснити, яке налаштування запобігає ескалації setuid і яке налаштування зменшує поверхню атаки системних викликів.

Модуль 5.4: Інструментарій безпеки — Тепер, коли ви розумієте низькорівневі примітиви безпеки середовища виконання, ви розглянете інструменти на кшталт Falco та Trivy, що спостерігають, перевіряють та автоматизують ці засоби контролю в масштабі.