Инструмент статического анализа XML-декодеров Wazuh

XML-файлы декодеров Wazuh определяют, как необработанные строки логов преобразуются в структурированные события безопасности. Ошибка в конфигурации декодера - отсутствующий элемент <order>, ссылка на несуществующий родительский декодер или несоответствие количества групп захвата в regex - может привести к тому, что критически важные поля будут молча отброшены из алертов, создавая слепые зоны в вашем SIEM-конвейере. Ручная проверка кода выявляет некоторые из этих проблем, но не масштабируется на сотни файлов декодеров, поставляемых с Wazuh или поддерживаемых вашей организацией.

В этой статье представлен wazuh-decoder-linter - инструмент статического анализа с открытым исходным кодом, который автоматически валидирует XML-файлы декодеров Wazuh. Он проверяет структуру, согласованность regex/order, атрибуты элементов и межфайловые цепочки родительских декодеров. Опционально инструмент интегрируется с Wazuh logtest API для верификации декодеров на работающем экземпляре менеджера. Из этого руководства вы узнаете, как установить инструмент, какие правила валидации он применяет, как использовать его из командной строки и Python-кода, а также как интегрировать его в CI/CD-конвейеры.

Структура XML-файлов декодеров Wazuh

Декодеры Wazuh определяются в XML-файлах, расположенных в /var/ossec/etc/decoders/ (пользовательские) или /var/ossec/ruleset/decoders/ (стандартные). Каждый файл содержит один или несколько элементов <decoder>. Типичный декодер имеет два уровня: родительский декодер, который сопоставляет имя программы, и дочерний декодер, который извлекает поля с помощью регулярного выражения.

<decoder name="example">
  <program_name>^example</program_name>
</decoder>

<decoder name="example">
  <parent>example</parent>
  <regex>User '(\w+)' logged from '(\d+.\d+.\d+.\d+)'</regex>
  <order>user, srcip</order>
</decoder>

Схема декодеров Wazuh поддерживает 16 дочерних элементов внутри <decoder>: parent, prematch, program_name, regex, order, fts, ftscomment, plugin_decoder, accumulate, type, json_null_field, json_array_structure, var, use_own_name, match и description. Каждый элемент имеет определённые ограничения: <regex> требует соответствующего <order>, <plugin_decoder> должен ссылаться на один из пяти известных плагинов (JSON_Decoder, OSSECAlert_Decoder, PF_Decoder, SymantecWS_Decoder, SonicWall_Decoder), а <type> принимает только восемь значений (firewall, ids, web-log, syslog, squid, windows, host-information, ossec).

Понимание этой структуры необходимо для написания корректных декодеров.

Типичные ошибки конфигурации декодеров

Анализ реальных файлов декодеров - включая более 80 файлов из стандартного набора правил Wazuh - выявляет несколько повторяющихся паттернов ошибок:

Regex без order (и наоборот). Наиболее частая ошибка. Элемент <regex> захватывает группы, но элемент <order>, который сопоставляет эти группы с именами полей, отсутствует. Декодер молча отбрасывает захваченные данные. Обратная ситуация - <order> без <regex> - также возникает, когда нет <plugin_decoder> для предоставления полей.

Несоответствие групп захвата. Регулярное выражение с 2 группами захвата в паре с <order>, содержащим 3 поля. Третье поле никогда не заполняется. Эта проблема коварна тем, что Wazuh не выдаёт ошибку при выполнении - он просто оставляет поле пустым.

Осиротевшие ссылки на родителей. Дочерний декодер объявляет <parent>sshd-custom</parent>, но ни один декодер с именем sshd-custom не существует в загруженных файлах. Дочерний декодер никогда не активируется.

Некорректные атрибуты элементов. Атрибут offset элемента <prematch> принимает только after_regex и after_parent. Использование after_prematch для <prematch> (допустимо только для <regex>) приводит к неопределённому поведению. Аналогично, <regex type="osmatch"> некорректен, поскольку osmatch не поддерживает группы захвата.

Нарушения синтаксиса OS_Regex. Стандартный движок регулярных выражений Wazuh (osregex) не поддерживает конструкции (?...), квантификаторы {n,m} и альтернацию (|) внутри групп. Использование этих паттернов приводит к молчаливым сбоям сопоставления.

Пустые элементы plugin_decoder. <plugin_decoder></plugin_decoder> без содержимого - всегда ошибка. Элемент должен указывать, какой плагин вызывать.

Архитектура wazuh-decoder-linter

Инструмент написан на Python (3.10+) с использованием lxml для парсинга XML и click для CLI. Кодовая база следует модульной архитектуре:

wazuh_decoder_linter/
  cli.py           # Click CLI: разбор аргументов, форматирование вывода
  constants.py     # Все константы валидации, перечисления, допустимые множества
  linter.py        # Ядро: класс WazuhDecoderLinter
  logtest.py       # Интеграция с Wazuh logtest API
  models.py        # Модели данных: LintResult, LintReport, DecoderMeta
  regex_utils.py   # Подсчёт групп regex и валидация синтаксиса

Ядро (WazuhDecoderLinter) использует двухпроходную стратегию парсинга для обеспечения устойчивости. Сначала выполняется попытка разобрать весь файл как XML. При неудаче - что часто случается, когда один блок декодера содержит некорректный контент - инструмент переключается на извлечение отдельных блоков <decoder>...</decoder> с отслеживанием глубины вложенности и обрабатывает каждый независимо. Это означает, что один некорректный декодер не препятствует проверке остальных валидных декодеров в том же файле.

Санитизация XML обрабатывает специфичные для Wazuh паттерны, которые нарушают стандартный парсинг XML: неэкранированные символы &, границы слов OS_Regex \< и голые символы <, не являющиеся XML-тегами. Санитайзер сохраняет валидные XML-сущности (&amp;, &#123;, &#x1F;), экранируя всё остальное.

16 правил валидации

Линтер реализует 16 отдельных правил валидации, каждое из которых привязано к уровню серьёзности (ERROR, WARNING или INFO):

Ошибки (обязательно исправить)

ПравилоОписание
Атрибут nameКаждый <decoder> должен иметь атрибут name
Пустой parentЭлементы <parent> не должны быть пустыми
Согласованность regex/order<regex> требует <order> и наоборот (если нет <plugin_decoder>)
Формат полей orderИмена полей должны соответствовать известным статическим полям или динамическому паттерну ^\w[\w.\- ]*$
Некорректные offsetАтрибуты offset должны использовать допустимые значения для каждого типа элемента
Некорректные типы regexАтрибут type должен быть osregex, pcre2 или osmatch
Пустой plugin_decoder<plugin_decoder> не должен быть пустым
Некорректные JSON-поля<json_null_field> должен быть “string” или “discard”; <json_array_structure> - “array” или “csv”
Значение use_own_nameДолжно быть “true”

Предупреждения (рекомендуется исправить)

ПравилоОписание
Неизвестные элементыXML-элементы, не входящие в 16 известных дочерних элементов декодера
Несоответствие группКоличество групп захвата regex не совпадает с количеством полей order
Тип декодераЗначение <type> не входит в 8 распознаваемых типов
Неизвестный плагинИмя plugin_decoder не входит в 5 известных плагинов
use_own_name без parent<use_own_name> требует <parent>
Accumulate без ID<accumulate> требует “id” в полях <order>
Поля FTSПоля FTS должны соответствовать известным статическим полям плюс location и name
Синтаксис OS_RegexВыявление неподдерживаемых конструкций в паттернах osregex
Цепочка родителейМежфайловая: имена родительских декодеров должны существовать в сканируемых файлах

Модуль утилит для регулярных выражений заслуживает отдельного внимания. Он различает Wazuh OS_Regex и PCRE2 при подсчёте групп захвата. В OS_Regex \( - это литеральная скобка (не группа), тогда как в PCRE2 незахватывающие группы ((?:...)), опережающие проверки ((?=...)) и ретроспективные проверки ((?<=...)) исключаются из подсчёта. Такая точность предотвращает ложные срабатывания при валидации количества групп.

Установка и использование CLI

Установка непосредственно из GitHub:

# Базовая установка
pip install git+https://github.com/pyToshka/wazuh-linter.git

# С поддержкой API-тестирования
pip install "wazuh-decoder-linter[api] @ git+https://github.com/pyToshka/wazuh-linter.git"

Статический анализ

Проверка отдельного файла или целой директории:

# Проверка отдельного файла
wazuh-decoder-lint /var/ossec/etc/decoders/local_decoder.xml

# Проверка всех файлов декодеров в директории
wazuh-decoder-lint /var/ossec/etc/decoders/

# Строгий режим: предупреждения считаются ошибками
wazuh-decoder-lint --strict /var/ossec/etc/decoders/

# Показать INFO-сообщения
wazuh-decoder-lint --show-info /var/ossec/etc/decoders/

# JSON-вывод для интеграции с CI/CD
wazuh-decoder-lint --format json /var/ossec/etc/decoders/

API-тестирование на работающем менеджере

Инструмент может верифицировать декодеры на работающем менеджере Wazuh через logtest API:

# Инлайн тестовый лог
wazuh-decoder-lint /var/ossec/etc/decoders/ \
  --test-api \
  --api-url https://wazuh-manager:55000 \
  --api-user wazuh-wui \
  --test-log "sshd:Oct 15 21:07:00 myhost sshd[1234]: Failed password for root from 10.0.0.1"

# YAML-файл с тестовыми случаями
wazuh-decoder-lint /var/ossec/etc/decoders/ \
  --test-api \
  --test-file test_cases.yml

Формат YAML-файла с тестами:

tests:
  - event: 'Oct 15 21:07:00 myhost sshd[1234]: Failed password for root from 10.0.0.1'
    decoder: sshd
    description: 'SSH failed password'
  - event: '192.168.1.1 - - [15/Oct/2024:21:07:00 +0000] "GET / HTTP/1.1" 200 1234'
    decoder: web-accesslog
    description: 'Apache access log entry'

Для безопасности API-пароля используйте переменную окружения WAZUH_API_PASSWORD вместо флага --api-pass, чтобы избежать раскрытия учётных данных в списке процессов.

Коды возврата

КодЗначение
0Все проверки пройдены
1Обнаружены ошибки (или предупреждения в строгом режиме, или неудачные API-тесты)
2Ошибка использования CLI

Программный Python API

Инструмент предоставляет Python API для интеграции в собственные инструменты:

from wazuh_decoder_linter import WazuhDecoderLinter

linter = WazuhDecoderLinter()
report = linter.lint_paths(["path/to/decoders/"])

for result in report.results:
    print(f"[{result.severity}] {result.file}:{result.line} - {result.message}")

if report.has_errors:
    print(f"Found {len(report.errors)} error(s)")

# Строгий режим: предупреждения считаются сбоями
if report.has_failures(strict=True):
    sys.exit(1)

Для API-тестирования:

from wazuh_decoder_linter import WazuhLogtest

with WazuhLogtest(
    api_url="https://wazuh:55000",
    user="wazuh-wui",
    password="secret",
    verify_ssl=False,
) as tester:
    results = tester.test_batch([
        {"event": "Failed password for root from 10.0.0.1", "decoder": "sshd"},
    ])
    for result in results:
        print(f"[{result['status']}] {result['description']}")

Интеграция с CI/CD-конвейерами

GitHub Actions

name: Lint Wazuh Decoders
on:
  push:
    paths:
      - 'decoders/**'
  pull_request:
    paths:
      - 'decoders/**'

jobs:
  lint-decoders:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.12'

      - name: Install wazuh-decoder-linter
        run: pip install "wazuh-decoder-linter[api] @ git+https://github.com/pyToshka/wazuh-linter.git"

      - name: Lint decoders
        run: wazuh-decoder-lint --strict --format json decoders/ > lint-results.json

      - name: Upload results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: lint-results
          path: lint-results.json

Pre-commit хук

Репозиторий включает .pre-commit-config.yaml, обеспечивающий качество кода с помощью black, isort, flake8, bandit и mypy. Вы можете добавить проверку декодеров как локальный pre-commit хук:

repos:
  - repo: local
    hooks:
      - id: wazuh-decoder-lint
        name: Wazuh Decoder Lint
        entry: wazuh-decoder-lint --strict
        language: python
        files: '\.xml$'
        types: [file]

Формат вывода JSON интегрируется с любой CI/CD-системой, способной обрабатывать структурированный вывод. Конвенция кодов возврата (0 = успех, 1 = ошибка, 2 = ошибка использования) следует стандартным Unix-соглашениям для бесшовной интеграции в конвейеры.

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

Тестирование на реальных декодерах

Проект включает обширный набор тестов: более 80 валидных XML-файлов декодеров из стандартного набора правил Wazuh, более 80 намеренно испорченных копий для тестирования обнаружения ошибок и более 90 параметризованных интеграционных тестовых случаев, охватывающих декодеры SSH, Apache, Cisco, Fortinet, Snort, auditd, Windows Security, AWS, Docker, Kubernetes и десятков других источников.

Пример текстового вывода:

[ERROR] broken_decoder.xml:14 - Empty <plugin_decoder> in decoder 'json-msgraph'
[WARNING] decoder.xml:58 - Decoder 'sshd-success': regex (osregex) has 2 capture
  group(s) but order has 3 field(s)

============================================================
  Wazuh Logtest API Results
============================================================
  [PASS] SSH failed password
  [FAIL] Apache access log
         expected: apache, got: web-accesslog

  API tests: 1 passed, 1 failed, 0 errors

Summary: 1 error(s), 1 warning(s)

Интеграционный набор тестов разворачивает полный стек Wazuh (менеджер, индексатор, панель управления) через Docker Compose и выполняет все 90+ тестовых случаев на реальном logtest API. Это подтверждает, что результаты статического анализа линтера соответствуют фактическому поведению Wazuh при выполнении.

Заключение

Статический анализ XML-файлов декодеров Wazuh устраняет разрыв между написанием декодеров и их развёртыванием с уверенностью в корректности. Инструмент wazuh-decoder-linter обнаруживает ошибки конфигурации - отсутствующие элементы order, несоответствия групп захвата regex, осиротевшие цепочки родителей, некорректные атрибуты - до того, как они попадут в продакшен и вызовут молчаливую потерю данных в вашем SIEM-конвейере.

Инструмент доступен как открытый исходный код под лицензией BSD 3-Clause, поддерживает Python 3.10+ и размещён на github.com/pyToshka/wazuh-linter. Приглашаем к участию в разработке правил валидации, тестовых случаев и документации.

Дополнительные материалы


Смотрите также