Безпека контейнерів: практики для образів і runtime
Збірка передає токен розгортання в крок RUN, використовує його й видаляє файл наступним рядком. Dockerfile виглядає акуратно. Образ публікується в публічний реєстр.
Шари образу доступні лише для додавання, тож видалення нічого не прибрало. Воно поклало новий шар поверх того, який досі тримає токен, і docker history зчитує його однією командою.
Ця відстань між тим, що Dockerfile нібито робить, і тим, що образ насправді містить, і є полем роботи з безпеки контейнерів. У runtime є дзеркальна версія тієї самої проблеми, де прапорець, доданий, щоб контейнер запрацював у стейджингу, тихо віддає йому хост.
Що таке безпека контейнерів?
Section titled “Що таке безпека контейнерів?”Безпека контейнерів — це практика поводження з образом і середовищем виконання як з межами безпеки, а не як з деталями пакування. Вона охоплює те, що містить зібраний образ, звідки він походить і хто може в нього писати, з якими привілеями працює контейнер і до яких портів та хостів він дістається після запуску.
NIST SP 800-190, Application Container Security Guide, ділить задачу так само: ризики образів, ризики реєстрів, ризики оркестратора, ризики середовища виконання та ризики хостової ОС. Більшість інцидентів припадає на перші чотири, і більшість із них є конфігурацією, а не кодом.
Різниця, яка має значення щодня, проста. Безпека застосунків питає, чи коректно ваш код обробляє введення. Безпека контейнерів питає, що ваш код успадковує, під ким він працює і хто ще може до нього дістатися.
Чим безпека контейнерів відрізняється від безпеки застосунків?
Section titled “Чим безпека контейнерів відрізняється від безпеки застосунків?”Уразливість застосунку дає зловмиснику одне: рівно те, що дозволяє ця конкретна вада. Слабкості контейнера вирішують, наскільки далеко це одне зайде.
Ін’єкція команд у сервісі, який працює під непривілейованим користувачем у distroless-образі, дає зловмиснику одну команду без шелу й без інструментів. Та сама вада у привілейованому контейнері з монтуванням хоста дає root на машині.
Різницю визначають те, що образ привіз із собою, і те, що контейнеру видали. Кожен інструмент усередині образу є інструментом, який успадкує зловмисник, а контейнер — це набір можливостей ядра, які деплой може просто вимкнути.
Мережа додає третю змінну. Прив’язку порту створює середовище виконання контейнерів на хості, тож у рев’ю security group вона не з’являється взагалі.
Які основні ризики безпеки контейнерів?
Section titled “Які основні ризики безпеки контейнерів?”Групування за місцем, де виникає збій, полегшує розподіл відповідальності між командами.
Збірка: що потрапляє всередину образу
Section titled “Збірка: що потрапляє всередину образу”Секрети, запечені в шари, є найпрямішим ризиком. Облікові дані, передані у збірку, лишаються в цьому шарі назавжди, а образи в публічних реєстрах скануються саме заради цього. Вправа «Секрети в шарах образів» веде вас через завантаження опублікованого образу платіжної компанії, зчитування дійсного токена з історії збірки, ротацію облікових даних і перезбірку з secret mount у BuildKit.
Це інший збій, ніж секрет, закомічений у репозиторій, хоча результат виглядає однаково. Механізм збереження тут шари, а не коміти, і виправленням є збірка, яка ніколи не пише значення на диск. Якщо облікові дані є ще й в історії системи контролю версій, цю половину покриває вправа «Секрети в історії Git».
Роздуті runtime-образи є тихішою проблемою збірки. Продакшн-образ із менеджером пакетів, компілятором, шелом і мережевими утилітами перетворює обмежену ваду на робочий набір інструментів. Вправа «Мінімальні образи контейнерів» стискає 412 МБ runtime багатоетапною збіркою й доводить, що шелу в підсумковому образі більше немає.
Постачання: звідки взявся базовий образ
Section titled “Постачання: звідки взявся базовий образ”Плаваючий тег базового образу означає, що шар під вашим кодом змінюється без чийогось рішення. Застарілий тег означає, що опубліковані відомі вразливості їдуть у продакшн з кожним деплоєм, у шарі, про який не згадує жоден файл залежностей. Вправа «Вразливі базові образи» сканує розгорнутий образ, закріплює базу за digest, переходить на slim-варіант і пересканує, щоб довести, що знахідки більше немає.
Гірше, коли база була ворожою від початку. Опублікувати образ під назвою, схожою на довірену, може будь-хто, і без перевірки підписів рядок FROM є просто рядком. Вправа «Шкідливі базові образи» простежує криптомайнер до видавця-двійника, а потім переводить перевірку підписів на момент завантаження, а не на момент інциденту.
Розповсюдження: хто може читати й писати у ваш реєстр
Section titled “Розповсюдження: хто може читати й писати у ваш реєстр”Реєстр тримає ваш код, вашу конфігурацію та артефакт, який ви розгортаєте. Анонімне завантаження віддає все це кожному, хто вгадає назву репозиторію, а анонімний запис дозволяє замінити тег, який ви саме збираєтесь випустити.
Вправа «Незахищеність реєстру контейнерів» покриває обидві половини й завершується обмеженими правами на запис і замороженими релізними тегами.
Runtime: що видали контейнеру
Section titled “Runtime: що видали контейнеру”Прапорець privileged разом із монтуванням хоста перетворює виконання коду в маловажливому воркері на контроль над машиною під ним. Процес, що працює як root усередині контейнера, є root на ядрі хоста, і це щоразу дивує тих, хто бачить це вперше. Пройдіть це у вправі «Привілейовані контейнери», де образ і деплой виправляються окремо, бо це два різні власники.
Docker API еквівалентний root і постачається без автентифікації. Демон, якому сказали слухати мережевий порт з вимкненим TLS, відповість кожному, хто до нього дістанеться, і на відміну від втечі з контейнера тут не потрібен жоден плацдарм. Вправа «Відкритий Docker-демон» починається з публічної сторінки статусу, яка називає продакшн-вузол, і завершується взаємним TLS зі скоупленим проксі до сокета.
Мережа: що насправді опублікувала прив’язка порту
Section titled “Мережа: що насправді опублікувала прив’язка порту”Публікація порту контейнера прив’язує його до всіх інтерфейсів за замовчуванням. Прив’язка, написана так, як її пише розробник на ноутбуці, здатна винести базу даних у публічний інтернет, коли той самий стек їде в продакшн, і хмарний фаєрвол при цьому не змінюється.
У мережі bridge за замовчуванням кожен контейнер також дістається до кожного іншого. Вправа «Незахищеність мережі контейнера» підключається до опублікованого порту бази без участі застосунку, прив’язує мапінг до loopback і сегментує сервіси у власну мережу.
Які практики безпеки контейнерів застосувати першими?
Section titled “Які практики безпеки контейнерів застосувати першими?”Порядок визначений тим, скільки ризику знімає кожен крок на годину роботи, а не тим, де він стоїть у бенчмарку.
- Скануйте образи в CI і зупиняйте збірку на критичних знахідках у базовому шарі. Це ловить категорію, яку не покриває жодне рев’ю залежностей.
- Закріплюйте базові образи за digest. Тег є рухомою ціллю. Digest є саме тим, що ви протестували.
- Використовуйте secret mount під час збірки, а не build-аргументи. Облікові дані з
--build-argзаписуються в образ. Secret mount у BuildKit не записується. - Розділяйте збірку та runtime багатоетапною збіркою. Компілятори, менеджери пакетів і шели належать етапу збірки й більше нікуди.
- Запускайте від імені не-root користувача й типово скидайте capabilities. Задайте користувача в образі, щоб деплой не зміг тихо повернутися до root.
- Ніколи не запускайте privileged, а монтування хоста зробіть точкою рев’ю. І те, і те іноді потрібне і завжди варте свідомого рішення.
- Вимагайте автентифікацію в реєстрі та заморожуйте релізні теги. Анонімне завантаження є витоком даних. Анонімний запис є компрометацією ланцюга постачання.
- Прив’язуйте опубліковані порти до loopback, якщо сервіс справді не публічний. Публічні ставте за проксі, який контролюєте ви.
- Ніколи не виставляйте Docker-демон на TCP-порт без взаємного TLS. Якщо контейнеру потрібен сокет, поставте перед ним скоуплений проксі.
- Перевіряйте підписи образів під час завантаження. Походження є єдиним захистом від бази, яка ніколи не була легітимною.
CIS Docker Benchmark покриває ту саму територію значно детальніше, і його варто запускати як аудит. Сприймайте список вище як порядок виправлень, а бенчмарк як запис того, що ви виправили.
Як навчати розробників безпеці контейнерів?
Section titled “Як навчати розробників безпеці контейнерів?”Більшість матеріалів про контейнери є чеклістами, а чеклісти вчать відповідності, а не судженню. Розробник може виконати кожне правило вище і все одно дописати --privileged о шостій вечора, щоб збірка пройшла, бо ніщо в чеклісті не показало йому, що саме цей прапорець видає.
У роботу переноситься проходження атаки. Розробник, який зчитав дійсний токен із історії шарів публічного образу, після цього пише інший Dockerfile. Той, хто забрав хост через неавтентифікований демон, додає конфігурацію TLS без нагадувань.
Саме так побудовані наші вправи з безпеки хмари та контейнерів. Кожна спершу ставить вас на бік зловмисника, а потім вимагає випустити виправлення й довести, що атака більше не працює, на інфраструктурі, яка поводиться як продакшн. Каталог з безпеки Git і репозиторіїв покриває сусідню поверхню, де ті самі облікові дані зазвичай витікають першими.
Командам, які володіють сервісами всередині цих контейнерів, зазвичай потрібен і рівень API. Наш посібник з практик безпеки API розбирає збої авторизації, до яких зловмисник дістається вже всередині мережі, а огляд платформ навчання безпечного коду порівнює їх за глибиною покриття цієї території.
Часті запитання
Section titled “Часті запитання”Чи є контейнер межею безпеки?
Не за замовчуванням. Контейнер є набором просторів імен ядра та cgroups, і деплой може вимкнути майже всі з них так, що нічого не виглядатиме зламаним. Межею він стає тоді, коли працює від не-root користувача, скидає capabilities, обходиться без privileged і монтувань хоста, а ядро хоста лишається пропатченим.
Чи прибирає видалення секрету в Dockerfile його з образу?
Ні. Шари образу доступні лише для додавання, тож RUN, який видаляє файл, додає новий шар із записом про видалення, поки попередній шар досі тримає сам файл. Будь-хто з образом прочитає його через docker history або розпакувавши шар.
Вважайте скомпрометованими будь-які облікові дані, що потрапили у збірку, і змініть їх до того, як правити Dockerfile.
Чим безпека контейнерів відрізняється від безпеки хмари?
Безпека контейнерів є підмножиною. Вона покриває образ, реєстр, середовище виконання й мережу контейнерів, тоді як безпека хмари покриває ще керування ідентичностями та доступом, конфігурацію сховищ, мережеві межі на рівні провайдера й control plane.
Рівень контейнерів є тим місцем, куди падає більшість помилок, за які відповідають розробники, тому з нього зазвичай і варто починати навчання.
Що робити спершу, сканувати образи чи міняти базові?
Спершу сканувати, бо не можна пріоритезувати те, що не виміряно, а скан покаже, чи критичні знахідки у ваших залежностях, чи в шарі під ними. Потім закріпіть базу за digest, щоб результат лишався правдивим, бо плаваючий тег здатен повернути знахідку, яку ви вже закрили.
Чи менш безпечний Docker за Kubernetes?
Питання зазвичай спрямоване не туди. Обидва запускають ті самі образи з тим самим ризиком базового шару, і обидва дозволяють деплою просити привілеї, які воркеру не потрібні. Kubernetes додає інструменти політик, які дозволяють блокувати такі запити централізовано, і додає власну поверхню атаки в оркестраторі.
Почніть з одного образу
Section titled “Почніть з одного образу”Запустіть docker history на образі, який ви розгортаєте найчастіше, і прочитайте кроки збірки. Якщо хоч один із них передавав облікові дані як build-аргумент, ви щойно знайшли ту саму помилку, з якої починається ця стаття, і ці облікові дані досі дійсні.
Далі дайте розробникам на чому потренуватися. Наші вправи з безпеки контейнерів безкоштовні, а про впровадження в інженерній команді можна поговорити з нами.