В наш век технологий обработка данных и мониторинг систем становятся критически важными. Поэтому для обеспечения бесперебойной работы я часто использую в своих проектах Elastic Stack (ранее известный как ELK Stack), позволяющий собирать, хранить и визуализировать огромные объемы данных. Этот подход широко применяется для анализа логов, мониторинга инфраструктуры, создания отчетов, а также для обнаружения аномалий и ошибок.
Одной из ключевых задач при работе с Elastic Stack является настройка эффективной системы уведомлений. Это позволяет оперативно реагировать на происходящие события и минимизировать последствия сбоев или атак. В таком контексте фреймворк ElastAlert 2 представляет собой мощный инструмент, который значительно расширяет возможности встроенных уведомлений Elastic Stack, обеспечивая гибкость и детализацию алертов.
В этой статье в блоге ЛАНИТ я расскажу о том, какое одно универсальное правило для мониторинга событий в ElastAlert 2 охватывает все основные варианты условий срабатывания. Рассмотрим, как можно использовать ElastAlert 2 для расширения функциональности алертинга в Kibana и как интегрировать оба инструмента для более эффективного мониторинга событий.

Как начинал с ElastAlert 2 и с какими трудностями столкнулся
На начальных этапах использования Elastic Stack в моей практике фреймворк ElastAlert 2 был основным инструментом для настройки алертов. Он предоставлял гибкие возможности для создания правил алертинга, которые позволяли отслеживать события в Elasticsearch (открытая распределенная система поиска и управления данными) и реагировать на них.
ElastAlert 2 идеально подходил для задач, где требовалась тонкая настройка условий срабатывания. Каждое правило в ElastAlert 2 было настроено на основе определенных фильтров, запросов в Elasticsearch и дополнительных условий. По мере увеличения их числа и отслеживаемых объектов мы с коллегами обнаружили ряд неудобств при работе с правилами в ElastAlert 2.
Большое количество правил. Мы создавали отдельные правила для разных типов событий и данных. Со временем их количество увеличилось до такой степени, что ими было сложно управлять и поддерживать в актуальном состоянии. Каждое новое правило требовало индивидуальной настройки, что добавляло много ручной работы.
Отсутствие централизованного управления. Стало трудно контролировать и изменять правила алертинга. Чтобы обновить или изменить настройку алерта, нужно было редактировать каждый YAML-файл, работать с системой контроля версий, запускать ansible и т.п.
Трудности с обновлениями и совместимостью. Правила становились сложными, обновление версии ElastAlert 2 или Elastic Stack требовало тестирования или переписывания каждого. Иногда изменения в одной части конфигурации могли повлиять на другие, что приводило к некорректной работе.
Когда в Kibana был внедрен механизм Alerts, мы начали постепенно переходить на новые возможности для настройки алертов, что значительно упростило управление уведомлениями. Новый функционал в Kibana позволил централизованно настраивать алерты прямо из интерфейса Kibana. Механизм Alerts в Kibana тесно связан с правилами (Rules) и представляет собой основу для настройки и управления уведомлениями в системе мониторинга.
Все алерты в Elastic Stack сводятся к шести категориям, но есть нюанс


Накапливая опыт работы в Elastic Stack c правилами и алертами, я пришел к выводу, что все правила в Elastic Stack делятся на несколько категорий в зависимости от типа данных, которые они отслеживают и анализируют. В базовой лицензии Elastic Stack я определил шесть категорий: пять категорий в разделе Observability и одна в разделе Security.
1. Правила для логов
Служат для мониторинга и анализа событий, записанных в логах различных сервисов и приложений. Логи могут включать в себя сообщения об ошибках, событиях, системных сообщениях и другие важные данные.
2. Правила для метрик
Метрики — это данные о состоянии инфраструктуры (использование процессора, памяти, сети, дисков и другие показатели производительности серверов и приложений).
3. Правила для APM (Application Performance Monitoring)
Предназначены для мониторинга производительности приложений. Они отслеживают время отклика сервисов внутри приложения, ошибки, а также другие показатели, связанные с производительностью.
4. Правила для Uptime
Обеспечивают мониторинг доступности сервисов и веб-сайтов. Также в правила Uptime входят функциональные тесты (Synthetic) и проверки срока действия сертификатов.
5. Пороговые правила (Threshold)
Эта категория охватывает случаи, когда нужно реагировать на превышение порога по количеству событий, метрик или других показателей.
6. Правила по безопасности (Security)
Помогают отслеживать инциденты, связанные с угрозами безопасности в инфраструктуре. Эти правила используются в сочетании с Elastic Enpoint Security (бывший SIEM) и позволяют выявлять такие подозрительные действия, как попытки несанкционированного доступа, атаки и другие угрозы.
После создания правил в Kibana для каждой категории создается системный индекс:
- .internal.alerts-observability.logs.alerts-default-*
"kibana": {
"properties": {
"alert": {
"properties": {
"action_group": {
"type": "keyword"
},
"case_ids": {
"type": "keyword"
},
"consecutive_matches": {
"type": "long"
},
"context": {
"type": "object"
},
"duration": {
"properties": {
"us": {
"type": "long"
}
}
},
"end": {
"type": "date"
},
"evaluation": {
"properties": {
"threshold": {
"type": "scaled_float",
"scaling_factor": 100
},
"value": {
"type": "scaled_float",
"scaling_factor": 100
},
"values": {
"type": "scaled_float",
"scaling_factor": 100
}
}
},
"flapping": {
"type": "boolean"
},
"flapping_history": {
"type": "boolean"
},
"group": {
"properties": {
"field": {
"type": "keyword"
},
"value": {
"type": "keyword"
}
}
},
"instance": {
"properties": {
"id": {
"type": "keyword"
}
}
},
"last_detected": {
"type": "date"
},
"maintenance_window_ids": {
"type": "keyword"
},
"previous_action_group": {
"type": "keyword"
},
"reason": {
"type": "keyword",
"fields": {
"text": {
"type": "match_only_text"
}
}
},
"risk_score": {
"type": "float"
},
"rule": {
"properties": {
"author": {
"type": "keyword"
},
"category": {
"type": "keyword"
},
"consumer": {
"type": "keyword"
},
"created_at": {
"type": "date"
},
"created_by": {
"type": "keyword"
},
"description": {
"type": "keyword"
},
"enabled": {
"type": "keyword"
},
"execution": {
"properties": {
"timestamp": {
"type": "date"
},
"uuid": {
"type": "keyword"
}
}
},
"from": {
"type": "keyword"
},
"interval": {
"type": "keyword"
},
"license": {
"type": "keyword"
},
"name": {
"type": "keyword"
},
"note": {
"type": "keyword"
},
"parameters": {
"type": "flattened",
"ignore_above": 4096
},
"producer": {
"type": "keyword"
},
"references": {
"type": "keyword"
},
"revision": {
"type": "long"
},
"rule_id": {
"type": "keyword"
},
"rule_name_override": {
"type": "keyword"
},
"rule_type_id": {
"type": "keyword"
},
"tags": {
"type": "keyword"
},
"to": {
"type": "keyword"
},
"type": {
"type": "keyword"
},
"updated_at": {
"type": "date"
},
"updated_by": {
"type": "keyword"
},
"uuid": {
"type": "keyword"
},
"version": {
"type": "keyword"
}
}
},
"severity": {
"type": "keyword"
},
"severity_improving": {
"type": "boolean"
},
"start": {
"type": "date"
},
"status": {
"type": "keyword"
},
"suppression": {
"properties": {
"docs_count": {
"type": "long"
},
"end": {
"type": "date"
},
"start": {
"type": "date"
},
"terms": {
"properties": {
"field": {
"type": "keyword"
},
"value": {
"type": "keyword"
}
}
}
}
},
"system_status": {
"type": "keyword"
},
"time_range": {
"type": "date_range",
"format": "epoch_millis||strict_date_optional_time"
},
"url": {
"type": "keyword",
"index": false,
"ignore_above": 2048
},
"uuid": {
"type": "keyword"
},
"workflow_assignee_ids": {
"type": "keyword"
},
"workflow_reason": {
"type": "keyword"
},
"workflow_status": {
"type": "keyword"
},
"workflow_status_updated_at": {
"type": "date"
},
"workflow_tags": {
"type": "keyword"
},
"workflow_user": {
"type": "keyword"
}
}
},
"space_ids": {
"type": "keyword"
},
"version": {
"type": "version"
}
}
}
- .internal.alerts-observability.metrics.alerts-default-*
"kibana": {
"properties": {
"alert": {
"properties": {
"action_group": {
"type": "keyword"
},
"case_ids": {
"type": "keyword"
},
"consecutive_matches": {
"type": "long"
},
"context": {
"type": "object"
},
"duration": {
"properties": {
"us": {
"type": "long"
}
}
},
"end": {
"type": "date"
},
"evaluation": {
"properties": {
"threshold": {
"type": "scaled_float",
"scaling_factor": 100
},
"value": {
"type": "scaled_float",
"scaling_factor": 100
},
"values": {
"type": "scaled_float",
"scaling_factor": 100
}
}
},
"flapping": {
"type": "boolean"
},
"flapping_history": {
"type": "boolean"
},
"group": {
"properties": {
"field": {
"type": "keyword"
},
"value": {
"type": "keyword"
}
}
},
"instance": {
"properties": {
"id": {
"type": "keyword"
}
}
},
"last_detected": {
"type": "date"
},
"maintenance_window_ids": {
"type": "keyword"
},
"previous_action_group": {
"type": "keyword"
},
"reason": {
"type": "keyword",
"fields": {
"text": {
"type": "match_only_text"
}
}
},
"risk_score": {
"type": "float"
},
"rule": {
"properties": {
"author": {
"type": "keyword"
},
"category": {
"type": "keyword"
},
"consumer": {
"type": "keyword"
},
"created_at": {
"type": "date"
},
"created_by": {
"type": "keyword"
},
"description": {
"type": "keyword"
},
"enabled": {
"type": "keyword"
},
"execution": {
"properties": {
"timestamp": {
"type": "date"
},
"uuid": {
"type": "keyword"
}
}
},
"from": {
"type": "keyword"
},
"interval": {
"type": "keyword"
},
"license": {
"type": "keyword"
},
"name": {
"type": "keyword"
},
"note": {
"type": "keyword"
},
"parameters": {
"type": "flattened",
"ignore_above": 4096
},
"producer": {
"type": "keyword"
},
"references": {
"type": "keyword"
},
"revision": {
"type": "long"
},
"rule_id": {
"type": "keyword"
},
"rule_name_override": {
"type": "keyword"
},
"rule_type_id": {
"type": "keyword"
},
"tags": {
"type": "keyword"
},
"to": {
"type": "keyword"
},
"type": {
"type": "keyword"
},
"updated_at": {
"type": "date"
},
"updated_by": {
"type": "keyword"
},
"uuid": {
"type": "keyword"
},
"version": {
"type": "keyword"
}
}
},
"severity": {
"type": "keyword"
},
"severity_improving": {
"type": "boolean"
},
"start": {
"type": "date"
},
"status": {
"type": "keyword"
},
"suppression": {
"properties": {
"docs_count": {
"type": "long"
},
"end": {
"type": "date"
},
"start": {
"type": "date"
},
"terms": {
"properties": {
"field": {
"type": "keyword"
},
"value": {
"type": "keyword"
}
}
}
}
},
"system_status": {
"type": "keyword"
},
"time_range": {
"type": "date_range",
"format": "epoch_millis||strict_date_optional_time"
},
"url": {
"type": "keyword",
"index": false,
"ignore_above": 2048
},
"uuid": {
"type": "keyword"
},
"workflow_assignee_ids": {
"type": "keyword"
},
"workflow_reason": {
"type": "keyword"
},
"workflow_status": {
"type": "keyword"
},
"workflow_status_updated_at": {
"type": "date"
},
"workflow_tags": {
"type": "keyword"
},
"workflow_user": {
"type": "keyword"
}
}
},
"space_ids": {
"type": "keyword"
},
"version": {
"type": "version"
}
}
}
- .internal.alerts-observability.apm.alerts-default-*
"kibana": {
"properties": {
"alert": {
"properties": {
"action_group": {
"type": "keyword"
},
"case_ids": {
"type": "keyword"
},
"consecutive_matches": {
"type": "long"
},
"context": {
"type": "object"
},
"duration": {
"properties": {
"us": {
"type": "long"
}
}
},
"end": {
"type": "date"
},
"evaluation": {
"properties": {
"threshold": {
"type": "scaled_float",
"scaling_factor": 100
},
"value": {
"type": "scaled_float",
"scaling_factor": 100
},
"values": {
"type": "scaled_float",
"scaling_factor": 100
}
}
},
"flapping": {
"type": "boolean"
},
"flapping_history": {
"type": "boolean"
},
"group": {
"properties": {
"field": {
"type": "keyword"
},
"value": {
"type": "keyword"
}
}
},
"instance": {
"properties": {
"id": {
"type": "keyword"
}
}
},
"last_detected": {
"type": "date"
},
"maintenance_window_ids": {
"type": "keyword"
},
"previous_action_group": {
"type": "keyword"
},
"reason": {
"type": "keyword",
"fields": {
"text": {
"type": "match_only_text"
}
}
},
"risk_score": {
"type": "float"
},
"rule": {
"properties": {
"author": {
"type": "keyword"
},
"category": {
"type": "keyword"
},
"consumer": {
"type": "keyword"
},
"created_at": {
"type": "date"
},
"created_by": {
"type": "keyword"
},
"description": {
"type": "keyword"
},
"enabled": {
"type": "keyword"
},
"execution": {
"properties": {
"timestamp": {
"type": "date"
},
"uuid": {
"type": "keyword"
}
}
},
"from": {
"type": "keyword"
},
"interval": {
"type": "keyword"
},
"license": {
"type": "keyword"
},
"name": {
"type": "keyword"
},
"note": {
"type": "keyword"
},
"parameters": {
"type": "flattened",
"ignore_above": 4096
},
"producer": {
"type": "keyword"
},
"references": {
"type": "keyword"
},
"revision": {
"type": "long"
},
"rule_id": {
"type": "keyword"
},
"rule_name_override": {
"type": "keyword"
},
"rule_type_id": {
"type": "keyword"
},
"tags": {
"type": "keyword"
},
"to": {
"type": "keyword"
},
"type": {
"type": "keyword"
},
"updated_at": {
"type": "date"
},
"updated_by": {
"type": "keyword"
},
"uuid": {
"type": "keyword"
},
"version": {
"type": "keyword"
}
}
},
"severity": {
"type": "keyword"
},
"severity_improving": {
"type": "boolean"
},
"start": {
"type": "date"
},
"status": {
"type": "keyword"
},
"suppression": {
"properties": {
"docs_count": {
"type": "long"
},
"end": {
"type": "date"
},
"start": {
"type": "date"
},
"terms": {
"properties": {
"field": {
"type": "keyword"
},
"value": {
"type": "keyword"
}
}
}
}
},
"system_status": {
"type": "keyword"
},
"time_range": {
"type": "date_range",
"format": "epoch_millis||strict_date_optional_time"
},
"url": {
"type": "keyword",
"index": false,
"ignore_above": 2048
},
"uuid": {
"type": "keyword"
},
"workflow_assignee_ids": {
"type": "keyword"
},
"workflow_reason": {
"type": "keyword"
},
"workflow_status": {
"type": "keyword"
},
"workflow_status_updated_at": {
"type": "date"
},
"workflow_tags": {
"type": "keyword"
},
"workflow_user": {
"type": "keyword"
}
}
},
"space_ids": {
"type": "keyword"
},
"version": {
"type": "version"
}
}
}
- .internal.alerts-observability.uptime.alerts-default-*
"kibana": {
"properties": {
"alert": {
"properties": {
"action_group": {
"type": "keyword"
},
"case_ids": {
"type": "keyword"
},
"consecutive_matches": {
"type": "long"
},
"context": {
"type": "object"
},
"duration": {
"properties": {
"us": {
"type": "long"
}
}
},
"end": {
"type": "date"
},
"evaluation": {
"properties": {
"threshold": {
"type": "scaled_float",
"scaling_factor": 100
},
"value": {
"type": "scaled_float",
"scaling_factor": 100
},
"values": {
"type": "scaled_float",
"scaling_factor": 100
}
}
},
"flapping": {
"type": "boolean"
},
"flapping_history": {
"type": "boolean"
},
"group": {
"properties": {
"field": {
"type": "keyword"
},
"value": {
"type": "keyword"
}
}
},
"instance": {
"properties": {
"id": {
"type": "keyword"
}
}
},
"last_detected": {
"type": "date"
},
"maintenance_window_ids": {
"type": "keyword"
},
"previous_action_group": {
"type": "keyword"
},
"reason": {
"type": "keyword",
"fields": {
"text": {
"type": "match_only_text"
}
}
},
"risk_score": {
"type": "float"
},
"rule": {
"properties": {
"author": {
"type": "keyword"
},
"category": {
"type": "keyword"
},
"consumer": {
"type": "keyword"
},
"created_at": {
"type": "date"
},
"created_by": {
"type": "keyword"
},
"description": {
"type": "keyword"
},
"enabled": {
"type": "keyword"
},
"execution": {
"properties": {
"timestamp": {
"type": "date"
},
"uuid": {
"type": "keyword"
}
}
},
"from": {
"type": "keyword"
},
"interval": {
"type": "keyword"
},
"license": {
"type": "keyword"
},
"name": {
"type": "keyword"
},
"note": {
"type": "keyword"
},
"parameters": {
"type": "flattened",
"ignore_above": 4096
},
"producer": {
"type": "keyword"
},
"references": {
"type": "keyword"
},
"revision": {
"type": "long"
},
"rule_id": {
"type": "keyword"
},
"rule_name_override": {
"type": "keyword"
},
"rule_type_id": {
"type": "keyword"
},
"tags": {
"type": "keyword"
},
"to": {
"type": "keyword"
},
"type": {
"type": "keyword"
},
"updated_at": {
"type": "date"
},
"updated_by": {
"type": "keyword"
},
"uuid": {
"type": "keyword"
},
"version": {
"type": "keyword"
}
}
},
"severity": {
"type": "keyword"
},
"severity_improving": {
"type": "boolean"
},
"start": {
"type": "date"
},
"status": {
"type": "keyword"
},
"suppression": {
"properties": {
"docs_count": {
"type": "long"
},
"end": {
"type": "date"
},
"start": {
"type": "date"
},
"terms": {
"properties": {
"field": {
"type": "keyword"
},
"value": {
"type": "keyword"
}
}
}
}
},
"system_status": {
"type": "keyword"
},
"time_range": {
"type": "date_range",
"format": "epoch_millis||strict_date_optional_time"
},
"url": {
"type": "keyword",
"index": false,
"ignore_above": 2048
},
"uuid": {
"type": "keyword"
},
"workflow_assignee_ids": {
"type": "keyword"
},
"workflow_reason": {
"type": "keyword"
},
"workflow_status": {
"type": "keyword"
},
"workflow_status_updated_at": {
"type": "date"
},
"workflow_tags": {
"type": "keyword"
},
"workflow_user": {
"type": "keyword"
}
}
},
"space_ids": {
"type": "keyword"
},
"version": {
"type": "version"
}
}
- .internal.alerts-observability.threshold.alerts-default-*
"kibana": {
"properties": {
"alert": {
"properties": {
"action_group": {
"type": "keyword"
},
"case_ids": {
"type": "keyword"
},
"consecutive_matches": {
"type": "long"
},
"context": {
"type": "object"
},
"duration": {
"properties": {
"us": {
"type": "long"
}
}
},
"end": {
"type": "date"
},
"evaluation": {
"properties": {
"threshold": {
"type": "scaled_float",
"scaling_factor": 100
},
"value": {
"type": "scaled_float",
"scaling_factor": 100
},
"values": {
"type": "scaled_float",
"scaling_factor": 100
}
}
},
"flapping": {
"type": "boolean"
},
"flapping_history": {
"type": "boolean"
},
"group": {
"properties": {
"field": {
"type": "keyword"
},
"value": {
"type": "keyword"
}
}
},
"instance": {
"properties": {
"id": {
"type": "keyword"
}
}
},
"last_detected": {
"type": "date"
},
"maintenance_window_ids": {
"type": "keyword"
},
"previous_action_group": {
"type": "keyword"
},
"reason": {
"type": "keyword",
"fields": {
"text": {
"type": "match_only_text"
}
}
},
"rule": {
"properties": {
"category": {
"type": "keyword"
},
"consumer": {
"type": "keyword"
},
"execution": {
"properties": {
"timestamp": {
"type": "date"
},
"uuid": {
"type": "keyword"
}
}
},
"name": {
"type": "keyword"
},
"parameters": {
"type": "flattened",
"ignore_above": 4096
},
"producer": {
"type": "keyword"
},
"revision": {
"type": "long"
},
"rule_type_id": {
"type": "keyword"
},
"tags": {
"type": "keyword"
},
"uuid": {
"type": "keyword"
}
}
},
"severity_improving": {
"type": "boolean"
},
"start": {
"type": "date"
},
"status": {
"type": "keyword"
},
"time_range": {
"type": "date_range",
"format": "epoch_millis||strict_date_optional_time"
},
"url": {
"type": "keyword",
"index": false,
"ignore_above": 2048
},
"uuid": {
"type": "keyword"
},
"workflow_assignee_ids": {
"type": "keyword"
},
"workflow_status": {
"type": "keyword"
},
"workflow_tags": {
"type": "keyword"
}
}
},
"space_ids": {
"type": "keyword"
},
"version": {
"type": "version"
}
}
}
- .internal.alerts-security.alerts-default-*
"kibana": {
"properties": {
"alert": {
"properties": {
"action_group": {
"type": "keyword"
},
"ancestors": {
"properties": {
"depth": {
"type": "long"
},
"id": {
"type": "keyword"
},
"index": {
"type": "keyword"
},
"rule": {
"type": "keyword"
},
"type": {
"type": "keyword"
}
}
},
"building_block_type": {
"type": "keyword"
},
"case_ids": {
"type": "keyword"
},
"consecutive_matches": {
"type": "long"
},
"depth": {
"type": "long"
},
"duration": {
"properties": {
"us": {
"type": "long"
}
}
},
"end": {
"type": "date"
},
"flapping": {
"type": "boolean"
},
"flapping_history": {
"type": "boolean"
},
"group": {
"properties": {
"id": {
"type": "keyword"
},
"index": {
"type": "integer"
}
}
},
"host": {
"properties": {
"criticality_level": {
"type": "keyword"
}
}
},
"instance": {
"properties": {
"id": {
"type": "keyword"
}
}
},
"last_detected": {
"type": "date"
},
"maintenance_window_ids": {
"type": "keyword"
},
"new_terms": {
"type": "keyword"
},
"original_event": {
"properties": {
"action": {
"type": "keyword"
},
"agent_id_status": {
"type": "keyword"
},
"category": {
"type": "keyword"
},
"code": {
"type": "keyword"
},
"created": {
"type": "date"
},
"dataset": {
"type": "keyword"
},
"duration": {
"type": "keyword"
},
"end": {
"type": "date"
},
"hash": {
"type": "keyword"
},
"id": {
"type": "keyword"
},
"ingested": {
"type": "date"
},
"kind": {
"type": "keyword"
},
"module": {
"type": "keyword"
},
"original": {
"type": "keyword"
},
"outcome": {
"type": "keyword"
},
"provider": {
"type": "keyword"
},
"reason": {
"type": "keyword"
},
"reference": {
"type": "keyword"
},
"risk_score": {
"type": "float"
},
"risk_score_norm": {
"type": "float"
},
"sequence": {
"type": "long"
},
"severity": {
"type": "long"
},
"start": {
"type": "date"
},
"timezone": {
"type": "keyword"
},
"type": {
"type": "keyword"
},
"url": {
"type": "keyword"
}
}
},
"original_time": {
"type": "date"
},
"previous_action_group": {
"type": "keyword"
},
"reason": {
"type": "keyword",
"fields": {
"text": {
"type": "match_only_text"
}
}
},
"risk_score": {
"type": "float"
},
"rule": {
"properties": {
"author": {
"type": "keyword"
},
"building_block_type": {
"type": "keyword"
},
"category": {
"type": "keyword"
},
"consumer": {
"type": "keyword"
},
"created_at": {
"type": "date"
},
"created_by": {
"type": "keyword"
},
"description": {
"type": "keyword"
},
"enabled": {
"type": "keyword"
},
"exceptions_list": {
"type": "object"
},
"execution": {
"properties": {
"timestamp": {
"type": "date"
},
"uuid": {
"type": "keyword"
}
}
},
"false_positives": {
"type": "keyword"
},
"from": {
"type": "keyword"
},
"immutable": {
"type": "keyword"
},
"interval": {
"type": "keyword"
},
"license": {
"type": "keyword"
},
"max_signals": {
"type": "long"
},
"name": {
"type": "keyword"
},
"note": {
"type": "keyword"
},
"parameters": {
"type": "flattened",
"ignore_above": 4096
},
"producer": {
"type": "keyword"
},
"references": {
"type": "keyword"
},
"revision": {
"type": "long"
},
"rule_id": {
"type": "keyword"
},
"rule_name_override": {
"type": "keyword"
},
"rule_type_id": {
"type": "keyword"
},
"tags": {
"type": "keyword"
},
"threat": {
"properties": {
"framework": {
"type": "keyword"
},
"tactic": {
"properties": {
"id": {
"type": "keyword"
},
"name": {
"type": "keyword"
},
"reference": {
"type": "keyword"
}
}
},
"technique": {
"properties": {
"id": {
"type": "keyword"
},
"name": {
"type": "keyword"
},
"reference": {
"type": "keyword"
},
"subtechnique": {
"properties": {
"id": {
"type": "keyword"
},
"name": {
"type": "keyword"
},
"reference": {
"type": "keyword"
}
}
}
}
}
}
},
"timeline_id": {
"type": "keyword"
},
"timeline_title": {
"type": "keyword"
},
"timestamp_override": {
"type": "keyword"
},
"to": {
"type": "keyword"
},
"type": {
"type": "keyword"
},
"updated_at": {
"type": "date"
},
"updated_by": {
"type": "keyword"
},
"uuid": {
"type": "keyword"
},
"version": {
"type": "keyword"
}
}
},
"severity": {
"type": "keyword"
},
"severity_improving": {
"type": "boolean"
},
"start": {
"type": "date"
},
"status": {
"type": "keyword"
},
"suppression": {
"properties": {
"docs_count": {
"type": "long"
},
"end": {
"type": "date"
},
"start": {
"type": "date"
},
"terms": {
"properties": {
"field": {
"type": "keyword"
},
"value": {
"type": "keyword"
}
}
}
}
},
"system_status": {
"type": "keyword"
},
"threshold_result": {
"properties": {
"cardinality": {
"properties": {
"field": {
"type": "keyword"
},
"value": {
"type": "long"
}
}
},
"count": {
"type": "long"
},
"from": {
"type": "date"
},
"terms": {
"properties": {
"field": {
"type": "keyword"
},
"value": {
"type": "keyword"
}
}
}
}
},
"time_range": {
"type": "date_range",
"format": "epoch_millis||strict_date_optional_time"
},
"url": {
"type": "keyword",
"index": false,
"ignore_above": 2048
},
"user": {
"properties": {
"criticality_level": {
"type": "keyword"
}
}
},
"uuid": {
"type": "keyword"
},
"workflow_assignee_ids": {
"type": "keyword"
},
"workflow_reason": {
"type": "keyword"
},
"workflow_status": {
"type": "keyword"
},
"workflow_status_updated_at": {
"type": "date"
},
"workflow_tags": {
"type": "keyword"
},
"workflow_user": {
"type": "keyword"
}
}
},
"space_ids": {
"type": "keyword"
},
"version": {
"type": "version"
}
}
}
Изучив маппинги индексов всех шести категорий, видим наличие полей kibana.alert.status и kibana.alert.uuid, по которым можно отследить срабатывание конкретного правила.
Напрашивалось простое решение: создаем одно правило в ElastAlert 2 с типом Change, которое будет отслеживать изменения поля в compare_key: kibana.alert.status, по ключу в query_key: kibana.alert.uuid. Но на практике оказалось все не так просто.
В ElastAlert 2 возникли сложности в правиле с типом Change:
Первое событие по ключу query_key не вызывает срабатывание, потому что ElastAlert 2 сравнивает текущее событие с предыдущим. Если предыдущего события не было – сравнивать не с чем и событие не считается измененным. Это несущественно, когда нас интересует именно факт изменения, а когда речь идет об алертах, особенно в продуктивной системе, ситуация может быть критичной.
Отсутствует настройка избыточности уведомлений, когда алерт слишком часто переходит из одного состояния в другое.
Некорректная обработка нескольких событий за один цикл. Если приходит несколько изменений за один цикл обработки, то сохранится только последнее изменение. Это поведение отражено прямо в исходном коде класса:
# TODO this is not technically correct
# if the term changes multiple times before an alert is sent
# this data will be overwritten with the most recent change
Создаем собственный тип правила
После того, как я столкнулся с ограничениями встроенного правила с типом Change, стало понятно – нужен новый тип, который обходил бы их, но при этом мог быть легко реализован без вмешательства в сам код ElastAlert 2.
И такое решение есть. Возможность создавать свои типы правил описана в официальной документации ElasAlert 2.
Вот как это делается:
создаём каталог, например
elastalert_modules
, в рабочей директории ElastAlert 2;кладём туда пустой
init.py
;там же создаем файл с логикой правила, например,
KibanaAlertRule.py
;в YAML-файле самого правила указываем тип правила:
type: elastalert_modules.kibana_alert.KibanaAlertRule.
В качестве основы для своего типа правила я взял встроенный в ElastAlert 2 класс CompareRule(RuleType)
, и внес в него следующие изменения:
добавил проверку и фильтрацию плавающих алертов;
сделал ключ уникальным, объединил
query_key
иcompare_key
(это важно, когда за один цикл приходит несколько разных событий);учел срабатывание при появлении первого события;
убрал цикл, вместо него добавил обработку каждого события.
В итоге у меня получился такой код:
import dateutil.parser
from elastalert.ruletypes import CompareRule, lookup_es_key, hashable, dt_to_ts, elastalert_logger
class KibanaAlertRule(CompareRule):
""" A rule that will store values for a certain term and match if those values change """
required_options = frozenset(['query_key', 'compound_compare_key', 'ignore_null'])
change_map = {}
occurrence_time = {}
def compare(self, event):
# Если в настройках включён флаг flapped, и в событии flapped=true — пропустить
if self.rules.get('flapped', False) and lookup_es_key(event, 'kibana.alert.flapping') is True:
elastalert_logger.debug("Skipping event due to kibana.alert.flapped=True and flapped option enabled")
return False
key = hashable(lookup_es_key(event, self.rules['query_key']))
values = []
elastalert_logger.debug(" Previous Values of compare keys " + str(self.occurrences))
query_value = lookup_es_key(event, self.rules['query_key'])
# Извлекаем значения для всех ключей в compound_compare_key
compound_values = [lookup_es_key(event, val) for val in self.rules['compound_compare_key']]
#values.append(str(query_value) + "_" + str(event['kibana.alert.uuid']) + "_" + "_".join(map(str, compound_values)))
values.append(str(query_value) + "_" + "_".join(map(str, compound_values)))
elastalert_logger.debug(" Current Values of compare keys " + str(values))
changed = False
# Если ключи отсутствуют, и это первое событие, то считаем событие измененным
if key not in self.occurrences:
previous_status = None
changed = True
elastalert_logger.debug(f"First occurrence for {key}, considering it as changed")
elif key in self.occurrences:
previous_status = self.occurrences[key]
# Если статус изменился, установим флаг изменений
if previous_status != values:
changed = True
elastalert_logger.debug(f"Status changed for {key}: {previous_status} -> {values}")
# Если сработала проверка на изменение, запишем изменения в change_map
if changed:
self.change_map[key] = (previous_status, values)
# If using timeframe, only return true if the time delta is < timeframe
if key in self.occurrence_time:
changed = event[self.rules['timestamp_field']] - self.occurrence_time[key] <= self.rules['timeframe']
# Update the current value and time
elastalert_logger.debug(" Setting current value of compare keys values " + str(values))
self.occurrences[key] = values
if 'timeframe' in self.rules:
self.occurrence_time[key] = event[self.rules['timestamp_field']]
elastalert_logger.debug("Final result of comparision between previous and current values " + str(changed))
return changed
def add_match(self, match):
change = self.change_map.get(hashable(lookup_es_key(match, self.rules['query_key'])))
extra = {}
if change:
extra = {'old_value': change[0],
'new_value': change[1]}
elastalert_logger.debug("Description of the changed records " + str(dict(list(match.items()) + list(extra.items()))))
super(KibanaAlertRule, self).add_match(dict(list(match.items()) + list(extra.items())))
Создаем универсальное правило
В процессе практического использования и анализа алертов я собрал универсальное правило для ElastAlert 2, которое обрабатывает все категории алертов в Kibana Alert. Выделил ключевые моменты в правиле, в том числе для оповещения в Telegram.
Указал шаблон индексов, в который попадают все категории алертов.
Добавил логику фильтрации частых срабатывания flapped.
Удалил спецсимволы * из заголовка через Jinja, так как ElastAlert 2 оборачивает заголовок в * для жирного выделения. Если в заголовке встретится дополнительный символ *, будет ошибка и сообщение не отправится.
Нужен информативный и читаемый заголовок, в зависимости от типа алерта. Заголовок формируется по логике:
- если алерт относится к SIEM, используется полеkibana.rule.name
- во всех остальных случаях –kibana.alert.reason
В итоге получилось такое универсальное правило:
name: Kibana Alerts
type: "elastalert_modules.kibana_alert.KibanaAlertRule"
index: .internal.alerts-*.alerts-*
compare_key: kibana.alert.status
query_key: kibana.alert.uuid
flapped: true
ignore_null: true
realert:
minutes: 0
buffer_time:
minutes: 1
alert:
- "telegram"
jinja_root_name: root
alert_subject: >-
{% set reason = root['kibana.alert.reason'] | replace('*', '') %}
{% if root['kibana.alert.rule.producer'] == "siem" -%}
{{ root['kibana.alert.rule.name'] }} is {{ root['kibana.alert.status'] }}
{%- else -%}
{{ reason }} is {{ root['kibana.alert.status'] }}
{%- endif %}
alert_text_type: alert_text_jinja
alert_text: |
Alert Status: {{ root['kibana.alert.status'] }}
Rule: {{ root['kibana.alert.rule.name'] }}
Message: {{ root['kibana.alert.reason'] }}
Date: {{ root['@timestamp'] }}
telegram_bot_token: "ХХХХХХХХХ:ХХХХХХХХХ"
telegram_room_id: "ХХХХХХХХХ"
Результат реального использования
Созданный тип правила и само универсальное правило очень удобно использовать в составе Helm-чарта, который поставляется с исходным кодом — достаточно указать путь к модулю и смонтировать необходимые файлы через extraVolumes и extraVolumeMounts. Но даже если не используется Kubernetes, никаких сложностей не возникнет для docker или использования на хосте.
Для наглядности приложил скриншот правил в Kibana и несколько практических скриншотов оповещений в Telegram при срабатывании алертов.
Привила в Kibana

Оповещение по событию логов

Оповещение по событию метрик

Оповещения по событиям APM

Оповещения по синтетическим тестам/сертификатам


Оповещения по пороговым значениям

Оповещения по событию SIEM


Что хотелось бы улучшить
В моем решении очень не хватает поддержки Jinja в параметрах для Telegram. Нет возможности отправлять оповещения в разные Telegram-каналы, в зависимости от условий. На практике с Kibana работают разные команды: разработчики, тестировщики и т.д. Было бы удобно направлять алерты каждым в свой канал, основываясь, на названии правила в Kibana. Например, такой сценарий:
telegram_room_id: >
{% if 'DEVELOP' in rule.name %}
-111111111
{% elif 'TEST' in rule.name %}
-222222222
{% else %}
-333333333
{% endif %}
Если вы сталкивались с подобной задачей и нашли простое решение, я буду благодарен за информацию о вашем опыте. Поделитесь, пожалуйста, в комментариях.
Заключение
Полагаю, что мое решение получилось универсальным и простым в использовании. Оно охватывает все основные категории алертов в Kibana. Особенно важно, что это позволяет перенести всю логику управления алертами в Kibana, добиться единого подхода к обработке всех типов оповещений. Это избавляет от необходимости администрирования правил в ElastAlert 2.