ДокументацияИнженерия контента1. Конвейер контента — обзор

Конвейер контента — обзор

Почему это важно. Каждая последующая глава настраивает один узел единого конвейера. Если держать в голове весь путь — где алерт входит, что его преобразует, где настраивается каждое преобразование и в каком порядке — то отдельные экраны перестают быть изолированными формами и становятся шагами одной сборки. Ошибитесь в порядке (например, layout ссылается на поле, которое маппер никогда не заполняет) — и экран кейса выглядит сломанным по причинам, невидимым на любой отдельной странице.

1.1. Путь алерта

Внешний алерт становится кейсом SOARForge, проходя фиксированный путь. Каждый переход либо перемещает данные, либо преобразует их:

  ┌─────────────────┐
  │  Source system  │   SIEM / EDR / mailbox / ticketing / custom
  └────────┬────────┘
           │  a) connector instance polls  (Fetch Incidents)
           │  b) source POSTs  → POST /api/v1/ingest   (API key)
           │  c) inbound webhook  (create_case)
           ▼
  ┌─────────────────────────────┐
  │  RabbitMQ ingest            │   exchange  soar.ingest
  │  (queue soar.ingest.alerts) │   HTTP 202 Accepted — async from here
  └──────────────┬──────────────┘
                 ▼
  ┌───────────────────────────────────────────────┐
  │  ingest processing                             │
  │   1. Classifier  → resolves the incident type  │
  │   2. Incoming mapper → fills the case fields    │
  │   3. Case row written to PostgreSQL            │
  │   4. Outbox event enqueued (never dual-write)  │
  └──────────────┬─────────────────────────────────┘
                 │
       ┌─────────┴──────────┐
       ▼                    ▼
  ┌──────────┐      ┌────────────────────────────┐
  │ Case row │      │ Outbox worker → OpenSearch  │
  │  in PG   │      │ (raw alert + full-text)     │
  └────┬─────┘      └────────────────────────────┘
       │
       ▼
  ┌──────────────────────────────────────────────┐
  │  Case screen                                  │
  │   • Layout renders the fields & widgets       │
  │   • Post-processing / playbooks / automations │
  │     run and write their results back          │
  └──────────────────────────────────────────────┘

Правило за диаграммой: приём алертов всегда асинхронный и всегда идёт через RabbitMQ. Опрашивает ли источник коннектор, отправляет ли он POST /api/v1/ingest или дёргает webhook — payload попадает на один и тот же exchange soar.ingest. HTTP-вызов немедленно возвращает 202 Accepted — в этот момент кейса ещё нет; он строится мгновением позже консьюмером ingest.

1.2. Этапы по порядку

Source system (система-источник). Система, которая поднимает алерт. SOARForge всё равно, что это, лишь бы её алерт можно было выразить как JSON-payload.

Connector instance (fetch). Коннектор описывает, как разговаривать с источником; инстанс — это одно настроенное подключение (учётные данные, ящик, регион). Инстанс с включённым Fetch Incidents опрашивает источник по своему Fetch interval и подаёт каждый алерт в очередь ingest. Источник может и сам проталкивать данные без опрашивающего инстанса — через POST /api/v1/ingest (аутентификация ingest-ключом API) или через входящий webhook.

RabbitMQ ingest. Эндпоинт POST /api/v1/ingest валидирует payload (source, опциональные sourceInstance / sourceRef, сам объект payload и опциональные вложения-доказательства), затем публикует его на exchange soar.ingest и возвращает 202. Ничто ниже по потоку не блокирует вызывающую сторону.

Classifier (классификатор). Консьюмер ingest первым запускает классификатор инстанса. Классификатор инспектирует одно поле payload и маршрутизирует алерт к типу инцидента на основании значения этого поля; если ни одно правило не совпало, используется fallback incident type классификатора. Один классификатор принадлежит инстансу и определяет тип инцидента до того, как случится любой маппинг полей.

Incoming mapper (входящий маппер). Как только тип инцидента известен, входящий маппер заполняет поля кейса из payload. На инстанс прикреплён один входящий маппер: его Common-маппинг применяется к каждому типу инцидента, а ветки Specific добавляют маппинги полей для конкретных типов. Каждый маппинг поля может прогонять цепочку трансформеров (переформовать значение) и фильтров (условия where, которые оставляют или отбрасывают элементы) — разбирается в главах 4 и 5.

Case created (кейс создан). Консьюмер записывает строку кейса в PostgreSQL. Три JSON-колонки несут контент, который производят маппер и автоматизация (см. §1.4), и в очередь ставится событие outbox. SOARForge никогда не пишет в OpenSearch из кода приложения — outbox-воркер является единственным путём, который проецирует сырой алерт и полнотекст в поиск.

Layout renders (layout отрисовывается). Когда аналитик открывает кейс, layout, привязанный к его типу инцидента, решает, какие поля и виджеты появляются и в каком порядке. Layout может показать только то поле, которое действительно существует — вот почему создание и маппинг полей идут до работы над layout-ом.

Playbooks & automations (плейбуки и автоматизации). Правила пост-создания и плейбуки (DAG из узлов действия / логики / человека) отрабатывают на кейсе и записывают свои результаты в колонку context_outputs, где их могут читать виджеты layout-а и другие автоматизации.

1.3. Где настраивается каждый этап

Каждый узел конвейера отображается на один экран Настроек. Крайняя правая колонка — это глава, которая разбирает его подробно.

Этап конвейера Расположение в Настройках Маршрут Глава
Connector & instance (fetch) Integrations → Connectors /settings/connectors 2
Код кастомной интеграции Integrations → Integration IDE /settings/integrations/editor 2
Classifier Objects Setup → Incident Types (classifier) /admin/incident-types 3
Incoming / outgoing mapper Objects Setup → Incident Types (mappers) /admin/incident-types 4
Filters & transformers Внутри строк полей входящего маппера /admin/incident-types 5
Custom fields Objects Setup → Incident Types → objects → Incidents (fields) /admin/incident-types 6
Layout Objects Setup → Layouts /admin/layouts 7
War Room panels Objects Setup → War Room /admin/war-room 7
Playbooks Playbooks /playbooks 8
Automations (scripts) Automations /automations 8

Один экран, много объектов. Классификатор, мапперы, типы инцидентов и их поля, дедупликация и правила pre/post-processing — всё живёт под одной поверхностью Objects Setup → Incident Types (/admin/incident-types), сгруппированное по стадии жизненного цикла (Classification → Enrichment → Automation). Именно там вы проведёте бо́льшую часть времени сборки.

1.4. Как хранятся данные инцидента

Инженер контента должен знать, куда на самом деле попадают данные, которые он маппит, потому что три колонки хранения ведут себя очень по-разному. Строка кейса намеренно держит их раздельно:

Колонка Что держит Изменяемость
raw_json Исходный payload алерта, ровно как он прибыл Неизменяема — никогда не переписывается
custom_fields Вывод входящего маппера (канонические + вендорские поля) Переписывается маппером
context_outputs Выводы плейбуков и интеграций, произведённые после создания Дополняется автоматизацией
labels Материализованные пары ключ/значение меток Управляется ingest / правилами

Когда кейс читается, SOARForge собирает всё это в одно представление инцидента (incident view). Системные поля (severity, status, name, owner, source, type…) лежат в корне, а те же значения плюс сопоставленные custom_fields вложены под incident.*, при этом нетронутый оригинал доступен по incident.rawJSON. Результаты плейбуков (context_outputs) сливаются в корень того же представления. Вот почему выражение преобразования данных адресует сопоставленные данные как ${incident.severity}, а первозданный оригинал — как ${incident.rawJSON.…}: они приходят из разных колонок.

Практическое следствие. Если маппинг выглядит неправильно на экране кейса, сравните incident.rawJSON (что на самом деле прибыло) с сопоставленным полем (что произвёл маппер). Разрыв всегда в классификаторе, маппере или layout-е — никогда в неизменяемом оригинале.

1.5. Рекомендуемый порядок сборки

Главы упорядочены так, как вам следует собирать, потому что каждый этап зависит от предыдущего:

  1. Connector & instance (гл. 2) — подключите источник и вытяните реальный образец алерта. Без образцов данных вы маппите вслепую.
  2. Classifier (гл. 3) — решите, каким типом инцидента становится каждый алерт. Ветки Specific маппера завязаны на тип инцидента, поэтому классифицируйте первым.
  3. Incoming mapper (гл. 4) — заполните поля кейса из payload.
  4. Filters & transformers (гл. 5) — очистите и переформуйте отдельные значения внутри маппера.
  5. Custom fields (гл. 6) — если модели нужно поле, которого нет в поставке платформы, создайте его до того, как маппить в него или размещать на layout-е.
  6. Layouts (гл. 7) — решите, что видит аналитик. Layout может показать только поля, которые уже существуют и заполняются.
  7. Playbooks & automations (гл. 8) — прикрепите логику реагирования, которая отрабатывает на готовом кейсе.

Почему порядок не обсуждается. Layout, ссылающийся на несопоставленное поле, отрисовывается пустым; ветка Specific маппера для типа инцидента, который классификатор никогда не выбирает, никогда не отрабатывает; плейбук, читающий ключ context_outputs, который ни один узел не пишет, не получает ничего. Сборка задом наперёд — самая частая причина «выглядит сломанным, но ошибок нет».

Распространённые ошибки

  • Ожидать, что кейс существует в тот же миг, как /ingest вернул ответ. Эндпоинт отвечает 202 Accepted, а кейс строится асинхронно из очереди soar.ingest. Проверяйте, ища кейс мгновением позже, а не по телу HTTP-ответа.
  • Маппить в raw_json. Нельзя — это неизменяемый оригинал. Вывод маппера всегда попадает в custom_fields; вывод автоматизации — в context_outputs. Тянуться к оригиналу в выражении означает incident.rawJSON.…, а не сопоставленное поле.
  • Классифицировать после маппинга у себя в голове. Классификатор отрабатывает первым и выбирает тип инцидента; ветка Specific входящего маппера выбирается этим типом. Если маппинг для конкретного типа никогда не срабатывает, проверьте классификатор прежде маппера.
  • Собирать layout первым. Layout перечисляет только существующие поля. Создайте кастомное поле и убедитесь, что маппер его заполняет, прежде чем пытаться его разместить.
  • Считать поиск авторитетным. Поисковая копия в OpenSearch — это проекция, записанная outbox-воркером; строка кейса в PostgreSQL — источник истины. Небольшое отставание между ними — норма, а не потеря данных.