Цепочка поставки: сканеры, подписи, SBOM
Посчитал, что вы на самом деле берёте с полки. В чистом образе python установлен 1 пакет. Одна команда установки самого обычного веб-фреймворка - и пакетов становится 8: семь чужих библиотек приехали как зависимости, и ни одну из них никто не выбирал. А сам базовый образ debian приносит 88 системных пакетов и 5242 файла ещё до вашего кода.
Стержень: в проде работает весь конус зависимостей вместе с вашим кодом, и управлять надо им целиком - версиями, обновлением и проверкой.
// Формулировки: «как контролировать зависимости?», «зачем сканировать образы?», «что такое состав артефакта?»
Что именно приезжает в прод
Три источника, и все три надо считать. Первый - базовый образ, тот, с которого начинается сборка: у меня debian принёс 88 пакетов и 5242 файла, из них 8 программ, работающих с правами root. Тот же alpine - 14 пакетов и 89 файлов, программ с правами root ноль. Разница в поверхности атаки на два порядка, и выбирается она одной строкой в файле сборки.
Второй - зависимости приложения: один установленный фреймворк принёс семь транзитивных зависимостей. Каждая из них имеет своего автора, свою историю обновлений и свой шанс однажды оказаться заброшенной или перехваченной.
// Третий - инструменты сборки, которые не должны доезжать до прода вовсе. Компилятор, менеджер пакетов, отладочные утилиты в готовом образе - это и лишний вес, и готовый инструментарий для того, кто попал внутрь. Лечится многостадийной сборкой: собрали в тяжёлой стадии, скопировали результат в лёгкую.
- транзитивная зависимость
- библиотека, которую притащила другая библиотека; её никто не выбирал
- поверхность атаки
- всё, что есть в образе и может быть использовано попавшим внутрь
Закрепление версий и обновление
Плавающая версия означает, что сегодняшняя сборка и завтрашняя - разные, а разобраться, что именно изменилось, невозможно. Поэтому версии закрепляют: приложения - файлом со всеми версиями зависимостей, включая транзитивные; базовый образ - отпечатком содержимого, а не тегом. Тег можно перевесить на другой образ, я это показывал; отпечаток нельзя.
Но закрепление создаёт вторую проблему: закреплённое устаревает и копит известные уязвимости. Значит, нужен и обратный процесс - регулярное обновление закреплений, желательно автоматическое, с прогоном тестов. Схема, которая работает: робот раз в неделю поднимает версии и открывает изменение, тесты проходят, человек смотрит и вливает.
// Ошибка на этом месте типовая: закрепили и забыли. Через год оказывается, что обновление невозможно, потому что версии разъехались на несколько крупных выпусков, в которых менялась совместимость, и всё ломается разом. Обновляться понемногу и часто дешевле, чем один раз и целиком.
- закрепление версий
- точные версии всех зависимостей, включая транзитивные
- обновление закреплений
- регулярный автоматический подъём версий с прогоном тестов
Сканирование, состав и подпись
Сканер сверяет список того, что стоит в образе, с базой известных уязвимостей. Ставят его в двух местах: в сборку, чтобы не выпускать новое с известными дырами, и по расписанию на то, что уже работает, - потому что уязвимость находят ПОСЛЕ выпуска, и вчера чистый образ сегодня уже нет.
Состав артефакта - это машиночитаемый список всего, что вошло в сборку, с версиями. Нужен он ровно для одного вопроса, который однажды прилетает: «в какой из наших сборок есть эта библиотека нужной версии». Без списка ответ ищется руками неделю, со списком - запросом за минуту.
// Подпись артефакта закрывает третью дыру: она подтверждает, что образ собран вашим сборочным конвейером, а не подсунут по дороге. Проверку подписи ставят на стороне, которая запускает: не подписан нашим ключом - не запускаем. Это же отсекает и «случайно выкатили образ, собранный на ноутбуке».
- сканирование
- сверка состава образа с базой известных уязвимостей
- состав артефакта
- машиночитаемый список всего, что вошло в сборку, с версиями
- подпись артефакта
- подтверждение, что образ собран вашим конвейером
Как отвечать: «Как контролировать то, что приезжает в прод вместе с кодом?»
Считать всё, что приезжает, а приезжает много. Я специально мерил: чистый образ python содержит один пакет, а после установки одного обычного фреймворка их восемь - семь транзитивных зависимостей никто не выбирал. Базовый образ debian сверх этого приносит 88 системных пакетов и больше пяти тысяч файлов, включая восемь программ, работающих с правами root; у alpine это 14 пакетов, 89 файлов и ноль таких программ. То есть выбор базы меняет поверхность атаки на два порядка одной строкой. Дальше по порядку: закрепляю версии приложения файлом со всеми транзитивными зависимостями, а базовый образ - отпечатком содержимого вместо тега, потому что тег можно перевесить. Ставлю автоматическое обновление закреплений раз в неделю с прогоном тестов, иначе через год обновиться будет невозможно. И держу сканирование в двух местах: в сборке и по расписанию на то, что уже работает, потому что уязвимость находят после выпуска.
Ответ показывает масштаб проблемы числами и даёт четыре конкретных механизма. Замечание про сканирование уже работающего отличает практика от того, кто поставил сканер в сборку и успокоился.
На чём валятся
- −Берут полновесный базовый образ по привычке и тащат в прод тысячи лишних файлов.
- −Закрепляют версии и не обновляют их: через год обновление ломает всё разом.
- −Ссылаются на базовый образ тегом, который можно перевесить, а не отпечатком.
- −Сканируют только при сборке и не сканируют то, что уже работает.
- −Оставляют в готовом образе компилятор и пакетный менеджер.
Проверьте себя
Пять вопросов из банка по этой подтеме. Всего их 6, остальные разбираются в тренажёре.
- Зачем сканировать собранные образы, если код прошёл ревью?A)В базовом слое и зависимостях есть уязвимостиB)Сканер проверяет корректность Dockerfile и порядок инструкцийC)Так измеряется размер образа и находятся лишние слоиD)Сканер подтверждает, что образ собран из нужной ветки
показать ответ и разбор
+A)В базовом слое и зависимостях есть уязвимости// разбор: Свой код — малая часть образа: остальное это базовый дистрибутив, системные библиотеки и десятки транзитивных зависимостей, где регулярно находят уязвимости. Сканер сверяет установленные пакеты с базами известных проблем и показывает, что чинится обновлением, а что придётся закрывать иначе. Проверять нужно и на сборке, и периодически в реестре: уязвимость может стать известной уже после публикации образа.
- Сканер показывает критическую уязвимость в системной библиотеке базового образа. Приложение её не использует. Что делать?A)Добавить исключение в сканер: раз не используется, риска нетB)Заменить базовый образ на другой дистрибутивC)Пересобрать на свежем базовом образе и зафиксировать новый дайджестD)Удалить библиотеку из образа отдельной инструкцией в конце сборки
показать ответ и разбор
+C)Пересобрать на свежем базовом образе и зафиксировать новый дайджест// разбор: Обычный путь — обновление: свежий базовый образ приносит исправленный пакет, сборка перезапускается, дайджест пинится заново. «Мы это не вызываем» — слабый аргумент: зависимость может дёрнуть библиотеку неявно, а проверять это на каждом релизе никто не будет. Исключения оформляют осознанно и со сроком, когда обновления ещё нет, а сервис нужен в проде.
- Как убедиться, что в прод едет именно тот образ, который собрал наш пайплайн?A)Проверять имя и тег образа при выкаткеB)Ограничить доступ к реестру только адресами кластераC)Хранить историю сборок и сверять её вручную при инцидентахD)Подписывать в пайплайне, проверять при допуске
показать ответ и разбор
+D)Подписывать в пайплайне, проверять при допуске// разбор: Тег переставляется, а доступ к реестру можно получить. Подпись привязывает образ к конкретной сборке: пайплайн подписывает дайджест, а контроллер допуска в кластере пропускает только подписанные доверенным ключом. Рядом обычно держат состав образа (SBOM) — по нему за минуту видно, задет ли прод очередной уязвимостью в библиотеке, без пересканирования всего парка.
- Сборка тянет зависимости по диапазону версий (^1.2). Чем это рискованно для supply chain?A)Диапазон версий замедляет установку зависимостейB)Проблема лишь в возможной несовместимости APIC)Подтянется чужая версия; спасает lock-файлD)Диапазон мешает кэшировать зависимости в CI
показать ответ и разбор
+C)Подтянется чужая версия; спасает lock-файл// разбор: Плавающий диапазон версий (^1.2, latest) означает, что сборка может подтянуть новую версию зависимости — в том числе скомпрометированную (взломанный мейнтейнер, вредоносный релиз). Защита: lock-файл, фиксирующий точные версии и криптографические хеши (package-lock, poetry.lock, go.sum) — сборка воспроизводима, а подмена содержимого пакета ломает проверку хеша. Обновления зависимостей делают осознанно и ревьюят.
- Вышла громкая уязвимость в популярной библиотеке. Как быстро понять, какие ваши сборки затронуты?A)Пересобрать вообще всё на всякий случайB)По SBOM: где именно лежит эта библиотека и версияC)Дождаться, пока сканер образов сам поднимет тревогуD)Спросить разработчиков по памяти, кто её использовал
показать ответ и разбор
+B)По SBOM: где именно лежит эта библиотека и версия// разбор: SBOM (Software Bill of Materials) — машиночитаемый список всех компонентов и версий в артефакте, включая транзитивные зависимости. Когда выходит новая CVE, по SBOM всех сборок за минуты отвечают на вопрос «затронуты ли мы и где именно» — вместо ручного перебора и слепой пересборки. Поэтому SBOM генерируют в пайплайне и хранят вместе с артефактом; на нём же строят непрерывное сопоставление с базами уязвимостей.
дальше
Теорию прочитали. Навык ставится повторением
В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.