From 25db8ceb85824716cfba127b29d48be5260a1a14 Mon Sep 17 00:00:00 2001 From: lastbulletbender Date: Wed, 18 Nov 2020 18:48:16 +0530 Subject: [PATCH 1/5] Add webhook alerter and docs The webhook alerter uses jinja templating engine which allows creating user customizable endpoints and body. --- docs/source/ruletypes.rst | 44 +++++++++++++++++++++++++++++++++++++++ elastalert/alerts.py | 39 ++++++++++++++++++++++++++++++++++ elastalert/loaders.py | 3 ++- 3 files changed, 85 insertions(+), 1 deletion(-) diff --git a/docs/source/ruletypes.rst b/docs/source/ruletypes.rst index ff3763712..45dbfbb08 100644 --- a/docs/source/ruletypes.rst +++ b/docs/source/ruletypes.rst @@ -2243,3 +2243,47 @@ Required: ``zbx_sender_port``: The port where zabbix server is listenning. ``zbx_host``: This field setup the host in zabbix that receives the value sent by Elastalert. ``zbx_item``: This field setup the item in the host that receives the value sent by Elastalert. + +Webhook +~~~~~~~ + +The Webhook alert type is a customisable alerter which uses Jinja to format the endpoint and the payload. +Webhook can use any standard HTTP method as well as user defined HTTP methods. + +Required: + +``webhook_method``: The HTTP method to be used in the request. Usually GET and POST. + +``webhook_endpoint``: The endpoint for the request. This string is used as a Jinja template and thus can be injected with fields in elasticsearch. + The variable match can be used to access fields for a particular match. + Example : + + webhook_endpoint : 'http://localhost:8080?q={{match[""]}}' + +Optional: + +``webhook_headers``: A list of key-value pairs which will be used as headers for the request. + Example: + + webhook_headers : + content-type: application/json + authorization: Basic 1234 + + Please set the content-type header to avoid any decoding issues on the server end. + + +``webhook_content``: Jinja string which can be used to build any kind of body. Default content will be the match in JSON format. + Examples : + + 1. To post all values as json + webhook_content : '{{ match|tojson }}' + 2. To create a custom JSON body : + webhook_content : '"field1" : "{{ match["field1"]}}"' + 3. To send the match as string : + webhook_content: '{{ match|string }}' + +``webhook_expected_status_codes``: List of status codes which can be considered as a successful response. + Default successful status codes range is from 200 to 299 + Example: + + webhook_expected_status_codes : [200,201] diff --git a/elastalert/alerts.py b/elastalert/alerts.py index d3fa7518f..18a44b594 100644 --- a/elastalert/alerts.py +++ b/elastalert/alerts.py @@ -23,6 +23,7 @@ import requests import stomp from exotel import Exotel +from jinja2 import Template from jira.client import JIRA from jira.exceptions import JIRAError from requests.auth import HTTPProxyAuth @@ -2182,3 +2183,41 @@ def get_info(self): 'type': 'hivealerter', 'hive_host': self.rule.get('hive_connection', {}).get('hive_host', '') } + + +class WebhookAlerter(Alerter): + + required_options = set(['webhook_method', 'webhook_endpoint']) + + def __init__(self, rule): + super(WebhookAlerter, self).__init__(rule) + self.type = 'webhook' + self.method = self.rule.get('webhook_method') + self.endpoint = self.rule.get('webhook_endpoint') + self.headers = self.rule.get('webhook_headers') + self.content = self.rule.get('webhook_content') + self.timeout = self.rule.get('webhook_timeout') + self.expected_status_codes = self.rule.get('webhook_expected_status_codes', list(range(200, 299))) + + def alert(self, matches): + endpoint_template = Template(self.endpoint) + content_template = Template(self.content) + for match in matches: + endpoint = endpoint_template.render(match=match) + content = content_template.render(match=match) + try: + response = requests.request(method=self.method, url=endpoint, timeout=self.timeout, + data=content, headers=self.headers) + if response.status_code not in self.expected_status_codes: + elastalert_logger.error('Got response : %s' % (response.content)) + raise RequestException('Status code %s of webhook not expected' % (response.status_code)) + except Exception as e: + raise EAException('Error sending alert via Webhook: %s' % (str(e))) + + def get_info(self): + + return { + 'type': self.type, + 'method': self.method, + 'endpoint': self.endpoint + } diff --git a/elastalert/loaders.py b/elastalert/loaders.py index 771194768..9d0acea3e 100644 --- a/elastalert/loaders.py +++ b/elastalert/loaders.py @@ -77,7 +77,8 @@ class RulesLoader(object): 'servicenow': alerts.ServiceNowAlerter, 'alerta': alerts.AlertaAlerter, 'post': alerts.HTTPPostAlerter, - 'hivealerter': alerts.HiveAlerter + 'hivealerter': alerts.HiveAlerter, + 'webhook': alerts.WebhookAlerter } # A partial ordering of alert types. Relative order will be preserved in the resulting alerts list From 246ca5fc6140e4b4f093cac020cd99d98da51f89 Mon Sep 17 00:00:00 2001 From: lastbulletbender Date: Wed, 18 Nov 2020 19:32:11 +0530 Subject: [PATCH 2/5] Add Jinja2 dependency in setup.py and requirements.txt --- requirements.txt | 1 + setup.py | 1 + 2 files changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index 9c32052d0..29674c7e6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,6 +8,7 @@ croniter>=0.3.16 elasticsearch>=7.0.0 envparse>=0.2.0 exotel>=0.1.3 +Jinja>=2.11.2 jira>=1.0.10,<1.0.15 jsonschema>=3.0.2 mock>=2.0.0 diff --git a/setup.py b/setup.py index 2845836a7..3af685403 100644 --- a/setup.py +++ b/setup.py @@ -36,6 +36,7 @@ 'elasticsearch==7.0.0', 'envparse>=0.2.0', 'exotel>=0.1.3', + 'Jinja2>=2.11.2', 'jira>=2.0.0', 'jsonschema>=3.0.2', 'mock>=2.0.0', From 27c41bf5411c6be11ac82937da5bfa5a8d988e7e Mon Sep 17 00:00:00 2001 From: lastbulletbender Date: Wed, 18 Nov 2020 20:25:18 +0530 Subject: [PATCH 3/5] Change Jinja to Jinja2 in requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 29674c7e6..a46711ba3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ croniter>=0.3.16 elasticsearch>=7.0.0 envparse>=0.2.0 exotel>=0.1.3 -Jinja>=2.11.2 +Jinja2>=2.11.2 jira>=1.0.10,<1.0.15 jsonschema>=3.0.2 mock>=2.0.0 From 87768354a5c0702ab783ca90a06075eeadd1b99f Mon Sep 17 00:00:00 2001 From: lastbulletbender Date: Fri, 20 Nov 2020 13:27:35 +0530 Subject: [PATCH 4/5] Add webhook to the list of alerts in README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 99acc02e7..9f7188115 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ Currently, we have built-in support for the following alert types: - Gitter - Line Notify - Zabbix +- Webhook Additional rule types and alerts can be easily imported or written. From ea32261b6132f2d36307b376577a372c63b96c6c Mon Sep 17 00:00:00 2001 From: lastbulletbender Date: Fri, 20 Nov 2020 23:57:56 +0530 Subject: [PATCH 5/5] Add example rule for webhook alert --- example_rules/example_webhook_frequency.yaml | 83 ++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 example_rules/example_webhook_frequency.yaml diff --git a/example_rules/example_webhook_frequency.yaml b/example_rules/example_webhook_frequency.yaml new file mode 100644 index 000000000..3ff7b68a9 --- /dev/null +++ b/example_rules/example_webhook_frequency.yaml @@ -0,0 +1,83 @@ +# Alert when the rate of events exceeds a threshold + +# (Optional) +# Elasticsearch host +#es_host: localhost + +# (Optional) +# Elasticsearch port +#es_port: 9200 + +# (OptionaL) Connect with SSL to Elasticsearch +#use_ssl: True + +# (Optional) basic-auth username and password for Elasticsearch +#es_username: someusername +#es_password: somepassword + +# (Required) +# Rule name, must be unique +name: webhook_based_rule_name + +# (Required) +# Type of alert. +# the frequency rule type alerts when num_events events occur with timeframe time +type: frequency + +# (Required) +# Index to search, wildcard supported +index: logstash-* + +#doc_type: "golog" + +# (Required, frequency specific) +# Alert when this many documents matching the query occur within a timeframe +num_events: 50 + +# (Required, frequency specific) +# num_events must occur within this amount of time to trigger an alert +timeframe: + hours: 2 + +# (Required) +# A list of Elasticsearch filters used for find events +# These filters are joined with AND and nested in a filtered query +# For more info: http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl.html +filter: +- query: + query_string: + query: "@message: *hihi*" + +# (Required) +# The alert is use when a match is found +alert: +- "webhook" + +# (Required) +# Webhook Method such as GET, POST or any user defined method which the endpoint supports +webhook_method: 'POST' + +# (Required) +# This can be a static endpoint or any elasticsearch field from the match can be added +webhook_endpoint: 'http://localhost:8080?q={{match["ES-FIELD-HERE"]}}' + +# (Optional) +# Any required headers can be added as a key value pair list +# It is advisable to add content-type header for the endpoint to understand the type of body you are sending. +# By default, no headers are sent with the request +# webhook_headers: +# content-type: application/json + +# (Optional) +# The body can be defined in formats such as JSON, XML or string. By default, no body is sent. +# This is an example of a JSON body being created. The match variable can be used to access any field from elasticsearch + +# webhook_content: '{ "container_name": "{{ match["container_name"] }}", +# "service": "{{ match["service"] }}" }' + +# To send all the fields in match as JSON, the tojson filter can be used. (Don't forget to set headers as application/json) +# webhook_content: '{{ match|tojson }}' + +# (Optional) +# The default status codes are 200 to 299. If the endpoint sends a different status code for success, they can be mentioned as a list. +# webhook_expected_status_codes: [302, 307]