def test_alert_text_global_substitution(ea): rule = ea.rules[0].copy() rule['owner'] = 'the owner from rule' rule['priority'] = 'priority from rule' rule['abc'] = 'abc from rule' rule['alert_text'] = 'Priority: {0}; Owner: {1}; Abc: {2}' rule['alert_text_args'] = ['priority', 'owner', 'abc'] match = { '@timestamp': '2016-01-01', 'field': 'field_value', 'abc': 'abc from match', } alert_text = unicode(BasicMatchString(rule, match)) assert 'Priority: priority from rule' in alert_text assert 'Owner: the owner from rule' in alert_text # When the key exists in both places, it will come from the match assert 'Abc: abc from match' in alert_text
def test_opsgenie_priority_none(): rule = { 'name': 'Opsgenie Details', 'type': mock_rule(), 'opsgenie_account': 'genies', 'opsgenie_key': 'ogkey', 'opsgenie_details': { 'Message': {'field': 'message'}, 'Missing': {'field': 'missing'} }, 'opsgenie_priority': 'abc' } match = { 'message': 'Testing', '@timestamp': '2014-10-31T00:00:00' } alert = OpsGenieAlerter(rule) with mock.patch('requests.post') as mock_post_request: alert.alert([match]) mock_post_request.assert_called_once_with( 'https://api.opsgenie.com/v2/alerts', headers={ 'Content-Type': 'application/json', 'Authorization': 'GenieKey ogkey' }, json=mock.ANY, proxies=None ) expected_json = { 'description': BasicMatchString(rule, match).__str__(), 'details': {'Message': 'Testing'}, 'message': 'ElastAlert: Opsgenie Details', 'source': 'ElastAlert', 'tags': ['ElastAlert', 'Opsgenie Details'], 'user': '******' } actual_json = mock_post_request.call_args_list[0][1]['json'] assert expected_json == actual_json
def test_rocket_chat_uses_custom_timeout(): rule = { 'name': 'Test Rule', 'type': 'any', 'rocket_chat_webhook_url': 'http://please.dontgohere.rocketchat', 'alert_subject': 'Cool subject', 'alert': [], 'rocket_chat_timeout': 20 } rules_loader = FileRulesLoader({}) rules_loader.load_modules(rule) alert = RocketChatAlerter(rule) match = {'@timestamp': '2016-01-01T00:00:00', 'somefield': 'foobarbaz'} with mock.patch('requests.post') as mock_post_request: alert.alert([match]) expected_data = { 'username': '******', 'channel': '', 'emoji': ':ghost:', 'attachments': [{ 'color': 'danger', 'title': 'Cool subject', 'text': BasicMatchString(rule, match).__str__(), 'fields': [] }], 'text': '' } mock_post_request.assert_called_once_with( rule['rocket_chat_webhook_url'], data=mock.ANY, headers={'content-type': 'application/json'}, proxies=None, verify=True, timeout=20) assert expected_data == json.loads( mock_post_request.call_args_list[0][1]['data'])
def test_opsgenie_details_with_environment_variable_replacement(environ): environ.update({ 'TEST_VAR': 'Bar' }) rule = { 'name': 'Opsgenie Details', 'type': mock_rule(), 'opsgenie_account': 'genies', 'opsgenie_key': 'ogkey', 'opsgenie_details': {'Foo': '$TEST_VAR'} } match = { '@timestamp': '2014-10-31T00:00:00' } alert = OpsGenieAlerter(rule) with mock.patch('requests.post') as mock_post_request: alert.alert([match]) mock_post_request.assert_called_once_with( 'https://api.opsgenie.com/v2/alerts', headers={ 'Content-Type': 'application/json', 'Authorization': 'GenieKey ogkey' }, json=mock.ANY, proxies=None ) expected_json = { 'description': BasicMatchString(rule, match).__str__(), 'details': {'Foo': 'Bar'}, 'message': 'ElastAlert: Opsgenie Details', 'priority': None, 'source': 'ElastAlert', 'tags': ['ElastAlert', 'Opsgenie Details'], 'user': '******' } actual_json = mock_post_request.call_args_list[0][1]['json'] assert expected_json == actual_json
def test_slack_uses_custom_slack_channel(): rule = { 'name': 'Test Rule', 'type': 'any', 'slack_webhook_url': ['http://please.dontgohere.slack'], 'slack_channel_override': '#test-alert', 'alert': [] } load_modules(rule) alert = SlackAlerter(rule) match = {'@timestamp': '2016-01-01T00:00:00', 'somefield': 'foobarbaz'} with mock.patch('requests.post') as mock_post_request: alert.alert([match]) expected_data = { 'username': '******', 'channel': '#test-alert', 'icon_emoji': ':ghost:', 'attachments': [{ 'color': 'danger', 'title': rule['name'], 'text': BasicMatchString(rule, match).__str__(), 'mrkdwn_in': ['text', 'pretext'], 'fields': [] }], 'text': '', 'parse': 'none' } mock_post_request.assert_called_once_with( rule['slack_webhook_url'][0], data=mock.ANY, headers={'content-type': 'application/json'}, proxies=None) assert expected_data == json.loads( mock_post_request.call_args_list[0][1]['data'])
def test_basic_match_string(ea): ea.rules[0]['top_count_keys'] = ['username'] match = {'@timestamp': '1918-01-17', 'field': 'value', 'top_events_username': {'bob': 10, 'mallory': 5}} alert_text = unicode(BasicMatchString(ea.rules[0], match)) assert 'anytest' in alert_text assert 'some stuff happened' in alert_text assert 'username' in alert_text assert 'bob: 10' in alert_text assert 'field: value' in alert_text # Non serializable objects don't cause errors match['non-serializable'] = {open: 10} alert_text = unicode(BasicMatchString(ea.rules[0], match)) # unicode objects dont cause errors match['snowman'] = u'☃' alert_text = unicode(BasicMatchString(ea.rules[0], match)) # Pretty printed objects match.pop('non-serializable') match['object'] = {'this': {'that': [1, 2, "3"]}} alert_text = unicode(BasicMatchString(ea.rules[0], match)) assert '"this": {\n "that": [\n 1, \n 2, \n "3"\n ]\n }' in alert_text ea.rules[0]['alert_text'] = 'custom text' alert_text = unicode(BasicMatchString(ea.rules[0], match)) assert 'custom text' in alert_text assert 'anytest' not in alert_text ea.rules[0]['alert_text_type'] = 'alert_text_only' alert_text = unicode(BasicMatchString(ea.rules[0], match)) assert 'custom text' in alert_text assert 'some stuff happened' not in alert_text assert 'username' not in alert_text assert 'field: value' not in alert_text ea.rules[0]['alert_text_type'] = 'exclude_fields' alert_text = unicode(BasicMatchString(ea.rules[0], match)) assert 'custom text' in alert_text assert 'some stuff happened' in alert_text assert 'username' in alert_text assert 'field: value' not in alert_text
def alert(self, matches): for match in matches: # Parse everything into description. description = str(BasicMatchString(self.rule, match)) # Set proper headers headers = { "Content-Type": "application/json", "Accept": "application/json;charset=utf-8" } proxies = { 'https': self.servicenow_proxy } if self.servicenow_proxy else None payload = { "description": description, "short_description": self.rule['short_description'], "comments": self.rule['comments'], "assignment_group": self.rule['assignment_group'], "category": self.rule['category'], "subcategory": self.rule['subcategory'], "cmdb_ci": self.rule['cmdb_ci'], "caller_id": self.rule["caller_id"] } if self.impact != None: payload["impact"] = self.impact if self.urgency != None: payload["urgency"] = self.urgency try: response = requests.post(self.servicenow_rest_url, auth=(self.rule['username'], self.rule['password']), headers=headers, data=json.dumps(payload, cls=DateTimeEncoder), proxies=proxies) response.raise_for_status() except RequestException as e: raise EAException("Error posting to ServiceNow: %s" % e) elastalert_logger.info("Alert sent to ServiceNow")
def test_alert_text_kw_global_substitution(ea): rule = ea.rules[0].copy() rule['foo_rule'] = 'foo from rule' rule['owner'] = 'the owner from rule' rule['abc'] = 'abc from rule' rule['alert_text'] = 'Owner: {owner}; Foo: {foo}; Abc: {abc}' rule['alert_text_kw'] = { 'owner': 'owner', 'foo_rule': 'foo', 'abc': 'abc', } match = { '@timestamp': '2016-01-01', 'field': 'field_value', 'abc': 'abc from match', } alert_text = unicode(BasicMatchString(rule, match)) assert 'Owner: the owner from rule' in alert_text assert 'Foo: foo from rule' in alert_text # When the key exists in both places, it will come from the match assert 'Abc: abc from match' in alert_text
def alert(self, matches): body = '' for match in matches: body += str(BasicMatchString(self.rule, match)) if len(matches) > 1: body += '\n----------------------------------------\n' if len(body) > 999: body = body[ 0: 900] + '\n *message was cropped according to line notify embed description limits!*' # post to Line Notify headers = { "Content-Type": "application/x-www-form-urlencoded", "Authorization": "Bearer {}".format(self.linenotify_access_token) } payload = {"message": body} try: response = requests.post("https://notify-api.line.me/api/notify", data=payload, headers=headers) response.raise_for_status() except RequestException as e: raise EAException("Error posting to Line Notify: %s" % e) elastalert_logger.info("Alert sent to Line Notify")
def test_alert_text_jinja(ea): rule = ea.rules[0].copy() rule['foo_rule'] = 'foo from rule' rule['owner'] = 'the owner from rule' rule['abc'] = 'abc from rule' rule['alert_text'] = 'Owner: {{owner}}; Foo: {{_data["foo_rule"]}}; Abc: {{abc}}; Xyz: {{_data["xyz"]}}' rule['alert_text_type'] = "alert_text_jinja" rule['jinja_root_name'] = "_data" rule['jinja_template'] = Template(str(rule['alert_text'])) match = { '@timestamp': '2016-01-01', 'field': 'field_value', 'abc': 'abc from match', 'xyz': 'from match' } alert_text = str(BasicMatchString(rule, match)) assert 'Owner: the owner from rule' in alert_text assert 'Foo: foo from rule' in alert_text assert 'Xyz: from match' in alert_text # When the key exists in both places, it will come from the match assert 'Abc: abc from match' in alert_text
def test_slack_alert_fields(): rule = { 'name': 'Test Rule', 'type': 'any', 'slack_webhook_url': 'http://please.dontgohere.slack', 'slack_username_override': 'elastalert', 'slack_alert_fields': [ { 'title': 'Host', 'value': 'somefield', 'short': True }, { 'title': 'Sensors', 'value': '@timestamp', 'short': True } ], 'alert_subject': 'Cool subject', 'alert': [] } rules_loader = FileRulesLoader({}) rules_loader.load_modules(rule) alert = SlackAlerter(rule) match = { '@timestamp': '2016-01-01T00:00:00', 'somefield': 'foobarbaz' } with mock.patch('requests.post') as mock_post_request: alert.alert([match]) expected_data = { 'username': '******', 'channel': '', 'icon_emoji': ':ghost:', 'attachments': [ { 'color': 'danger', 'title': rule['alert_subject'], 'text': BasicMatchString(rule, match).__str__(), 'mrkdwn_in': ['text', 'pretext'], 'fields': [ { 'short': True, 'title': 'Host', 'value': 'foobarbaz' }, { 'short': True, 'title': 'Sensors', 'value': '2016-01-01T00:00:00' } ], } ], 'text': '', 'parse': 'none' } mock_post_request.assert_called_once_with( rule['slack_webhook_url'], data=mock.ANY, headers={'content-type': 'application/json'}, proxies=None, verify=True, timeout=10 ) assert expected_data == json.loads(mock_post_request.call_args_list[0][1]['data'])
def alert(self, matches): body = '' for match in matches: body += str(BasicMatchString(self.rule, match)) # Separate text of aggregated alerts with dashes if len(matches) > 1: body += '\n----------------------------------------\n' if self.custom_message is None: self.message = self.create_title(matches) else: self.message = self.custom_message.format(**matches[0]) self.recipients = self._parse_responders(self.recipients, self.recipients_args, matches, self.default_reciepients) self.teams = self._parse_responders(self.teams, self.teams_args, matches, self.default_teams) post = {} post['message'] = self.message if self.account: post['user'] = self.account if self.recipients: post['responders'] = [{ 'username': r, 'type': 'user' } for r in self.recipients] if self.teams: post['teams'] = [{'name': r, 'type': 'team'} for r in self.teams] if self.description: post['description'] = self.description.format(**matches[0]) else: post['description'] = body if self.entity: post['entity'] = self.entity.format(**matches[0]) if self.source: post['source'] = self.source.format(**matches[0]) post['tags'] = [] for i, tag in enumerate(self.tags): post['tags'].append(tag.format(**matches[0])) priority = self.priority if priority: priority = priority.format(**matches[0]) if priority and priority not in ('P1', 'P2', 'P3', 'P4', 'P5'): elastalert_logger.warning( "Priority level does not appear to be specified correctly. \ Please make sure to set it to a value between P1 and P5" ) else: post['priority'] = priority if self.alias is not None: post['alias'] = self.alias.format(**matches[0]) details = self.get_details(matches) if details: post['details'] = details elastalert_logger.debug(json.dumps(post)) headers = { 'Content-Type': 'application/json', 'Authorization': 'GenieKey {}'.format(self.api_key), } # set https proxy, if it was provided proxies = { 'https': self.opsgenie_proxy } if self.opsgenie_proxy else None try: r = requests.post(self.to_addr, json=post, headers=headers, proxies=proxies) elastalert_logger.debug('request response: {0}'.format(r)) if r.status_code != 202: elastalert_logger.info("Error response from {0} \n " "API Response: {1}".format( self.to_addr, r)) r.raise_for_status() elastalert_logger.info("Alert sent to OpsGenie") except Exception as err: raise EAException("Error sending alert: {0}".format(err))
def alert(self, matches): alerts = [] qk = self.rule.get('query_key', None) fullmessage = {} for match in matches: if qk is not None: resmatch = lookup_es_key(match, qk) else: resmatch = None if resmatch is not None: elastalert_logger.info( 'Alert for %s, %s at %s:' % (self.rule['name'], resmatch, lookup_es_key(match, self.rule['timestamp_field']))) alerts.append( 'Alert for %s, %s at %s:' % (self.rule['name'], resmatch, lookup_es_key(match, self.rule['timestamp_field']))) fullmessage['match'] = resmatch else: elastalert_logger.info( 'Rule %s generated an alert at %s:' % (self.rule['name'], lookup_es_key(match, self.rule['timestamp_field']))) alerts.append( 'Rule %s generated an alert at %s:' % (self.rule['name'], lookup_es_key(match, self.rule['timestamp_field']))) fullmessage['match'] = lookup_es_key( match, self.rule['timestamp_field']) elastalert_logger.info(str(BasicMatchString(self.rule, match))) fullmessage['alerts'] = alerts fullmessage['rule'] = self.rule['name'] fullmessage['rule_file'] = self.rule['rule_file'] fullmessage['matching'] = str(BasicMatchString(self.rule, match)) fullmessage['alertDate'] = datetime.datetime.now().strftime( "%Y-%m-%d %H:%M:%S") fullmessage['body'] = self.create_alert_body(matches) fullmessage['matches'] = matches self.stomp_hostname = self.rule.get('stomp_hostname', 'localhost') self.stomp_hostport = self.rule.get('stomp_hostport', '61613') self.stomp_login = self.rule.get('stomp_login', 'admin') self.stomp_password = self.rule.get('stomp_password', 'admin') self.stomp_destination = self.rule.get('stomp_destination', '/queue/ALERT') self.stomp_ssl = self.rule.get('stomp_ssl', False) try: conn = stomp.Connection( [(self.stomp_hostname, self.stomp_hostport)], use_ssl=self.stomp_ssl) conn.connect(self.stomp_login, self.stomp_password) # Ensures that the CONNECTED frame is received otherwise, the disconnect call will fail. time.sleep(1) conn.send(self.stomp_destination, json.dumps(fullmessage)) conn.disconnect() except Exception as e: raise EAException("Error posting to Stomp: %s" % e) elastalert_logger.info("Alert sent to Stomp")