Все модули нормализации (deny, if, assign) используют Common Expression Language (CEL) для написания выражений. В данном справочнике перечислены все функции, доступные в CEL-окружении KOMRAD.
Доступ к полям события
Переменная event предоставляет доступ ко всем полям текущего события.
| Функция | Возврат | Описание |
|---|
event.vars("field") | dyn | Получить значение поля. Возвращает null, если поле отсутствует |
event.as_string("field") | string | Получить значение поля как строку. Возвращает "", если поле отсутствует |
event["field"] | dyn | Прямой доступ к полю (вызывает ошибку, если поле отсутствует) |
event.Raw | string | Прямой доступ к базовому полю (аналогично для других базовых полей) |
Базовые поля события
К следующим полям можно обращаться напрямую через event.<поле>:
| Поле | Тип | Описание |
|---|
Raw | string | Исходный текст события |
Producer | string | Идентификатор источника |
PluginIDs | list | Список ID плагинов |
AssetIPs | list | IP связанных активов |
Key | string | Ключ события |
Count | int | Число агрегированных событий |
FwdIP | string | IP-адрес отправителя |
FwdPort | int | Порт отправителя |
CollectorID | string | ID коллектора |
CollectorType | string | Тип коллектора |
SetupID | string | ID настройки |
AggregationName | string | Метка агрегации |
CTime | timestamp | Время создания |
WTime | timestamp | Время записи |
GenerationTime | timestamp | Время генерации |
SL | uint | Метка безопасности |
TenantID | uint | ID тенанта |
Custom | map | Карта пользовательских полей (ECS.*) |
Примеры
event.vars("ECS.Client.IP") != null ? event.as_string("ECS.Client.IP") : "unknown"
event.Raw.startsWith("system,info")
event.FwdIP.in_cidr("10.0.0.0/8")
Работа со строками
Проверки и сравнения
| Функция | Возврат | Описание |
|---|
s.contains_substr(sub) | bool | Содержит ли строка подстроку |
s.contains_any(chars) | bool | Содержит ли строка любой символ из набора |
s.has_prefix(prefix) | bool | Начинается ли строка с префикса |
s.has_suffix(suffix) | bool | Заканчивается ли строка суффиксом |
s.startsWith(prefix) | bool | Начинается ли строка с префикса (стандарт CEL) |
s.endsWith(suffix) | bool | Заканчивается ли строка суффиксом (стандарт CEL) |
s.equal_fold(other) | bool | Регистронезависимое сравнение |
s.compare(other) | int | Лексикографическое сравнение (-1, 0, 1) |
Поиск
| Функция | Возврат | Описание |
|---|
s.index(sub) | int | Индекс первого вхождения подстроки (-1 если нет) |
s.last_index(sub) | int | Индекс последнего вхождения подстроки |
s.index_any(chars) | int | Индекс первого символа из набора |
s.last_index_any(chars) | int | Индекс последнего символа из набора |
s.count(sub) | int | Количество неперекрывающихся вхождений |
Преобразование
| Функция | Возврат | Описание |
|---|
s.to_lower() | string | Преобразование в нижний регистр |
s.to_upper() | string | Преобразование в верхний регистр |
s.to_title() | string | Преобразование в Title Case |
s.replace_all(old, new) | string | Замена всех вхождений |
s.repeat(n) | string | Повторить строку n раз |
s.canonical_mime_header_key() | string | Канонический MIME-заголовок |
Разделение и объединение
| Функция | Возврат | Описание |
|---|
s.split(sep) | list(string) | Разделить строку по разделителю (ext.Strings) |
s.split_n(sep, n) | list(string) | Разделить с максимальным числом частей |
s.split_after(sep) | list(string) | Разделить после каждого вхождения разделителя |
s.split_after_n(sep, n) | list(string) | Разделить после разделителя с лимитом |
s.fields() | list(string) | Разделить по пробельным символам |
list.join(sep) | string | Объединить список строк через разделитель (ext.Strings) |
Обрезка
| Функция | Возврат | Описание |
|---|
s.trim(chars) | string | Удалить символы с обеих сторон |
s.trim_left(chars) | string | Удалить символы слева |
s.trim_right(chars) | string | Удалить символы справа |
s.trim_space() | string | Удалить пробельные символы с обеих сторон |
s.trim_prefix(prefix) | string | Удалить префикс |
s.trim_suffix(suffix) | string | Удалить суффикс |
Извлечение подстрок
| Функция | Возврат | Описание |
|---|
s.substring(start, end) | string | Подстрока по позициям Unicode code points [start:end) |
s.charAt(i) | string | Символ по индексу (ext.Strings) |
s.indexOf(sub) | int | Индекс подстроки (ext.Strings) |
s.lastIndexOf(sub) | int | Последний индекс подстроки (ext.Strings) |
Форматирование
| Функция | Возврат | Описание |
|---|
format.sprintf(args) | string | Форматирование строки (синтаксис Go fmt.Sprintf) |
sprintf(format, args) | string | Форматирование строки (свободная функция) |
Примеры
event.as_string("ECS.User.Email").split("@")[1]
event.Raw.has_prefix("system,") && event.Raw.has_suffix("via ssh")
event.as_string("ECS.Host.Name").to_lower().replace_all(" ", "_")
Регулярные выражения
Регулярные выражения используют синтаксис RE2. Паттерны, определённые в блоке regex: конфигурации, предварительно компилируются и доступны по имени.
| Функция | Возврат | Описание |
|---|
s.re_match(pattern) | bool | Совпадает ли строка с паттерном |
s.re_find(pattern) | string | Первое совпадение |
s.re_find_all(pattern) | list(string) | Все совпадения |
s.re_find_submatch(pattern) | list(string) | Группы захвата первого совпадения |
s.re_find_all_submatch(pattern) | list(list(string)) | Группы захвата всех совпадений |
s.re_replace_all(pattern, repl) | string | Замена всех совпадений (поддержка $1, ${name}) |
Все функции также работают с типом bytes.
Примеры
event.Raw.re_match('^system,info,account user \\w+ logged in')
event.Raw.re_find_submatch('^user (\\w+) from (\\S+)')[1]
event.Raw.re_find_submatch('^user (\\w+) from (\\S+)')[2]
size(event.Raw.re_find_submatch('pattern (\\w+)')) > 1
? event.Raw.re_find_submatch('pattern (\\w+)')[1]
: ''
event.as_string("Raw").re_replace_all('\\d+', 'N')
Хеширование и кодирование
Хеш-функции
| Функция | Возврат | Описание |
|---|
s.md5() / md5(s) | bytes | MD5 хеш |
s.sha1() / sha1(s) | bytes | SHA-1 хеш |
s.sha256() / sha256(s) | bytes | SHA-256 хеш |
s.hmac(algo, key) | bytes | HMAC (algo: "sha1" или "sha256", key: bytes) |
Кодирование
| Функция | Возврат | Описание |
|---|
s.base64() / base64(s) | string | Base64 кодирование (стандартное) |
s.base64_decode() | bytes | Base64 декодирование |
s.base64_raw() / base64_raw(s) | string | Base64 без дополнения (raw) |
s.base64_raw_decode() | bytes | Декодирование raw Base64 |
s.hex() / hex(s) | string | Шестнадцатеричное кодирование |
s.hex_decode() / hex_decode(s) | bytes | Декодирование hex-строки |
Генерация
| Функция | Возврат | Описание |
|---|
uuid() | string | Генерация случайного UUID v4 |
Все хеш-функции работают как со строками, так и с байтами.
Примеры
event.Raw.sha256().hex()
event.as_string("ECS.Message").base64()
event.Raw.hmac("sha256", b"secret_key").hex()
uuid()
Работа с JSON
| Функция | Возврат | Описание |
|---|
v.encode_json() / encode_json(v) | string | Кодировать значение в JSON-строку |
s.decode_json() / decode_json(s) | dyn | Декодировать JSON-строку в объект |
s.decode_json_string_numbers() | dyn | Декодировать JSON, числа как строки |
s.decode_json_stream() | list(dyn) | Декодировать поток JSON-объектов (NDJSON) |
s.decode_json_stream_string_numbers() | list(dyn) | Декодировать поток JSON, числа как строки |
s.jsonpath_has(path) | bool | Проверить наличие JSONPath в JSON-строке |
s.jsonpath_get(path) | dyn | Извлечь значение по JSONPath из JSON-строки |
Функции decode_json и decode_json_stream работают как со строками, так и с байтами.
Примеры
event.Raw.decode_json()
event.as_string("Raw").jsonpath_get("$.user.id")
event.as_string("Raw").jsonpath_has("$.error")
{"key": "value"}.encode_json()
Работа с XML
| Функция | Возврат | Описание |
|---|
s.decode_xml() / decode_xml(s) | dyn | Декодировать XML в map (без схемы) |
s.decode_xml(schema) | dyn | Декодировать XML с использованием именованной XSD-схемы |
XSD-схемы задаются в конфигурации модуля через поле xml-xsd:
processors:
- module: assign
xml-xsd:
order: |
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
...
</xs:schema>
assign:
- target: ECS.Data
cel: 'event.Raw.decode_xml("order")'
Работа со временем
Функции
| Функция | Возврат | Описание |
|---|
now() | timestamp | Текущее UTC-время в момент вызова |
ts.format(layout) | string | Форматировать timestamp по шаблону Go time layout |
s.parse_time(layout) | timestamp | Парсинг строки во время по шаблону |
s.parse_time(layouts) | timestamp | Парсинг строки, попробовать несколько шаблонов |
ts.round(duration) | timestamp | Округлить время до ближайшего кратного duration |
ts.truncate(duration) | timestamp | Отсечь время до кратного duration |
d.round(duration) | duration | Округлить длительность |
d.truncate(duration) | duration | Отсечь длительность |
Глобальные переменные
| Переменная | Тип | Описание |
|---|
now | timestamp | Время начала вычисления (вычисляется один раз) |
time_layout | map | Карта именованных шаблонов времени |
Шаблоны времени (time_layout)
| Ключ | Значение | Описание |
|---|
time_layout.RFC3339 | "2006-01-02T15:04:05Z07:00" | ISO 8601 |
time_layout.RFC3339Nano | "2006-01-02T15:04:05.999999999Z07:00" | С наносекундами |
time_layout.DateTime | "2006-01-02 15:04:05" | Дата и время |
time_layout.DateOnly | "2006-01-02" | Только дата |
time_layout.TimeOnly | "15:04:05" | Только время |
time_layout.RFC1123 | "Mon, 02 Jan 2006 15:04:05 MST" | RFC 1123 |
time_layout.RFC1123Z | "Mon, 02 Jan 2006 15:04:05 -0700" | RFC 1123 с числовой зоной |
time_layout.RFC822 | "02 Jan 06 15:04 MST" | RFC 822 |
time_layout.RFC822Z | "02 Jan 06 15:04 -0700" | RFC 822 с числовой зоной |
time_layout.RFC850 | "Monday, 02-Jan-06 15:04:05 MST" | RFC 850 |
time_layout.ANSIC | "Mon Jan _2 15:04:05 2006" | ANSI C |
time_layout.UnixDate | "Mon Jan _2 15:04:05 MST 2006" | Unix |
time_layout.RubyDate | "Mon Jan 02 15:04:05 -0700 2006" | Ruby |
time_layout.Kitchen | "3:04PM" | Время кухонных часов |
time_layout.Stamp | "Jan _2 15:04:05" | Syslog-формат |
time_layout.StampMilli | "Jan _2 15:04:05.000" | С миллисекундами |
time_layout.StampMicro | "Jan _2 15:04:05.000000" | С микросекундами |
time_layout.StampNano | "Jan _2 15:04:05.000000000" | С наносекундами |
time_layout.HTTP | "Mon, 02 Jan 2006 15:04:05 GMT" | HTTP-дата |
time_layout.Layout | "01/02 03:04:05PM '06 -0700" | Эталонный шаблон Go |
Примеры
now().format(time_layout.RFC3339)
event.as_string("ECS.Timestamp").parse_time(time_layout.RFC3339)
event.as_string("ECS.Timestamp").parse_time([time_layout.RFC3339, time_layout.DateTime])
now().truncate(duration("1h"))
IP-адреса и CIDR
| Функция | Возврат | Описание |
|---|
v.in_cidr(cidr) | bool | Проверить вхождение IP-адреса в подсеть CIDR |
Функция работает с:
- string — IP-адрес как строка (
"192.168.1.1")
- bytes — IP-адрес как байты
- list — список IP-адресов (возвращает
true, если хотя бы один входит в CIDR)
Параметр CIDR: "10.0.0.0/8" или просто IP "10.0.0.1" (подразумевается /32).
Примеры
event.FwdIP.in_cidr("192.168.0.0/16")
event.AssetIPs.in_cidr("10.0.0.0/8")
event.FwdIP.in_cidr("172.16.0.0/12") || event.FwdIP.in_cidr("10.0.0.0/8")
MIME-преобразования
| Функция | Возврат | Описание |
|---|
b.mime(type) | dyn | Преобразовать байты через зарегистрированный MIME-обработчик |
Поддерживаемые MIME-типы
| MIME-тип | Описание | Возврат |
|---|
"application/gzip" | Декомпрессия gzip | bytes |
"text/csv; header=present" | Парсинг CSV с заголовком | list(map(string, string)) |
"text/csv; header=absent" | Парсинг CSV без заголовка | list(list(string)) |
"application/x-ndjson" | Парсинг newline-delimited JSON | list(dyn) |
"application/zip" | Извлечение ZIP-архива | map ("File", "Comment") |
Примеры
bytes(event.Raw).mime("application/gzip")
bytes(event.Raw).mime("text/csv; header=present")
Коллекции
Макрос as
| Макрос | Описание |
|---|
v.as(var, expr) | Привязать значение к переменной и вычислить выражение. Эквивалент [v].map(var, expr)[0] |
Работа с картами (map)
| Функция | Возврат | Описание |
|---|
m.keys() / keys(m) | list | Отсортированный список ключей |
m.values() / values(m) | list | Список значений (отсортирован по ключам) |
m.with(other) | map | Слияние карт (все поля из other добавляются/заменяются) |
m.with_replace(other) | map | Слияние: только замена существующих полей |
m.with_update(other) | map | Слияние: только добавление новых полей (без перезаписи) |
m.collate(path) | list(dyn) | Обход по пути через точку, сбор значений |
m.collate(paths) | list(dyn) | Обход по нескольким путям |
m.drop(path) | map | Удалить поле по пути через точку |
m.drop(paths) | map | Удалить поля по нескольким путям |
m.drop_empty() | map | Рекурсивно удалить пустые списки и карты |
Работа со списками (list)
| Функция | Возврат | Описание |
|---|
l.min() / min(l) | dyn | Минимальное значение |
l.max() / max(l) | dyn | Максимальное значение |
l.sum() / sum(l) | int/double | Сумма элементов |
min(a, b) | dyn | Минимум из двух значений |
max(a, b) | dyn | Максимум из двух значений |
front(l, n) | list | Первые n элементов |
tail(l) | list | Все элементы после первого |
tail(l, n) | list | Все элементы после индекса n |
l.zip(other) / zip(l, other) | map | Объединить два равных списка в карту |
l.collate(path) | list(dyn) | Обход элементов по пути |
l.drop(path) | list | Удалить поле из каждого элемента |
l.drop_empty() | list | Рекурсивно удалить пустые элементы |
Стандартные расширения (ext.Lists, ext.Sets)
| Функция | Возврат | Описание |
|---|
l.flatten() | list | Плоский список из вложенных списков |
l.slice(start, end) | list | Срез списка |
l.sort() | list | Сортировка |
l.distinct() | list | Уникальные элементы |
sets.contains(l, sub) | bool | Содержит ли список все элементы подсписка |
sets.intersects(a, b) | bool | Есть ли общие элементы |
sets.equivalent(a, b) | bool | Эквивалентны ли множества |
math.greatest(a, b) | dyn | Максимум (ext.Math) |
math.least(a, b) | dyn | Минимум (ext.Math) |
Примеры
["key1", "key2"].zip(["val1", "val2"])
{"a": 1}.with({"b": 2})
data.collate("users.name")
event.Raw.re_find_submatch('user (\\w+)').as(parts, size(parts) > 1 ? parts[1] : '')
Обработка ошибок
| Функция | Возврат | Описание |
|---|
try(expr) | dyn | Если expr — ошибка, возвращает строку ошибки; иначе — значение |
try(expr, key) | dyn | Если expr — ошибка, возвращает {key: "сообщение ошибки"} |
is_error(expr) | bool | true, если выражение — ошибка |
Функции try и is_error являются нестрогими — они принимают ошибки как аргументы вместо того, чтобы прерывать вычисление.
Примеры
try(event.as_string("Raw").decode_json())
is_error(event.as_string("data").decode_json()) ? "invalid" : "valid"
try(event.Raw.decode_json(), "parse_error")
Агрегация (fold)
| Макрос | Описание |
|---|
list.fold(var, acc, init, expr) | Свёртка списка: итерация с привязкой элемента к var, аккумулятор acc с начальным значением init, выражение expr на каждом шаге |
Примеры
[1, 2, 3, 4].fold(x, acc, 0, acc + x)
["a", "b", "c"].fold(x, acc, "", acc + x)
Привязка переменных (cel.bind)
| Макрос | Описание |
|---|
cel.bind(var, init, expr) | Привязать значение init к переменной var и вычислить expr |
Примеры
cel.bind(parts, event.Raw.re_find_submatch('user (\\w+) from (\\S+)'),
size(parts) > 2 ? parts[1] + "@" + parts[2] : "unknown"
)
cel.bind(x, event.vars('Normalizer.Parts')[?2].orValue(''),
x == 'logged in' ? 'success'
: (x == 'authentication failed' ? 'failure' : 'unknown')
)
Опциональные типы
Поддержка опциональных типов позволяет безопасно работать с отсутствующими значениями.
| Выражение | Описание |
|---|
list[?i] | Безопасный доступ к элементу списка (возвращает optional) |
map[?key] | Безопасный доступ к ключу карты (возвращает optional) |
opt.orValue(default) | Получить значение или default, если отсутствует |
optional.of(value) | Создать optional со значением |
optional.none() | Создать пустой optional |
Примеры
event.vars('parts')[?1].orValue('')
data[?"optional_field"].orValue("default_value")
EQL-функции
Функции, совместимые с Elastic Query Language (EQL):
| Функция | Возврат | Описание |
|---|
eql_substring(s, start) | string | Подстрока от позиции до конца (отрицательный индекс — с конца) |
eql_substring(s, start, end) | string | Подстрока [start:end) |
eql_indexOf(s, sub) | int | Индекс первого вхождения подстроки (-1 если нет) |
eql_indexOf(s, sub, start) | int | Индекс вхождения после позиции start |
eql_between(s, left, right) | string | Текст между левым и правым разделителями |
eql_between(s, left, right, greedy) | string | С жадным поиском правого разделителя |
eql_between(s, left, right, greedy, insensitive) | string | С жадным поиском и без учёта регистра |