Как инвентаризировать контейнеры с помощью Wazuh-агента - Академия Selectel

Как инвентаризировать контейнеры с помощью Wazuh-агента

Как настроить автоматический сбор данных о работающих Docker-контейнерах — образах, привилегиях и capabilities — и передать их в Wazuh для мониторинга и алертинга.

Зачем это нужно

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

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

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

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

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

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

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

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

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

Шаг 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..."
}

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

ПолеТипОписаниеИнтерес для ИБ
namestringИмя контейнераПозволяет быстро определить, какой сервис связан с событием безопасности. Упрощает расследование подозрительной сетевой активности и анализ инцидентов.
repostringИмя образа без тегаИспользуется для контроля происхождения образов и инвентаризации контейнерной среды. Появление образов из неутвержденных репозиториев может указывать на нарушение процессов поставки ПО.
tagstringТег образа. latest, если не заданПомогает отслеживать версии приложений и сопоставлять их с известными уязвимостями. Использование latest усложняет аудит и воспроизводимость развертываний.
registrystringИсточник образа (docker.io, local, свой реестр)Позволяет контролировать использование доверенных реестров. Образы из внешних или неавторизованных источников требуют дополнительной проверки.
privilegedboolПризнак запуска в привилегированном режимеОдин из наиболее важных признаков риска. Привилегированный контейнер получает расширенный доступ к ресурсам хоста и существенно увеличивает последствия возможной компрометации.
userstringПользователь внутри контейнераПомогает проверить соблюдение принципа минимальных привилегий. Запуск процессов от root повышает риск развития атаки после получения доступа к контейнеру.
cap_addarrayДобавленные Linux-привилегии (capabilities) Позволяет выявить контейнеры с расширенными системными возможностями. Особое внимание стоит уделять CAP_SYS_ADMIN, CAP_SYS_PTRACE и другим привилегированным capabilities.
cap_droparrayОтозванные capabilitiesПоказывает, какие возможности были дополнительно ограничены. Наличие ALL обычно свидетельствует о более строгой конфигурации безопасности контейнера.
digeststringSHA256-дайджест манифеста. 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 или других контейнерных рантаймов, а сам скрипт дополнить сбором сетевых настроек, томов и других параметров, важных для вашей модели угроз.