
Контейнерные среды меняются быстро: новые образы разворачиваются автоматически, конфигурации обновляются в 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..." }
Ниже для удобства привожу «шпаргалку» с описанием полей и их типами.
Поле |
Тип |
Описание |
Интерес для ИБ |
|
string |
Имя контейнера |
Позволяет быстро определить, какой сервис связан с событием безопасности. Упрощает расследование подозрительной сетевой активности и анализ инцидентов. |
|
string |
Имя образа без тега |
Используется для контроля происхождения образов и инвентаризации контейнерной среды. Появление образов из неутвержденных репозиториев может указывать на нарушение процессов поставки ПО. |
|
string |
Тег образа. latest, если не задан |
Помогает отслеживать версии приложений и сопоставлять их с известными уязвимостями. Использование latest усложняет аудит и воспроизводимость развертываний. |
|
string |
Источник образа (docker.io, local, свой реестр) |
Позволяет контролировать использование доверенных реестров. Образы из внешних или неавторизованных источников требуют дополнительной проверки. |
|
bool |
Признак запуска в привилегированном режиме |
Один из наиболее важных признаков риска. Привилегированный контейнер получает расширенный доступ к ресурсам хоста и существенно увеличивает последствия возможной компрометации. |
|
string |
Пользователь внутри контейнера |
Помогает проверить соблюдение принципа минимальных привилегий. Запуск процессов от root повышает риск развития атаки после получения доступа к контейнеру. |
c |
array |
Добавленные Linux-привилегии (capabilities) |
Позволяет выявить контейнеры с расширенными системными возможностями. Особое внимание стоит уделять |
|
array |
Отозванные capabilities |
Показывает, какие возможности были дополнительно ограничены. Наличие |
|
string |
SHA256-дайджест манифеста. |
Позволяет однозначно идентифицировать конкретную версию образа. Полезен для контроля целостности и выявления неподписанных или неучтенных артефактов. |
Шаг 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"
Например, запрос по привилегированным контейнерам позволяет быстро выявить сервисы с повышенными правами на хосте. Поиск образов не из корпоративного реестра помогает контролировать происхождение контейнеров и обнаруживать отклонения от вашей принятой политики.
Рекомендации по безопасности
На основе данных инвентаризации вы можете сразу улучшить конфигурацию контейнеров. Рассмотрим снова на примере небольшой «шпаргалки».
Проблема |
Риск |
Рекомендация |
|
Контейнер имеет полный доступ к хосту |
Убрать |
|
Эскалация при побеге из контейнера |
Добавить |
|
Непредсказуемые обновления, нет проверки целостности |
Использовать фиксированные теги или digest-ссылки |
|
Образ создан вручную, нет аудит-следа |
Пушить все образы в корпоративный реестр со сканированием |
|
Возможность изменить сетевые настройки хоста |
Добавить |
Эти проверки не заменяют анализ образов на этапе 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%, пока лот не ушел другому.