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

Added syslog alerter #1433

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ Currently, we have built-in support for the following alert types:
- Exotel
- Twilio
- Gitter
- ServiceNow
- Syslog

Additional rule types and alerts can be easily imported or written.

Expand Down
136 changes: 113 additions & 23 deletions elastalert/alerts.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import datetime
import json
import logging
import logging.handlers

import subprocess
import sys
import warnings
Expand All @@ -12,6 +14,7 @@
from smtplib import SMTP_SSL
from smtplib import SMTPAuthenticationError
from smtplib import SMTPException
import socket
from socket import error

import boto3
Expand Down Expand Up @@ -125,7 +128,8 @@ def _pretty_print_as_json(self, blob):
return json.dumps(blob, cls=DateTimeEncoder, sort_keys=True, indent=4, ensure_ascii=False)
except UnicodeDecodeError:
# This blob contains non-unicode, so lets pretend it's Latin-1 to show something
return json.dumps(blob, cls=DateTimeEncoder, sort_keys=True, indent=4, encoding='Latin-1', ensure_ascii=False)
return json.dumps(blob, cls=DateTimeEncoder, sort_keys=True, indent=4, encoding='Latin-1',
ensure_ascii=False)

def __str__(self):
self.text = ''
Expand Down Expand Up @@ -359,7 +363,8 @@ def alert(self, matches):
elastalert_logger.info(
'Alert for %s, %s at %s:' % (self.rule['name'], match[qk], lookup_es_key(match, self.rule['timestamp_field'])))
else:
elastalert_logger.info('Alert for %s at %s:' % (self.rule['name'], lookup_es_key(match, self.rule['timestamp_field'])))
elastalert_logger.info(
'Alert for %s at %s:' % (self.rule['name'], lookup_es_key(match, self.rule['timestamp_field'])))
elastalert_logger.info(unicode(BasicMatchString(self.rule, match)))

def get_info(self):
Expand Down Expand Up @@ -431,7 +436,8 @@ def alert(self, matches):
try:
if self.smtp_ssl:
if self.smtp_port:
self.smtp = SMTP_SSL(self.smtp_host, self.smtp_port, keyfile=self.smtp_key_file, certfile=self.smtp_cert_file)
self.smtp = SMTP_SSL(self.smtp_host, self.smtp_port, keyfile=self.smtp_key_file,
certfile=self.smtp_cert_file)
else:
self.smtp = SMTP_SSL(self.smtp_host, keyfile=self.smtp_key_file, certfile=self.smtp_cert_file)
else:
Expand Down Expand Up @@ -608,7 +614,8 @@ def set_jira_arg(self, jira_field, value, fields):
# If the schema information is not available, raise an exception since we don't know how to set it
# Note this is only the case for two built-in types, id: issuekey and id: thumbnail
if not ('schema' in field or 'type' in field['schema']):
raise Exception("Could not determine schema information for the jira field '{0}'".format(normalized_jira_field))
raise Exception(
"Could not determine schema information for the jira field '{0}'".format(normalized_jira_field))
arg_type = field['schema']['type']

# Handle arrays of simple types like strings or numbers
Expand Down Expand Up @@ -764,7 +771,7 @@ def alert(self, matches):
# Re-raise the exception, preserve the stack-trace, and give some
# context as to which watcher failed to be added
raise Exception(
"Exception encountered when trying to add '{0}' as a watcher. Does the user exist?\n{1}" .format(
"Exception encountered when trying to add '{0}' as a watcher. Does the user exist?\n{1}".format(
watcher,
ex
)), None, sys.exc_info()[2]
Expand Down Expand Up @@ -997,7 +1004,8 @@ def alert(self, matches):

for url in self.ms_teams_webhook_url:
try:
response = requests.post(url, data=json.dumps(payload, cls=DateTimeEncoder), headers=headers, proxies=proxies)
response = requests.post(url, data=json.dumps(payload, cls=DateTimeEncoder), headers=headers,
proxies=proxies)
response.raise_for_status()
except RequestException as e:
raise EAException("Error posting to ms teams: %s" % e)
Expand Down Expand Up @@ -1075,7 +1083,8 @@ def alert(self, matches):

for url in self.slack_webhook_url:
try:
response = requests.post(url, data=json.dumps(payload, cls=DateTimeEncoder), headers=headers, proxies=proxies)
response = requests.post(url, data=json.dumps(payload, cls=DateTimeEncoder), headers=headers,
proxies=proxies)
response.raise_for_status()
except RequestException as e:
raise EAException("Error posting to slack: %s" % e)
Expand Down Expand Up @@ -1242,7 +1251,8 @@ def alert(self, matches):
}

try:
response = requests.post(self.url, data=json.dumps(payload, cls=DateTimeEncoder), headers=headers, proxies=proxies)
response = requests.post(self.url, data=json.dumps(payload, cls=DateTimeEncoder), headers=headers,
proxies=proxies)
response.raise_for_status()
except RequestException as e:
raise EAException("Error posting to VictorOps: %s" % e)
Expand Down Expand Up @@ -1285,7 +1295,8 @@ def alert(self, matches):
}

try:
response = requests.post(self.url, data=json.dumps(payload, cls=DateTimeEncoder), headers=headers, proxies=proxies)
response = requests.post(self.url, data=json.dumps(payload, cls=DateTimeEncoder), headers=headers,
proxies=proxies)
warnings.resetwarnings()
response.raise_for_status()
except RequestException as e:
Expand Down Expand Up @@ -1322,7 +1333,8 @@ def alert(self, matches):
}

try:
response = requests.post(self.gitter_webhook_url, json.dumps(payload, cls=DateTimeEncoder), headers=headers, proxies=proxies)
response = requests.post(self.gitter_webhook_url, json.dumps(payload, cls=DateTimeEncoder), headers=headers,
proxies=proxies)
response.raise_for_status()
except RequestException as e:
raise EAException("Error posting to Gitter: %s" % e)
Expand Down Expand Up @@ -1464,21 +1476,21 @@ def alert(self, matches):
# set https proxy, if it was provided
proxies = {'https': self.stride_proxy} if self.stride_proxy else None
payload = {
"body": {
"content": [
{
"body": {
"content": [
{
"text": body,
"type": "text"
}
{
"content": [
{
"text": body,
"type": "text"
}
],
"type": "paragraph"
}
],
"type": "paragraph"
}
],
"version": 1,
"type": "doc"
}
"version": 1,
"type": "doc"
}
}

try:
Expand All @@ -1499,3 +1511,81 @@ def get_info(self):
return {'type': 'stride',
'stride_cloud_id': self.stride_cloud_id,
'stride_converstation_id': self.stride_converstation_id}


class SyslogAlerter(Alerter):
@staticmethod
def _string_to_level(log_level):
""" Convert a commandline string to a proper log level
@param log_level command line log level argument
@return logging.LEVEL the logging.LEVEL object to return
"""
if log_level == "CRITICAL":
return logging.CRITICAL
if log_level == "ERROR":
return logging.ERROR
if log_level == "WARNING":
return logging.WARNING
if log_level == "INFO":
return logging.INFO
if log_level == "DEBUG":
return logging.DEBUG
return logging.NOTSET

# By setting required_options to a set of strings
# You can ensure that the rule config file specifies all
# of the options. Otherwise, ElastAlert will throw an exception
# when trying to load the rule.
required_options = {'syslog_host'}

def __init__(self, rule):
super(SyslogAlerter, self).__init__(rule)
self.syslog_host = self.rule['syslog_host']

if 'syslog_port' in self.rule:
self.syslog_port = self.rule['syslog_port']
else:
self.syslog_port = 514

if 'syslog_protocol' in self.rule and self.rule['syslog_protocol'] == 'tcp':
self.syslog_protocol = socket.SOCK_STREAM
else:
self.syslog_protocol = socket.SOCK_DGRAM

if 'syslog_level' in self.rule:
self.syslog_level = self.rule['syslog_level']
else:
self.syslog_level = SyslogAlerter._string_to_level("WARNING")

if 'syslog_facility' in self.rule:
self.syslog_facility = self.rule['syslog_facility']
else:
self.syslog_facility = 16

# Alert is called
def alert(self, matches):
syslogger = logging.getLogger('SyslogLogger')
syslogger.setLevel(self.syslog_level)
handler = logging.handlers.SysLogHandler(address=(self.syslog_host, self.syslog_port),
facility=self.syslog_facility, socktype=self.syslog_protocol)
syslogger.addHandler(handler)
# Matches is a list of match dictionaries.
# It contains more than one match when the alert has
# the aggregation option set
for match in matches:
elastalert_logger.debug("[SyslogAlerter] Trying to process... \n" + json.dumps(match))
match_string = str(BasicMatchString(self.rule, match))
syslogger.log(self.syslog_level, match_string)
#Delete the old handler that won't be used anymore
tmp_handlers = syslogger.handlers
for h in tmp_handlers:
if type(h) == logging.handlers.SysLogHandler:
elastalert_logger.debug("[SyslogAlerter] deleting handler.. %s", str(h))
syslogger.removeHandler(h)

# get_info is called after an alert is sent to get data that is written back
# to Elasticsearch in the field "alert_info"
# It should return a dict of information relevant to what the alert does
def get_info(self):
return {'type': 'Syslog Alerter',
'output_server': self.syslog_host}
1 change: 1 addition & 0 deletions elastalert/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
'telegram': alerts.TelegramAlerter,
'gitter': alerts.GitterAlerter,
'servicenow': alerts.ServiceNowAlerter,
'syslog': alerts.SyslogAlerter,
'post': alerts.HTTPPostAlerter
}
# A partial ordering of alert types. Relative order will be preserved in the resulting alerts list
Expand Down
7 changes: 7 additions & 0 deletions elastalert/schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -271,3 +271,10 @@ properties:
### Simple
simple_webhook_url: *arrayOfString
simple_proxy: {type: string}

### Syslog
syslog_host: {type: string}
syslog_port: {type: integer}
syslog_protocol: {enum: ["tcp", "udp"]}
syslog_level: {enum: ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"]}
syslog_facility: {type: integer}