Меня зовут Паша Пилькевич, и я работаю инженером-разработчиком в команде разработки программного обеспечения STEP LOGIC Security Data Lake (SDL).
Эта статья будет полезна для тех, кто работает в центре кибербезопасности (Security Operation Center, SOC) или планирует его построить. Я расскажу о том, как мы решили нетривиальные задачи контент-менеджмента и какие технологии для этого применялись.

Немного о STEP SDL
Наша платформа предоставляет руководителям и специалистам центров кибербезопасности набор инструментов анализа больших массивов машинных данных для наблюдения за состоянием защищённости вычислительной сети и управления инцидентами информационной безопасности. SDL реализует принцип единого окна доступа к данным о событиях, активах, угрозах и инцидентах. Он обеспечивает их наглядное представление и корреляцию с использованием единого конструктора визуализаций, языков поисковых запросов и описания алгоритмов. Особенности продукта – полноценная мультитенантность, гибкость и встроенная функциональность сразу нескольких классов решений, таких как SIEM и IRP/SOAR.
Проблематика области
В SIEM и SOAR системах присутствует набор настроек и алгоритмов анализа данных, которые можно назвать одним словом – контент. К нему относятся дашборды, визуализации, алгоритмы синтаксического анализа и нормализации данных, правила обнаружения инцидентов, правила хранения данных, настройки модели данных и другие сущности. Этот контент необходимо разрабатывать, изменять, переносить между инсталляциями, обновлять, тестировать, контролировать и т. д.
В активно используемых инсталляциях, а особенно, когда система функционирует в мультитенантном режиме, проблема обеспечения актуальности и качества контента становится одной из важнейших. Например, в публичных коммерческих SOC количество сущностей контента и их параметров под одного клиента исчисляется трёхзначными цифрами.
Анализ существующих продуктов подобного класса показал, что эффективное централизованное управление контентом в SOC – нерешённая и острая проблема.
Какую функциональность хотелось получить
На начальном этапе разработки мы проанализировали проблемные вопросы и определили главные требования к итоговому компоненту.
-
Определение конфигурации всего контента SDL в удобном формате в одном месте для наглядного анализа и контроля внесения изменений.
-
Задание эталонного контента с возможностью самостоятельной шаблонизации и параметризации сущностей в формате jinja2 для задания собственных изменяемых параметров и их групп в любом месте.
-
Поддержка управления конфигурацией как кодом через центральный репозиторий для совместной разработки и бесшовной передачи изменений контента между инсталляциями SDL (встраивание в CI/CD).
-
Валидация контента на соответствие заданным спецификациям для снижения количества ошибок при его разработке и внесении изменений.
-
Возможность контроля целостности инсталляции для обеспечения целостности и уверенности, что в production-инсталляции содержится только работоспособный, протестированный и согласованный между собой контент.

Забегая вперёд, хочу сказать, что всё из перечисленного нам удалось. Далее рассказ пойдет о том, как мы реализовали возможность шаблонизации контента.
Шаблонизация: почему весь процесс конфигурирования построен вокруг неё
Шаблон – это текстовый файл, в котором может быть определена логика генерации содержимого с помощью специальных операторов и переменных. Например, вы можете передавать в один и тот же файл различное значение переменной «severity» (низкая, средняя, высокая), а в шаблоне, вместо конкретной важности, обратиться к переменной: %%%severity%%%.

Наше первое концептуальное решение в вопросе контент-менеджмента заключалось в том, чтобы предоставить пользователю не файлы контента, а шаблоны контента.
На выходе пользователь может кастомизировать сущности абсолютно так, как он хочет, опираясь на свои нужды и специфику системы. Особенно полезны возможности использования условных операторов, обычного проброса переменных и, в некоторых случаях, циклов. Это также позволяет предоставлять пользователю автоматически тиражируемый контент. Теперь не нужно беспокоиться о том, что какой-либо префикс не был заменён или что после очередного обновления появилось новое дополнительное тиражируемое поле, которое необходимо добавлять в существовавший до этого модуль тиражирования. То есть операция тиражирования теперь в принципе не является головной болью пользователя, так как она совершается каждый раз автоматически при генерации фактического контента из шаблонов.
В качестве шаблонизатора была выбрана ныне известная библиотека jinja2 ввиду её широкого распространения, удобства использования и наличия всего необходимого функционала.

Как я упомянул ранее, одна из самых важных задач, решаемых с помощью шаблонов, – простая передача переменных в шаблон файла сущности, когда пользователь, например, хочет изменить поведение правила корреляции в зависимости от клиента, к которому оно относится.
Но где объявлять переменные, применяемые к шаблону? Ранее мы формировали файл, содержащий в себе перечень всех имеющихся рабочих пространств, объектов защиты и контента, который относится к ним. Такое решение позволяло открыть один конфигурационный файл и получить представление о состоянии инсталляции, но функциональной составляющей в нем не было.
Мы развили эту идею и теперь предлагаем централизованно управлять инсталляцией, редактируя только один файл конфигурации SDL. В нём по-прежнему находится перечень всех клиентов и спейсов/теннантов, нетиражируемых шаблонов контента. При этом присутствует возможность передавать любые переменные в конкретный шаблон, добавление или удаление строк с сущностями в конфиге влияет на поведение модуля SCM (добавление/удаление фактических сущностей из инсталляции).
Помимо перечисленного, мы приняли решение сделать центральный конфигурационный файл шаблоном jinja2, чтобы пользователи могли определять статические переменные в нём для сокращения объёма файла конфига. Теперь подробнее о самом файле конфигурации.
Единый файл конфигурации STEP SDL
Файл имеет название installation.yml.j2 и в него входит
-
Секция рабочих пространств (spaces)
-
Секция объектов защиты (clients)
-
Секция нетиражируемого контента (common)
Тиражируемые секции представляют собой массивы объектов, в каждом из них определены следующие поля
-
Идентификатор спейса или клиента (строка)
-
Перечень типов сущностей, имеющихся в инсталляции для заданного спейса/клиента (объект)
В объекте типа сущности храним информацию следующего характера
-
Путь до файла или папки (если сущность является составной) в специальной директории для хранения пользовательского контента (строка)
-
Перечень кастомных переменных, относящихся к конкретному шаблону сущностей. Внутри секции variables можно указать любой тип, который поддерживается YAML
Нетиражируемая секция common имеет такую же структуру, за исключением того, что в ней хранится не массив объектов, а объекты в чистом виде, так как они являются общими для всех клиентов и спейсов.
installation.yml.j2
spaces:
- space_id: <идентификатор спейса>
<тип сущности>:
- path: <путь до файла/папки сущности в каталоге sdl_content>
variables:
<имя переменной для сущности>: <значение переменной>
clients:
- client_id: <идентификатор клиента>
<тип сущности>:
- path: <путь до файла/папки сущности в каталоге sdl_content>
variables:
<имя переменной для сущности>: <значение переменной>
common:
<тип сущности>:
- path: <путь до файла/папки сущности в каталоге sdl_content>
variables:
<имя переменной для сущности>: <значение переменной>
Путь до самих сущностей необходимо указать в формате относительного пути от директории, содержащей весь пользовательский контент. Такое решение обусловлено нашим желанием дать возможность пользователю группировать контент на свой вкус. Это также облегчает фактическое ориентирование по конфигурации, так как понятно, на какой именно файл ссылается та или иная строка в конфиге.
Сам конфигурационный файл, как и контент, с которым работает пользователь, представлен в формате .j2, что позволяет применять шаблонизацию прямо внутри конфигурации.
Например, у вас есть набор корреляционных правил, которые нужно распространить на всех имеющихся клиентов. В таком случае вы можете определить статическую переменную со списком правил, а ниже, в фактической конфигурации инсталляции, обращаться к этой переменной.

Помимо работы с переменными, арсенал уверенного конфигуратора инсталляций можно пополнить условными операторами и циклами, которые позволяют сократить объём конфигурации, что актуально в производственных масштабах, когда вы настраиваете инсталляцию для, скажем, 40, 100 и более клиентов.
Кейсы использования SCM в SOC
А теперь самое время рассмотреть некоторые из возможных сценариев управления контентом, которые возникают в SOC.
Кейс №1: тиражирование нового тенанта c набором контента и параметрами «по умолчанию»
В первом случае у нас есть учётные данные и адрес (и ничего более, никаких конфигурационных файлов, никаких файлов контента) инсталляции SDL, контент которой нужно тиражировать для нового клиента. Что будем делать?
Открываем дистрибутив SCMи с помощью операции синхронизации автоматически получаем текущее состояние конфигурации. После выполнения данной операции открываем центральный конфигурационный файл и видим, что в инсталляции сейчас присутствует одно рабочее пространство и один клиент:
spaces:
- space_id: first_space
clients:
- client_id: first_client
common: {}
Для добавления нового клиента нужно всего лишь указать его в данном файле и запустить операцию по внесению изменений.
spaces:
- space_id: first_space
clients:
- client_id: first_client
- client_id: second_client
common: {}
Таким образом, мы всегда можем получить текущее состояние конфигурации от неё самой, нам не нужно задаваться вопросом о получении конфига и его актуальности. При этом само управление контентом стало значительно быстрее, ведь полный набор контента для объекта защиты загружается в инсталляцию с помощью написания одной строчки и запуска двух операций.
Кейс №2: тиражирование пользовательского контента
Нам необходимо добавить в инсталляцию новое правило обнаружения для двух клиентов, но поведение при срабатывании правила различается.
В данном случае за описанное требование к поведению отвечает системное поле result_type: для первого клиента нам необходимо значение «Writable», а для второго «Notable».
Необходимо сложить шаблон пользовательского правила в папку для кастомного контента и указать это правило для двух клиентов. Для гибкого управления поведением правила в зависимости от клиента нужно создать переменную с любым наименованием и необходимым значением для добавленного правила:
spaces:
- space_id: first_space
clients:
- client_id: first_client
sdl-detection-rules:
- path: rules/windows_account_added_to_privileged_group
variables:
result_type: Writable
- client_id: second_client
sdl-detection-rules:
- path: rules/windows_account_added_to_privileged_group
variables:
result_type: Notable
common: {}
В самом шаблоне пользовательского правила обнаружения обращение к переменной будет по тому же имени, которое было задано в центральном конфигурационном файле:

Благодаря такому функционалу пользователь может централизованно хранить и передавать абсолютно любые параметры любого типа для своего контента, тем самым гибко управлять поведением сущностей для каждого отдельного объекта защиты и рабочего пространства.
Кейс №3: использование условных и дефолтных значений при шаблонизации в кастомном контенте, применение циклов для динамической генерации сущностей
Часто возникает потребность реализовать поведение, когда пользователь может либо задать параметр, который бы подставился в необходимом месте в шаблоне, либо не задавать его и использовать значение по умолчанию.
Для реализации такого поведения необходимо применить условный оператор в шаблоне сущности:

В центральном конфигурационном файле же необходимо определить переменную «url» для тех клиентов, где мы бы хотели подставить своё значение адреса:
spaces:
- space_id: first_space
clients:
- client_id: first_client
sdl-detection-rules:
- path: rules/windows_account_added_to_privileged_group
variables:
result_type: Writable
- client_id: second_client
sdl-detection-rules:
- path: rules/windows_account_added_to_privileged_group
variables:
result_type: Notable
url: https://192.168.1.100:9562/api/new/address
common: {}
Расширенное использование функций шаблонизации позволяет значительно упростить работу с контентом, уменьшить объём шаблонов сущностей и динамически определять содержимое итоговых файлов.
Часто можно встретить сущности, содержимое которых зависит, например, от присутствующих объектов защиты в инсталляции. Каждый раз, когда добавляется новый объект защиты, возникает необходимость добавлять однотипный элемент в файл сущности. Но благодаря возможностям шаблонизации теперь можно определить переменную с перечнем всех имеющихся клиентов, а в шаблоне использовать цикл, который бы автоматически генерировал те однотипные элементы за нас:

Центральный конфигурационный файл необходимо дополнить переменными списка клиентов, для которых будет генерироваться данная конструкция:
spaces:
- space_id: first_space
clients:
- client_id: first_client
sdl-detection-rules:
- path: rules/windows_account_added_to_privileged_group
variables:
result_type: Writable
rule_clients:
- first_client
- second_client
- client_id: second_client
sdl-detection-rules:
- path: rules/windows_account_added_to_privileged_group
variables:
result_type: Notable
url: https://192.168.1.100:9562/api/new/address
rule_clients:
- first_client
- second_client
common: {}
Кейс №4: использование шаблонизации в центральном файле конфигурации
В инсталляции присутствует несколько клиентов и множество правил обнаружения, причём, часть правил должны относиться ко всем объектам защиты, а часть – подобраны под конкретного клиента и не должны быть задействованы в других. Как заполнить конфигурационный файл, чтобы не повторять множество строк с обязательными правилами обнаружения и при этом оставить возможность индивидуального управления клиентами? Использовать шаблоны, конечно.
Мы можем представить объект клиента как переменную шаблона, объявить списки с общими и индивидуальными правилами и объединить их средствами jinja2:
{% set default_rules = [{"path": "default-rule"}] %}
{% set client1_rules = [{"path": "rule1"}, {"path": "rule2"}] %}
{% set client2_rules = [{"path": "rule3"}, {"path": "rule4"}] %}
{%
set client1 = {
"client_id": "client1",
"sdl-detection-rules": default_rules + client1_rules
}
%}
{%
set client2 = {
"client_id": "client2",
"sdl-detection-rules": default_rules + client2_rules
}
%}
spaces: []
clients:
- %%% client1 %%%
- %%% client2 %%%
common: {}
В результате получим следующий конфигурационный файл:
spaces: []
clients:
- client_id: client1
sdl-detection-rules:
- path: default-rule
- path: rule1
- path: rule2
- client_id: client2
sdl-detection-rules:
- path: default-rule
- path: rule3
- path: rule4
common: {}
Выводы, задел на будущее
Проблема управления изменяемым контентом в системах мониторинга и анализа машинных данных обостряется по мере развития и масштабирования SOC. SDL Content Manager позволяет централизованно и гибко управлять всем аналитическим контентом SOC, отслеживая при этом каждое принятое изменение.
Основным нововведением стало применение эталонного контента в виде шаблонов с возможностью параметризации любых значений или целых разделов под отдельные группы тенантов (объектов защиты, рабочие пространства). При этом дополнительное удобство управления конфигурацией обеспечивается за счёт встраивания SDL Content Manager в процедуры непрерывной сборки/развертывания контента, с контролем и согласованием изменений при их переносе из среды разработки в production-среду.
Рассказ о других функциях STEP LOGIC Security Data Lake (SDL), таких как адаптивные действия, дерево процессов, цепочки инцидентов, сводный перечень активов и другие, продолжу в следующих статьях. Оставайтесь на связи.
Автор: Delonce