Контейнерные среды меняются быстро: новые образы разворачиваются автоматически, конфигурации обновляются в CI/CD-пайплайнах. Без непрерывной инвентаризации вы не знаете, что именно запущено прямо сейчас: какой образ, с каким дайджестом, из какого реестра, с какими привилегиями. 

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

Автоматическая инвентаризация позволяет превратить эту информацию в события мониторинга и получать уведомления сразу же после появления, например, «рискованных» конфигураций.

На связи Андрей, руководитель направления безопасности облака в Selectel. Ранее я рассказывал про сам инструмент Wazuh и его функциональность, а в этой статье покажу пример использования механизма wodle command на практической задаче.

Wodle (сокращение от Wazuh module) — это модуль, который расширяет возможности агента. Он предназначен для выполнения целого ряда задач от сбора данных из внешних источников до запуска сканеров безопасности или выполнения пользовательских сценариев.

Проще говоря, это легковесный «плагин» к агенту Wazuh. Он работает в фоновом режиме, выполняет свою задачу, а результаты в виде логов отправляет менеджеру для дальнейшего анализа

Вкратце об архитектуре решения

Схема потока данных выглядит так: скрипт запускается агентом Wazuh по расписанию, обращается к Docker CLI и выводит JSON в stdout. Wazuh перехватывает вывод и отправляет его менеджеру в виде отдельных событий. 

Схема потока данных.
Схема потока данных.

Security Center

Рассказываем о лучших практиках и средствах ИБ, требованиях и изменениях в законодательстве.

Исследовать →

Шаг 1. Скрипт инвентаризации

Перейдем к самому скрипту. Он совместим с любым окружением Linux. Сохраните его по пути /var/ossec/custom-scripts/container_inventory.sh и выдайте права на исполнение.

#!/bin/sh

# Проверяем наличие docker и доступность демона
command -v docker >/dev/null 2>&1 || exit 0
docker info >/dev/null 2>&1 || exit 0

docker ps --format "{{.Names}}|{{.ID}}" | while IFS="|" read -r name cid; do
  image=$(docker inspect --format '{{.Config.Image}}' "$cid" 2>/dev/null)
  [ -z "$image" ] && continue

  # Разбиваем repo:tag
  lastpart="${image##*/}"
  if echo "$lastpart" | grep -q ':'; then
    tag="${lastpart##*:}"
    repo="${image%:${tag}}"
  else
    tag="latest"
    repo="$image"
  fi

  # Определяем реестр по первому сегменту пути
  first="${repo%%/*}"
  if [ "$repo" = "$first" ]; then
    registry="docker.io"
  elif echo "$first" | grep -qE "\\.|:"; then
    registry="$first"
  else
    registry="docker.io"
  fi

  # Digest
  digests=$(docker image inspect --format "{{json .RepoDigests}}" "$image" 2>/dev/null)
  if [ -z "$digests" ] || [ "$digests" = "[]" ]; then
    digest="N/A"
    [ "$registry" = "docker.io" ] && registry="local"
  else
    digest=$(docker image inspect --format "{{index .RepoDigests 0}}" "$image" 2>/dev/null)
  fi

  # Привилегии, пользователь, capabilities
  ainfo=$(docker inspect \
    --format "{{.HostConfig.Privileged}}|{{.Config.User}}|{{json .HostConfig.CapAdd}}|{{json .HostConfig.CapDrop}}" \
    "$cid" 2>/dev/null)
  privileged="${ainfo%%|*}"
  r="${ainfo#*|}"
  user="${r%%|*}"
  r="${r#*|}"
  capadd="${r%%|*}"
  capdrop="${r##*|}"

  [ -z "$user" ]         && user="root"
  [ "$capadd" = "null" ] && capadd="[]"
  [ "$capdrop" = "null" ] && capdrop="[]"

  printf '{"type":"container_inventory","name":"%s","repo":"%s","tag":"%s","registry":"%s","privileged":%s,"user":"%s","cap_add":%s,"cap_drop":%s,"digest":"%s"}\n' \
    "$name" "$repo" "$tag" "$registry" "${privileged:-false}" \
    "$user" "$capadd" "$capdrop" "${digest:-N/A}"
done

exit 0

Установка прав:

chmod 750 /var/ossec/custom-scripts/container_inventory.sh
chown root:wazuh /var/ossec/custom-scripts/container_inventory.sh

Шаг 2. Конфигурация Wazuh-агента (wodle command)

На узле, где установлен Wazuh-агент, откройте файл /var/ossec/etc/ossec.conf и добавьте в секцию <ossec_config>следующий блок конфигурации:


      <!-- ossec.conf агента -->
<ossec_config>

  <wodle name="command">
    <disabled>no</disabled>

    <!-- Тег для фильтрации в дашборде -->
    <tag>container_inventory</tag>

    <command>/var/ossec/custom-scripts/container_inventory.sh</command>

    <!-- Интервал: каждые 6 часов -->
    <interval>6h</interval>

    <!-- ignore_output=no: передаём stdout в Wazuh -->
    <ignore_output>no</ignore_output>

    <!-- Таймаут: 60 секунд -->
    <timeout>60</timeout>

    <!-- Запускать сразу при старте агента -->
    <run_on_start>yes</run_on_start>
  </wodle>

</ossec_config>

Здесь механизм wodle command запускает произвольные команды по расписанию и перехватывает их вывод как события Wazuh.

После изменения конфигурации перезапустите Wazuh-агента:

systemctl restart wazuh-agent
# или
/var/ossec/bin/wazuh-control restart

Структура событий

Прежде чем приступить к настройке правил алертинга важно отметить, что строка вывода скрипта — это отдельное JSON-событие. Рассмотрим пример такого события:

{
  "type":       "container_inventory",
  "name":       "nginx-prod",
  "repo":       "nginx",
  "tag":        "1.25-alpine",
  "registry":   "docker.io",
  "privileged": false,
  "user":       "nginx",
  "cap_add":    [],
  "cap_drop":   ["ALL"],
  "digest":     "nginx@sha256:a49e14a8..."
}

Ниже для удобства привожу «шпаргалку» с описанием полей и их типами.

Поле

Тип

Описание

Интерес для ИБ

name

string

Имя контейнера

Позволяет быстро определить, какой сервис связан с событием безопасности. Упрощает расследование подозрительной сетевой активности и анализ инцидентов.

repo

string

Имя образа без тега

Используется для контроля происхождения образов и инвентаризации контейнерной среды. Появление образов из неутвержденных репозиториев может указывать на нарушение процессов поставки ПО.

tag

string

Тег образа. latest, если не задан

Помогает отслеживать версии приложений и сопоставлять их с известными уязвимостями. Использование latest усложняет аудит и воспроизводимость развертываний.

registry

string

Источник образа (docker.io, local, свой реестр)

Позволяет контролировать использование доверенных реестров. Образы из внешних или неавторизованных источников требуют дополнительной проверки.

privileged

bool

Признак запуска в привилегированном режиме

Один из наиболее важных признаков риска. Привилегированный контейнер получает расширенный доступ к ресурсам хоста и существенно увеличивает последствия возможной компрометации.

user

string

Пользователь внутри контейнера

Помогает проверить соблюдение принципа минимальных привилегий. Запуск процессов от root повышает риск развития атаки после получения доступа к контейнеру.

cap_add

array

Добавленные Linux-привилегии (capabilities) 

Позволяет выявить контейнеры с расширенными системными возможностями. Особое внимание стоит уделять CAP_SYS_ADMINCAP_SYS_PTRACE и другим привилегированным capabilities.

cap_drop

array

Отозванные capabilities

Показывает, какие возможности были дополнительно ограничены. Наличие ALL обычно свидетельствует о более строгой конфигурации безопасности контейнера.

digest

string

SHA256-дайджест манифеста. N/A — для локальных образов

Позволяет однозначно идентифицировать конкретную версию образа. Полезен для контроля целостности и выявления неподписанных или неучтенных артефактов.

Шаг 3. Настройка правил алертинга в Wazuh

Добавьте правила в /var/ossec/etc/rules/local_rules.xml на менеджере. Wazuh декодирует JSON-поля автоматически через динамический декодер.

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

<group name="container_inventory,">

  <!-- Базовое правило: принимаем все события инвентаризации -->
  <rule id="100200" level="0">
    <decoded_as>json</decoded_as>
    <field name="type">^container_inventory$</field>
    <description>Container inventory event</description>
  </rule>

  <!-- Привилегированный контейнер — высокий уровень -->
  <rule id="100201" level="12">
    <if_sid>100200</if_sid>
    <field name="privileged">^true$</field>
    <description>Privileged container detected: $(name)</description>
    <group>docker_security,privileged_container</group>
  </rule>

  <!-- Контейнер с расширенными capabilities -->
  <rule id="100202" level="10">
    <if_sid>100200</if_sid>
    <field name="cap_add">^\["[^"]+"\]</field>
    <description>Container with extra capabilities: $(name) cap_add=$(cap_add)</description>
    <group>docker_security,capability_escalation</group>
  </rule>

  <!-- Образ с тегом latest -->
  <rule id="100203" level="5">
    <if_sid>100200</if_sid>
    <field name="tag">^latest$</field>
    <description>Container running 'latest' tag: $(name) / $(repo)</description>
    <group>docker_inventory,untagged_image</group>
  </rule>

  <!-- Контейнер запущен от root -->
  <rule id="100204" level="7">
    <if_sid>100200</if_sid>
    <field name="user">^root$</field>
    <description>Container process running as root: $(name)</description>
    <group>docker_security,root_container</group>
  </rule>

  <!-- Локальный образ (не из реестра) -->
  <rule id="100205" level="8">
    <if_sid>100200</if_sid>
    <field name="registry">^local$</field>
    <description>Container using local image (no registry): $(name) / $(repo)</description>
    <group>docker_security,local_image</group>
  </rule>

</group>

Шаг 4. Поиск событий в Wazuh Dashboards

После настройки правил полезно проверить, какие данные поступают в индекс и как они выглядят в поиске. Проще всего сделать это через Discover в Wazuh Dashboards. Ниже привожу несколько запросов, которые помогают быстро выявить потенциально рискованные контейнеры и проверить качество инвентаризации.

# Все события инвентаризации
data.type: container_inventory

# Только привилегированные контейнеры
data.type: container_inventory AND data.privileged: true

# Контейнеры с добавленными capabilities
data.type: container_inventory AND data.cap_add: [* TO *]

# Образы не из корпоративного реестра
data.type: container_inventory AND NOT data.registry: "registry.company.internal"

# Контейнеры с тегом latest, запущенные от root
data.type: container_inventory AND data.tag: "latest" AND data.user: "root"

Например, запрос по привилегированным контейнерам позволяет быстро выявить сервисы с повышенными правами на хосте. Поиск образов не из корпоративного реестра помогает контролировать происхождение контейнеров и обнаруживать отклонения от вашей принятой политики.

Рекомендации по безопасности

На основе данных инвентаризации вы можете сразу улучшить конфигурацию контейнеров. Рассмотрим снова на примере небольшой «шпаргалки».

Проблема

Риск

Рекомендация

privileged: true

Контейнер имеет полный доступ к хосту

Убрать --privileged, заменить на конкретные capabilities

user: root

Эскалация при побеге из контейнера

Добавить USER в Dockerfile или --user при запуске

tag: latest

Непредсказуемые обновления, нет проверки целостности

Использовать фиксированные теги или digest-ссылки

registry: local

Образ создан вручную, нет аудит-следа

Пушить все образы в корпоративный реестр со сканированием

cap_add: [NET_ADMIN]

Возможность изменить сетевые настройки хоста

Добавить cap_drop: ALL + только необходимые capabilities

Эти проверки не заменяют анализ образов на этапе CI/CD, но позволяют быстро выявить отклонения в уже работающей инфраструктуре. На практике наиболее полезными обычно оказываются контроль привилегированных контейнеров, запуска от root и использования неподконтрольных образов.

Расширение скрипта

Базовый вариант собирает только сведения об образе и привилегиях контейнера. При необходимости вы можете дополнить его данными о сетевых настройках, точках монтирования и окружении процесса. Предлагайте в комментариях, какие пути расширения видите — будет интересно обсудить.

Порты и сетевые настройки

Информация о проброшенных портах и сетевом режиме помогает выявлять сервисы с неожиданной сетевой доступностью или использованием host-сети.

ports=$(docker inspect --format \
  '{{range $p, $conf := .NetworkSettings.Ports}}{{$p}}{{end}}' \
  "$cid" 2>/dev/null | tr '\n' ',')
network_mode=$(docker inspect --format '{{.HostConfig.NetworkMode}}' "$cid")

Смонтированные volumes

Данные о монтированиях полезны для поиска контейнеров с доступом к чувствительным каталогам хоста.

mounts=$(docker inspect --format '{{json .Mounts}}' "$cid")

Переменные среды (с маскированием секретов)

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

env_list=$(docker inspect --format '{{json .Config.Env}}' "$cid" | \
  sed 's/"[^"]*PASSWORD[^"]*"/"REDACTED"/gi')

Внимание! Не логируйте переменные среды без маскирования — они часто содержат токены, пароли и ключи API, которые могут попасть в индекс Wazuh и стать видимыми в дашборде.

Таймаут на большом количестве контейнеров

При 50+ контейнерах вызовы docker inspect могут занять больше 60 секунд. Увеличьте значение <timeout> в конфиге или оптимизируйте скрипт, объединив несколько inspect-вызовов в один.

Что в итоге

С помощью wodle command можно быстро расширить возможности Wazuh и собирать данные, которые отсутствуют в стандартной поставке агента. В рассмотренном примере мы настроили инвентаризацию контейнеров и превратили сведения об образах, привилегиях и настройках безопасности в события мониторинга и алерты.

Такой подход не заменяет проверку образов на этапе CI/CD и сканирование уязвимостей до развертывания, но помогает контролировать фактическое состояние контейнерной среды. Это особенно полезно в динамичной инфраструктуре, где контейнеры регулярно пересоздаются, обновляются или запускаются из разных пайплайнов.

При необходимости аналогичный механизм можно адаптировать для Podman, CRI-O или других контейнерных рантаймов, а сам скрипт дополнить сбором сетевых настроек, томов и других параметров, важных для вашей модели угроз. Если вы используете Wazuh для мониторинга контейнеров или собираете похожую инвентаризацию другими способами — расскажите в комментариях, какие проверки оказались наиболее полезными в вашей инфраструктуре.

Снижаем цены на выделенные серверы в реальном времени

Успейте арендовать со скидкой до 35%, пока лот не ушел другому.

Подробнее →

Комментарии (0)