Конфигурация пользовательского парсера
В этом разделе описан синтаксис конфигурации пользовательского парсера в TRON.ASOC, поддерживаемые выражения и функции, ограничения конструктора парсеров, а также порядок отладки конфигурации. Порядок создания, назначения и удаления парсеров описан в разделе Действия с парсерами.
Конструктор парсеров основан на стандартном языке JSONata, доработанном под модель данных TRON.ASOC: часть конструкций языка не поддерживается, часть работает иначе, чем в стандартной реализации.
Из этого следуют два практических правила:
- используйте только те конструкции и функции, которые приведены в разделах Поля маппинга и Доступные функции — обращение к неподдерживаемой конструкции отключает разбор;
- результат проверки выражения в стороннем инструменте (try.jsonata.org) не гарантирует такого же результата в TRON.ASOC. Используйте его только для проверки путей к полям на реальном отчете, а логику выражения проверяйте на образце отчета в системе (см. раздел Создание и проверка конфигурации).
Отличия от стандартной JSONata
Конструктор парсеров использует стандартный язык JSONata, доработанный под задачи разбора отчетов в TRON.ASOC. Конструкции, приведенные в таблице ниже, работают в конструкторе иначе, чем в стандартной реализации языка. Это наиболее частая причина того, что корректное на вид выражение возвращает пустое значение.
| Конструкция | Стандартная JSONata | Конструктор парсеров TRON.ASOC |
|---|---|---|
Оператор or | Строго логический: "a" or "b" возвращает true | Возвращает первое непустое значение (fallback-выражение): "a" or "b" возвращает "a" |
$match(expr, pattern) | Требует regex-литерал: $match(expr, /CVE-\d+/) | Принимает шаблон строкой: $match(expr, "CVE-\\d+") |
| Тернарный оператор с условием по отсутствующему полю | x ? a : b возвращает b, если поля x нет в находке | Возвращает пустое значение, ветка b не применяется |
| Имена полей с дефисом | Требуют обратных кавычек: info.`cwe-id` | Обратные кавычки не поддерживаются, поле указывается как есть: info.cwe-id |
:= (присваивание переменной) | Поддерживается | Не поддерживается |
{ ... } (литерал объекта) | Поддерживается | Не поддерживается |
% (обращение к родительскому контексту) | Поддерживается | Не поддерживается |
$number(), $exists(), $count() | Поддерживаются | Не поддерживаются |
Тернарный оператор с условием по полю, которое есть не у всех находок, возвращает значение только для находок, где поле присутствует. Для остальных находок поле остается пустым, даже если в ветке «иначе» указано непустое выражение. Для необязательных полей используйте оператор or (см. раздел Типовые приемы).
Если хотя бы одно поле секции содержит неподдерживаемую конструкцию, секция не возвращает ни одной находки целиком. Сообщение об ошибке при этом не выводится и результатом разбора будет 0 уязвимостей, без указания на конкретное поле.
Поэтому при разработке конфигурации добавляйте поля поэтапно, а не все сразу (см. раздел Создание и проверка конфигурации).
Структура конфигурации
Конфигурация парсера — это JSON-объект, в котором каждому полю модели TRON.ASOC сопоставлено выражение JSONata.
Конфигурация может быть описана в двух формах:
- одна секция — поля маппинга указываются на верхнем уровне объекта;
{
"items_path": "runs[0].results",
"category": "...",
"severity": "..."
}
- несколько секций — поля маппинга указываются внутри массива
sections, по одному объекту на каждый тип находок.
{
"sections": [
{ "items_path": "Results[].Vulnerabilities", "category": "...", "severity": "..." },
{ "items_path": "Results[].Misconfigurations", "category": "...", "severity": "..." }
]
}
Находки, извлеченные всеми секциями, объединяются в общий результат отчета.
Описание секций конфигураций
items_path
items_path обозначает путь к массиву находок в отчете. Все остальные выражения секции вычисляются относительно отдельного элемента этого массива, а не относительно корня отчета.
| Структура отчета | Значение items_path |
|---|---|
| Находки в одном вложенном массиве | runs[0].results |
| Корень отчета — сам массив находок | $ |
| Два уровня вложенности | Results[].Vulnerabilities |
| Три уровня вложенности | site[].alerts[].instances |
Если items_path не начинается с префикса $, то путь будет обработан упрощенным обработчиком, который понимает только последовательность сегментов и разворот массивов []. Фильтры-предикаты ([field = "value"]) в такой записи не применяются.
Чтобы использовать фильтр, начните путь с $:
"items_path": "$.issues[type.key = \"Sca\"].ptOsvs"
Фильтрация по значению поля поддерживается не для всех форм пути. Если после добавления фильтра секция возвращает 0 находок, разверните массив без фильтра и отсеките лишние находки на уровне полей маппинга.
Отдельные ошибочные формы записи, которые встречаются чаще всего:
"[]"не является самостоятельным путем. Квадратные скобки — это оператор разворота массива при сегменте пути ("[].messages"). Чтобы развернуть корневой массив отчета, используйте"$";- пустая строка включает автоопределение массива находок. Автоопределение срабатывает не на всех структурах отчета, поэтому в пользовательских парсерах указывайте
items_pathявно.
sections и __parent
Массив sections применяется, когда отчет содержит несколько независимых массивов находок разных типов (например, отчет Trivy содержит уязвимости, мисконфигурации и секреты) или когда один элемент отчета должен породить несколько находок.
Внутри секции доступен служебный объект __parent — объект, в котором лежал развернутый массив items_path.
__parent дает доступ только к скалярным полям родительского объекта. Вложенные объекты и массивы через __parent недоступны: выражение вида __parent.type.displayName не возвращает значения и, как любое неподдерживаемое обращение, может отключить секцию целиком.
Если нужное значение лежит во вложенном объекте родителя, используйте скалярное поле-аналог того же уровня (например, готовый текстовый идентификатор компонента) либо перенесите разворот на уровень выше.
Пример: один компонент SCA (Software Composition Analysis) содержит массив уязвимостей, и каждая уязвимость должна стать отдельной находкой TRON.ASOC.
{
"sections": [
{
"items_path": "issues",
"category": "type.displayName",
"severity": "$lookup({\"10\":\"1\",\"20\":\"1\",\"30\":\"2\",\"40\":\"3\",\"50\":\"4\"}, $string(level.severity))"
},
{
"items_path": "issues[].ptOsvs",
"category": "summary",
"cve": "$match($join(sources, \"; \"), \"CVE-\\\\d{4}-\\\\d+\").match",
"source_file": "__parent.parentItem"
}
]
}
Секция с более общим items_path извлекает в том числе и те элементы, которые обрабатывает вторая секция. Совпадающие находки затем объединяются по составному ключу (см. раздел Объединение находок), поэтому итоговое количество находок обычно меньше суммы находок всех секций.
Доступные функции
В таблице ниже представлены функции, поддерживаемые конструктором парсеров TRON.ASOC.
| Функция | Назначение |
|---|---|
$lookup(map, key) | Возвращает значение из таблицы соответствия по ключу |
$string(expr) | Приводит значение к строке |
$join(array, separator) | Объединяет элементы массива в строку через разделитель |
$split(string, separator) | Разбивает строку на массив |
$substring(string, start, length) | Возвращает подстроку |
$replace(string, pattern, replacement) | Заменяет вхождения шаблона |
$uppercase(string) | Приводит строку к верхнему регистру |
$match(string, pattern) | Ищет совпадение с шаблоном, возвращает объект с ключами match, index, groups |
$max(array) | Возвращает максимальное значение массива чисел |
$truncBytes(string, n) | Обрезает строку до n байт без разрыва многобайтных символов |
$tailBytes(string, n) | Возвращает последние n байт строки |
$linesJoin(lines) | Объединяет через перенос строки непустые значения Content из массива объектов вида {"Content": "..."} |
$sanitize(html) | Удаляет HTML-теги из строки и обрезает пробелы по краям |
$cwe(value) | Возвращает первый идентификатор вида CWE-XX, найденный в строке или массиве строк |
$cweAll(value) | Возвращает все уникальные идентификаторы CWE, найденные в строке, включая ссылки вида cwe.mitre.org/data/definitions/79.html, объединенные через ; |
Функции, отсутствующие в таблице, не поддерживаются конструктором парсеров. Обращение к неподдерживаемой функции отключает всю секцию (см. предупреждение в разделе Отличия от стандартной JSONata).
Особенности отдельных функций
$match возвращает объект, а не строку. Чтобы получить текст совпадения, обратитесь к ключу match:
"cve": "$match($join(sources, \"; \"), \"CVE-\\\\d{4}-\\\\d+\").match"
$max работает только с числами. Если поле отчета содержит оценку строкой ("5.3"), вызов $max завершится ошибкой и отключит секцию. В большинстве отчетов внутри одной находки встречается не более одной оценки каждой версии CVSS, поэтому вместо $max достаточно индекса [0].
$join не принимает null. Явный null в отчете и отсутствующий ключ обрабатываются по-разному: отсутствующий путь безопасно возвращает пустое значение, а null приводит к ошибке. Ставьте проверку перед вызовом:
"cve": "info.classification.cve-id ? $join(info.classification.cve-id, \"; \") : \"\""
Проверка через тернарный оператор здесь допустима, поскольку при отсутствии поля ожидаемый результат — пустое значение. Ту же проверку ставьте перед $uppercase и другими функциями, получающими значение из необязательного поля. Указывать в ветке «иначе» непустое значение по умолчанию бесполезно: если поля нет, эта ветка не применяется (см. раздел Отличия от стандартной JSONata).
Типовые приемы
Поля с дефисом в имени. Указываются как есть, без обратных кавычек:
"source_file": "matched-at or url"
Значение из нескольких возможных источников. Оператор or возвращает первое непустое значение, поэтому альтернативы перечисляются цепочкой:
"line": "startLine or numberLine or beginLine"
Значение по умолчанию для необязательного поля. Если поле есть не у всех находок, задайте запасной источник через or, а не через тернарный оператор:
"source_object": "matcher-name or template-id"
Выражение вида "matcher-name ? matcher-name : template-id" для находок без поля matcher-name вернет пустое значение.
Отбор нужных элементов массива. Если массив содержит идентификаторы разных систем (CVE, GHSA, BDU), не обращайтесь к элементу по позиции — первым может оказаться идентификатор другой системы. Соберите массив в строку и найдите нужный идентификатор по шаблону:
"cve": "$match($join(sources, \"; \"), \"CVE-\\\\d{4}-\\\\d+\").match"
Сборка нескольких значений в одно поле. Классификации и другие разнородные данные объединяются конкатенацией с проверкой каждого источника:
"message": "(owasp.value != \"\" ? (\"OWASP \" & owasp.value & \" | \") : \"\") & (sans.value != \"\" ? (\"SANS \" & sans.value) : \"\")"
Значения из вложенных массивов объектов. Путь по массиву объектов автоматически применяется к каждому элементу, отдельная функция не нужна: pcidss.displayName вернет массив значений всех элементов.
Ограничения на значения полей
Значения полей находки ограничены по длине в байтах, у каждого поля свое ограничение. В кириллическом тексте один символ занимает два байта, поэтому ограничение достигается примерно вдвое быстрее, чем в латинице.
Если при разборе отчета возникает ошибка, уменьшите длину значения функцией $truncBytes:
"category": "$truncBytes(summary, 500)"
Обрезка строки обычными строковыми функциями может разорвать многобайтный символ, после чего сохранение находки завершится ошибкой, поэтому для обрезки используйте именно $truncBytes.
Объединение находок
Находки с одинаковыми значениями ключевых полей объединяются в одну запись по составному ключу. Ниже представлен список таких ключей:
tool_type_name;cve;cwe;lib_name;lib_version;line;matched_code;source_file;source_object;category.
Если конфигурация парсера не заполняет часть полей составного ключа, разные находки могут получить одинаковый ключ и объединиться в одну. Чаще всего это происходит с типами находок, у которых в отчете нет пути к файлу и номера строки (находки SCA, обнаруженные секреты) — при пустых source_file и line ключ сводится к паре category и cwe.
Если ожидаемое количество находок меньше фактического количества записей в отчете, сопоставьте разницу с количеством совпадающих составных ключей — это штатное поведение, а не потеря данных.
Требования к отчету
Отчет должен быть синтаксически корректным документом JSON по RFC 8259. Разбор выражений парсера выполняется уже над разобранным документом, поэтому конфигурацией парсера нельзя обойти ошибки самого файла:
- недопустимые escape-последовательности (после
\разрешены только",\,/,b,f,n,r,tиuс четырьмя шестнадцатеричными цифрами); - комментарии
//— грамматикой JSON не предусмотрены; - неэкранированные переводы строк и другие управляющие символы внутри строковых значений;
- неэкранированные двойные кавычки внутри строковых значений.
Если отчет не принимается системой, сначала проверьте его любым валидатором JSON, и только затем ищите причину в конфигурации парсера.
SARIF — это схема данных поверх JSON, а не альтернативный формат файла. Любой SARIF-отчет — это и JSON-файл.
При этом один и тот же инструмент безопасности может экспортировать результаты в нескольких несовместимых схемах (например, SARIF и собственный формат JSON). Конфигурация парсера привязана к конкретной схеме: парсер, написанный под SARIF-экспорт инструмента, не разберет его же отчет в собственном формате. Перед составлением конфигурации убедитесь, что образец отчета получен тем же способом экспорта, который будет использоваться в дальнейшем.
Создание и проверка конфигурации
Порядок создания конфигурации
Для создания корректной конфигурации парсера выполните следующие шаги:
- Определите структуру отчета: в каком массиве лежат находки, сколько уровней вложенности, есть ли находки разных типов в одном массиве.
- Составьте минимальную конфигурацию: только
items_path,categoryиseverityс постоянным значением (например,"1"). - Проверьте конфигурацию и убедитесь, что количество найденных находок соответствует ожидаемому.
- Добавляйте поля по одному или небольшими группами, проверяя конфигурацию после каждого шага.
- Если конфигурация содержит несколько секций, проверяйте каждую секцию отдельно и объединяйте их только после проверки.
Такой порядок создания рекомендован потому, что ошибка в любом поле отключает секцию целиком и не указывает на конкретное поле. В конфигурации из двадцати полей определить источник ошибки без пошагового добавления невозможно.
Ошибки, возникающие при создании конфигурации
Ниже представлены наиболее распространенные ошибки, возникающие при создании и проверке конфигурации.
| Ошибка | Вероятная причина |
|---|---|
0 находок при заведомо непустом отчете | Некорректный items_path, либо неподдерживаемая конструкция в одном из полей секции |
0 находок при любом значении items_path, включая заведомо неверное | Инструменту безопасности назначен другой парсер. Проверьте значение поля Парсер в настройках инструмента |
| Одна находка вместо нескольких | Все находки получили одинаковый составной ключ (см. раздел Объединение находок) |
| Часть находок отсутствует | Совпадение составных ключей у части находок либо неучтенное значение в таблице $lookup |
| Отдельное поле пустое у всех находок | Опечатка в пути, обратные кавычки вокруг имени поля с дефисом, либо значение отсутствует в самом отчете |
| Отдельное поле пустое у части находок | В отчете несколько типов находок с разной структурой — добавьте альтернативные источники через or. Если поле задано тернарным оператором, проверьте, есть ли поле из условия у всех находок: при его отсутствии выражение возвращает пустое значение |
| Ошибка сохранения находок | Разбор выполнен успешно, проблема на этапе сохранения (см. раздел Ограничения на значения полей) |
Если после изменения конфигурации результат не меняется, изменяйте по одному параметру за раз. Одновременная правка нескольких полей не позволяет определить, какое из изменений повлияло на результат.
Проверка конфигурации на образце отчета
Отдельного режима проверки конфигурации на образце отчета в системе нет. Чтобы проверить конфигурацию, импортируйте образец отчета в проверку безопасности с назначенным парсером и оцените результат по составу найденных уязвимостей и заполненности их полей. Порядок действий описан в разделе Создание парсера для инструмента безопасности, не поддерживаемого TRON.ASOC.
Чтобы не смешивать проверочные запуски с рабочими данными, выполняйте проверку в отдельном слое или отдельной проверке безопасности.
Изменение конфигурации назначенного парсера
Изменение конфигурации парсера наследуется во все инструменты безопасности и проверки безопасности, где этот парсер используется, но применяется только к отчетам, обработанным после изменения. Ранее сохраненные уязвимости не пересчитываются: новые поля у них не появятся, а изменившиеся значения не обновятся.
Чтобы результаты разбора соответствовали новой конфигурации, обработайте отчет повторно. При повторной обработке уязвимости, которые обновленный парсер не находит, закрываются, а найденные заново объединяются с ранее найденными по совпадающему хешу.
Некорректная конфигурация приводит поэтому не только к отсутствию новых уязвимостей, но и к закрытию ранее найденных. Проверяйте конфигурацию на отдельном слое или отдельной проверке безопасности, прежде чем назначать парсер на инструмент безопасности с рабочими данными.