Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add webhook alerter and docs #3042

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
44 changes: 44 additions & 0 deletions docs/source/ruletypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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["<ES_FIELD_HERE>"]}}'

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]
39 changes: 39 additions & 0 deletions elastalert/alerts.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
3 changes: 2 additions & 1 deletion elastalert/loaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
83 changes: 83 additions & 0 deletions example_rules/example_webhook_frequency.yaml
Original file line number Diff line number Diff line change
@@ -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]
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ croniter>=0.3.16
elasticsearch>=7.0.0
envparse>=0.2.0
exotel>=0.1.3
Jinja2>=2.11.2
jira>=1.0.10,<1.0.15
jsonschema>=3.0.2
mock>=2.0.0
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down