сеньорчикОткрыть в Telegram
← вся теориятеория к собесу · Безопасность инфраструктуры

Харденинг контейнеров и гейты в CI

Укрепление и проверки в конвейере

Собрал минимальный профиль запуска и проверил каждую настройку отдельно. От обычного пользователя вместо root - смена владельца чужого файла даёт отказ. С отобранными разрешениями - тот же отказ. С файловой системой только на чтение - запись в системный файл даёт «Read-only file system», а временные файлы уезжают в отдельный кусок памяти. Каждая строчка проверяема за десять секунд.

Стержень: укрепление - это короткий список настроек по умолчанию плюс проверка, которая не даёт их обойти.

// Формулировки: «как укрепить контейнер?», «какие проверки ставить в конвейер?», «что делать с легаси, которое требует root?»

Минимальный профиль

Список короткий, и он одинаковый почти для всех сервисов. Запуск от обычного пользователя. Отобрать все разрешения root и вернуть только нужные - большинству сервисов не нужно ни одного. Запретить повышение прав, потому что в образе остаются программы, работающие от владельца файла: в базовом debian их восемь, в alpine ноль. Файловая система только на чтение, а под временные файлы - отдельный кусок памяти. Пределы по памяти и процессору, чтобы один сервис не съел машину.

Отдельно: не пробрасывать внутрь сокет демона контейнеров - это его интерфейс управления, работающий без всякого пароля, - и не ставить полный набор привилегий. Я показывал раньше, что это даёт: с сокетом обычный контейнер перечисляет и запускает любые другие контейнеры на машине, то есть равен root на машине.

// Всё это ставится один раз в шаблон и применяется ко всем новым сервисам. Проверять полезно на живом стенде, а не по документации: настройка, которая «должна работать», иногда молча не применяется - например, потому что образ ожидает root и падает при старте от другого пользователя.

минимальный профиль
короткий набор настроек запуска, применяемый ко всем сервисам по умолчанию
запрет повышения прав
программы с правами владельца перестают поднимать привилегии

Что делать с тем, что требует root

Всегда находится сервис, который «работает только от root». Разбирается это по порядку. Сначала выясняем, ЧТО именно ему нужно: обычно одно конкретное разрешение вроде открытия сетевого порта с маленьким номером или доступа к устройству. Тогда вместо root выдают именно его.

Второй случай - права на файлы: приложение пишет в каталог, принадлежащий root. Лечится сменой владельца каталога при сборке образа, а не выдачей прав в рантайме.

// Третий случай - приложение действительно требует полного root, и переписать его нельзя. Тогда оно изолируется отдельно: своя машина или свой узел, минимум соседей, отдельная сеть, усиленный мониторинг. И это фиксируется как осознанный риск с ответственным, а не тихо остаётся исключением, о котором через год никто не помнит.

точечное разрешение
выдать одно нужное право вместо полного root
осознанный риск
записанное исключение с ответственным, а не забытая настройка

Проверки в конвейере, которые работают

Конвейер - это набор шагов, которые проходит каждое изменение кода перед выкаткой, и проверки живут именно там. Работает проверка только если она блокирует, а не советует. Проверка, которая печатает предупреждение, через месяц перестаёт читаться. Но блокирующую проверку нельзя включать разом на весь код: она остановит всё, и её отключат в первый же горячий день.

Рабочий порядок такой. Сначала включить в режиме наблюдения и собрать список нарушений - обычно он неприятно длинный. Потом зафиксировать список как принятый долг и блокировать только НОВЫЕ нарушения: старое живёт, новое не проходит. Дальше разбирать долг по частям в спокойном темпе.

// Набор проверок, который окупается почти всегда: секреты в коде, известные уязвимости в зависимостях, запуск от root в описании сервиса, плавающие теги образов вместо отпечатков, открытые наружу порты. Каждая из них ловит целый класс ошибок, а не единичный случай. И у каждой должен быть понятный обход с обоснованием - без него люди начнут обходить сам конвейер.

режим наблюдения
проверка считает нарушения, но не блокирует; нужен для первого замера
принятый долг
зафиксированный список старых нарушений; блокируются только новые

Как отвечать: «Как укрепить запуск сервиса в контейнере?»

Есть короткий список, который применяется по умолчанию ко всем, и я его проверял на стенде построчно. Запуск от обычного пользователя вместо root: смена владельца чужого файла сразу становится невозможной. Отобрать все разрешения root и вернуть только нужные - большинству сервисов не нужно ни одного из четырнадцати, которые выдаются по умолчанию. Запретить повышение прав, потому что в образе остаются программы, работающие от владельца файла: в базовом debian я насчитал восемь таких, в alpine ноль. Файловая система только на чтение, а под временные файлы отдельный кусок памяти. Пределы по памяти и процессору. И отдельно: не пробрасывать внутрь сокет демона контейнеров, потому что это равносильно выдаче прав root на всю машину. Если какой-то сервис не запускается с этим профилем, разбираюсь, какое конкретно разрешение ему нужно, и выдаю только его.

Ответ даёт готовый список с проверенным эффектом каждой настройки и заканчивается тем, как поступать с исключениями. Это применимо в понедельник.

На чём валятся

  • Оставляют запуск от root по умолчанию, потому что «так заработало».
  • Пробрасывают сокет демона контейнеров ради удобства сборки.
  • Включают блокирующие проверки разом на весь код и получают их отключение в первый горячий день.
  • Ставят проверки в режиме советов и через месяц их никто не читает.
  • Оставляют исключение «этому сервису нужен root» без записи и ответственного.

Проверьте себя

Пять вопросов из банка по этой подтеме. Всего их 6, остальные разбираются в тренажёре.

  1. #dvo_hardening1 / 5
    Почему в образе просят прописать непривилегированного пользователя?
    A)Root внутри контейнера — это root на ядре хоста при побеге
    B)От root процессы работают медленнее из-за проверок ядра
    C)Иначе логи контейнера будут писаться от чужого владельца
    D)Root не даёт контейнеру слушать порты выше тысячи
    показать ответ и разбор
    +A)Root внутри контейнера — это root на ядре хоста при побеге

    // разбор: Пользователи в контейнере и на хосте — это одни и те же числовые идентификаторы, если не включён user namespace. Значит, root в контейнере равен root на узле, и любая дыра в рантайме или неудачное монтирование превращается в полный доступ к машине. Плюс от root проще навредить самому себе: правки в системных каталогах образа, файлы с чужими правами на смонтированных томах.

  2. #dvo_hardening2 / 5
    Какой набор ограничений ставят поду в кластере по умолчанию?
    A)Привилегированный режим с полным набором прав для совместимости
    B)Корневая ФС на чтение, минимум прав и capabilities
    C)Доступ к сети хоста и монтирование его файловой системы
    D)Отключение проб и ограничений ресурсов ради скорости старта
    показать ответ и разбор
    +B)Корневая ФС на чтение, минимум прав и capabilities

    // разбор: Базовый профиль такой: контейнер не root, корневая файловая система только на чтение (данные — во временный том), запрещено повышение привилегий, набор capabilities урезан до необходимого, включён стандартный профиль seccomp. Всё это описывается в securityContext и проверяется на допуске в кластер, чтобы правила не зависели от аккуратности автора манифеста.

  3. #dvo_hardening3 / 5
    Проверки безопасности в CI встроили, но команда их игнорирует: пайплайн всегда красный. Как чинить процесс?
    A)Отключить проверки и запускать их раз в квартал отдельно
    B)Оставить как есть: разработчики привыкнут к предупреждениям
    C)Блокировать узкий список, остальное — в отчёт
    D)Перенести проверки на стадию после выкатки, чтобы не тормозить релизы
    показать ответ и разбор
    +C)Блокировать узкий список, остальное — в отчёт

    // разбор: Ворота работают, только пока их срабатывание означает «стоп». Практика: блокирует узкий и понятный набор — секрет в коде, критичная уязвимость с готовым исправлением, запуск от root, — а остальное копится в отчёт с ответственным и сроком. Дополнительно фиксируют базовый уровень: новые нарушения блокируют, накопленное чинят планово. Иначе красный цвет теряет смысл и вместе с ним пропадает вся польза.

  4. #dvo_hardening4 / 5
    Образ собирают на полном ubuntu с компилятором, shell и кучей утилит. Чем плоха такая база для прода?
    A)Большая поверхность атаки; берут минимальный/distroless образ
    B)Полный образ медленнее запускает главный процесс
    C)Ubuntu требует платной подписки для прода
    D)На полном образе не работает проброс портов
    показать ответ и разбор
    +A)Большая поверхность атаки; берут минимальный/distroless образ

    // разбор: Толстая база (полный дистрибутив с компилятором, shell, пакетным менеджером, утилитами) — это большая поверхность атаки: каждый лишний пакет несёт свои уязвимости, а shell и инструменты помогают атакующему после пробоя (разведка, скачивание, эскалация). Берут минимальную базу (distroless, alpine, scratch) — только рантайм и приложение, без shell и лишнего. Меньше компонентов — меньше CVE и меньше возможностей для атаки внутри контейнера.

  5. #dvo_hardening5 / 5
    Контейнер работает с дефолтным набором Linux capabilities. Как сузить его права по харденингу?
    A)Запустить контейнер с флагом --privileged для стабильности
    B)Выдать все capabilities, чтобы не ловить отказы в правах
    C)Сбросить все capabilities и добавить обратно лишь нужные
    D)Оставить дефолт: он и так уже безопасен
    показать ответ и разбор
    +C)Сбросить все capabilities и добавить обратно лишь нужные

    // разбор: Linux capabilities дробят права root на отдельные (NET_ADMIN, SYS_TIME и т.д.). По умолчанию контейнеру выдаётся набор, где часть возможностей ему не нужна. Харденинг: drop ALL и добавить обратно только реально необходимые (например, NET_BIND_SERVICE для порта <1024). Плюс no-new-privileges, seccomp-профиль, read-only rootfs, непривилегированный USER. Так поверхность привилегий сжимается до минимума, а --privileged делает ровно противоположное.

дальше

Теорию прочитали. Навык ставится повторением

В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.