diff --git a/docs/source/ruletypes.rst b/docs/source/ruletypes.rst index 2480e4fa..7f8c8332 100644 --- a/docs/source/ruletypes.rst +++ b/docs/source/ruletypes.rst @@ -1779,7 +1779,7 @@ Required: ``dingtalk_access_token``: Dingtalk access token. -``dingtalk_msgtype``: Dingtalk msgtype. ``text``, ``markdown``, ``single_action_card``, ``action_card``. +``dingtalk_msgtype``: Dingtalk msgtype, default to ``text``. ``markdown``, ``single_action_card``, ``action_card``. dingtalk_msgtype single_action_card Required: @@ -2233,10 +2233,10 @@ The alerter requires the following options: ``ms_teams_webhook_url``: The webhook URL that includes your auth data and the ID of the channel you want to post to. Go to the Connectors menu in your channel and configure an Incoming Webhook, then copy the resulting URL. You can use a list of URLs to send to multiple channels. -``ms_teams_alert_summary``: Summary should be configured according to `MS documentation `_, although it seems not displayed by Teams currently. - Optional: +``ms_teams_alert_summary``: Summary should be configured according to `MS documentation `_, although it seems not displayed by Teams currently, defaults to ``ElastAlert Message``. + ``ms_teams_theme_color``: By default the alert will be posted without any color line. To add color, set this attribute to a HTML color value e.g. ``#ff0000`` for red. ``ms_teams_proxy``: By default ElastAlert will not use a network proxy to send notifications to MS Teams. Set this option using ``hostname:port`` if you need to use a proxy. @@ -2768,9 +2768,9 @@ Zabbix will send notification to a Zabbix server. The item in the host specified Required: -``zbx_sender_host``: The address where zabbix server is running. +``zbx_sender_host``: The address where zabbix server is running, defaults to ``'localhost'``. -``zbx_sender_port``: The port where zabbix server is listenning. +``zbx_sender_port``: The port where zabbix server is listenning, defaults to ``10051``. ``zbx_host``: This field setup the host in zabbix that receives the value sent by ElastAlert 2. diff --git a/elastalert/alerters/chatwork.py b/elastalert/alerters/chatwork.py index f7f7db3f..4a6330bc 100644 --- a/elastalert/alerters/chatwork.py +++ b/elastalert/alerters/chatwork.py @@ -14,8 +14,8 @@ class ChatworkAlerter(Alerter): def __init__(self, rule): super(ChatworkAlerter, self).__init__(rule) - self.chatwork_apikey = self.rule.get('chatwork_apikey') - self.chatwork_room_id = self.rule.get('chatwork_room_id') + self.chatwork_apikey = self.rule['chatwork_apikey'] + self.chatwork_room_id = self.rule['chatwork_room_id'] self.url = 'https://api.chatwork.com/v2/rooms/%s/messages' % (self.chatwork_room_id) self.chatwork_proxy = self.rule.get('chatwork_proxy', None) self.chatwork_proxy_login = self.rule.get('chatwork_proxy_login', None) diff --git a/elastalert/alerters/datadog.py b/elastalert/alerters/datadog.py index b5796e95..3b71e426 100644 --- a/elastalert/alerters/datadog.py +++ b/elastalert/alerters/datadog.py @@ -8,13 +8,13 @@ class DatadogAlerter(Alerter): - ''' Creates a Datadog Event for each alert ''' + """ Creates a Datadog Event for each alert """ required_options = frozenset(['datadog_api_key', 'datadog_app_key']) def __init__(self, rule): super(DatadogAlerter, self).__init__(rule) - self.dd_api_key = self.rule.get('datadog_api_key', None) - self.dd_app_key = self.rule.get('datadog_app_key', None) + self.dd_api_key = self.rule['datadog_api_key'] + self.dd_app_key = self.rule['datadog_app_key'] def alert(self, matches): url = 'https://api.datadoghq.com/api/v1/events' diff --git a/elastalert/alerters/dingtalk.py b/elastalert/alerters/dingtalk.py index 3c5282f1..e87eca6b 100644 --- a/elastalert/alerters/dingtalk.py +++ b/elastalert/alerters/dingtalk.py @@ -11,13 +11,13 @@ class DingTalkAlerter(Alerter): """ Creates a DingTalk room message for each alert """ - required_options = frozenset(['dingtalk_access_token', 'dingtalk_msgtype']) + required_options = frozenset(['dingtalk_access_token']) def __init__(self, rule): super(DingTalkAlerter, self).__init__(rule) - self.dingtalk_access_token = self.rule.get('dingtalk_access_token') + self.dingtalk_access_token = self.rule['dingtalk_access_token'] self.dingtalk_webhook_url = 'https://oapi.dingtalk.com/robot/send?access_token=%s' % (self.dingtalk_access_token) - self.dingtalk_msgtype = self.rule.get('dingtalk_msgtype') + self.dingtalk_msgtype = self.rule.get('dingtalk_msgtype', 'text') self.dingtalk_single_title = self.rule.get('dingtalk_single_title', 'elastalert') self.dingtalk_single_url = self.rule.get('dingtalk_single_url', '') self.dingtalk_btn_orientation = self.rule.get('dingtalk_btn_orientation', '') diff --git a/elastalert/alerters/httppost.py b/elastalert/alerters/httppost.py index 74f1635b..d5f4aaff 100644 --- a/elastalert/alerters/httppost.py +++ b/elastalert/alerters/httppost.py @@ -13,11 +13,11 @@ class HTTPPostAlerter(Alerter): def __init__(self, rule): super(HTTPPostAlerter, self).__init__(rule) - post_url = self.rule.get('http_post_url') + post_url = self.rule['http_post_url'] if isinstance(post_url, str): post_url = [post_url] self.post_url = post_url - self.post_proxy = self.rule.get('http_post_proxy') + self.post_proxy = self.rule.get('http_post_proxy', None) self.post_payload = self.rule.get('http_post_payload', {}) self.post_static_payload = self.rule.get('http_post_static_payload', {}) self.post_all_values = self.rule.get('http_post_all_values', not self.post_payload) @@ -41,6 +41,8 @@ def alert(self, matches): verify = self.post_ca_certs else: verify = not self.post_ignore_ssl_errors + if self.post_ignore_ssl_errors: + requests.packages.urllib3.disable_warnings() headers.update(self.post_http_headers) proxies = {'https': self.post_proxy} if self.post_proxy else None diff --git a/elastalert/alerters/teams.py b/elastalert/alerters/teams.py index 201159ba..fb33fe31 100644 --- a/elastalert/alerters/teams.py +++ b/elastalert/alerters/teams.py @@ -8,7 +8,7 @@ class MsTeamsAlerter(Alerter): """ Creates a Microsoft Teams Conversation Message for each alert """ - required_options = frozenset(['ms_teams_webhook_url', 'ms_teams_alert_summary']) + required_options = frozenset(['ms_teams_webhook_url']) def __init__(self, rule): super(MsTeamsAlerter, self).__init__(rule) diff --git a/elastalert/alerters/zabbix.py b/elastalert/alerters/zabbix.py index 533ac7ef..da96f0ed 100644 --- a/elastalert/alerters/zabbix.py +++ b/elastalert/alerters/zabbix.py @@ -53,8 +53,8 @@ def __init__(self, *args): self.zbx_sender_host = self.rule.get('zbx_sender_host', 'localhost') self.zbx_sender_port = self.rule.get('zbx_sender_port', 10051) - self.zbx_host = self.rule.get('zbx_host') - self.zbx_key = self.rule.get('zbx_key') + self.zbx_host = self.rule['zbx_host'] + self.zbx_key = self.rule['zbx_key'] self.timestamp_field = self.rule.get('timestamp_field', '@timestamp') self.timestamp_type = self.rule.get('timestamp_type', 'iso') self.timestamp_strptime = self.rule.get('timestamp_strptime', '%Y-%m-%dT%H:%M:%S.%fZ') diff --git a/pytest.ini b/pytest.ini index 0ad3341d..259ba35a 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,3 +1,5 @@ [pytest] markers = elasticsearch: mark a test as using elasticsearch. +filterwarnings = + ignore::pytest.PytestUnhandledThreadExceptionWarning \ No newline at end of file diff --git a/requirements-dev.txt b/requirements-dev.txt index f3d183c2..09da6ae2 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ m2r2 pluggy>=0.12.0 pre-commit pylint<2.9 -pytest==6.1.2 +pytest==6.2.4 pytest-xdist==2.2.1 setuptools sphinx_rtd_theme diff --git a/tests/alerters/chatwork_test.py b/tests/alerters/chatwork_test.py index 8d60a0f3..bc81ae34 100644 --- a/tests/alerters/chatwork_test.py +++ b/tests/alerters/chatwork_test.py @@ -101,3 +101,57 @@ def test_chatwork_ea_exception(): alert.alert([match]) except EAException: assert True + + +def test_chatwork_getinfo(): + rule = { + 'name': 'Test Chatwork Rule', + 'type': 'any', + 'chatwork_apikey': 'xxxx1', + 'chatwork_room_id': 'xxxx2', + 'alert': [] + } + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = ChatworkAlerter(rule) + + expected_data = { + "type": "chatwork", + "chatwork_room_id": "xxxx2" + } + actual_data = alert.get_info() + assert expected_data == actual_data + + +@pytest.mark.parametrize('chatwork_apikey, chatwork_room_id, expected_data', [ + ('', '', True), + ('xxxx1', '', True), + ('', 'xxxx2', True), + ('xxxx1', 'xxxx2', + { + "type": "chatwork", + "chatwork_room_id": "xxxx2" + }), +]) +def test_chatwork_key_error(chatwork_apikey, chatwork_room_id, expected_data): + try: + rule = { + 'name': 'Test Chatwork Rule', + 'type': 'any', + 'alert': [] + } + + if chatwork_apikey != '': + rule['chatwork_apikey'] = chatwork_apikey + + if chatwork_room_id != '': + rule['chatwork_room_id'] = chatwork_room_id + + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = ChatworkAlerter(rule) + + actual_data = alert.get_info() + assert expected_data == actual_data + except KeyError: + assert expected_data diff --git a/tests/alerters/datadog_test.py b/tests/alerters/datadog_test.py index 956396e9..74b7c654 100644 --- a/tests/alerters/datadog_test.py +++ b/tests/alerters/datadog_test.py @@ -67,3 +67,56 @@ def test_datadog_alerterea_exception(): alert.alert([match]) except EAException: assert True + + +def test_datadog_getinfo(): + rule = { + 'name': 'Test Datadog Event Alerter', + 'type': 'any', + 'datadog_api_key': 'test-api-key', + 'datadog_app_key': 'test-app-key', + 'alert': [], + 'alert_subject': 'Test Datadog Event Alert' + } + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = DatadogAlerter(rule) + + expected_data = {'type': 'datadog'} + actual_data = alert.get_info() + assert expected_data == actual_data + + +@pytest.mark.parametrize('datadog_api_key, datadog_app_key, expected_data', [ + ('', '', True), + ('xxxx1', '', True), + ('', 'xxxx2', True), + ('xxxx1', 'xxxx2', + { + 'type': 'datadog' + }), +]) +def test_datadog_key_error(datadog_api_key, datadog_app_key, expected_data): + try: + rule = { + 'name': 'Test Datadog Event Alerter', + 'type': 'any', + 'alert': [], + 'alert_subject': 'Test Datadog Event Alert' + } + + if datadog_api_key != '': + rule['datadog_api_key'] = datadog_api_key + + if datadog_app_key != '': + rule['datadog_app_key'] = datadog_app_key + + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = DatadogAlerter(rule) + + expected_data = {'type': 'datadog'} + actual_data = alert.get_info() + assert expected_data == actual_data + except KeyError: + assert expected_data diff --git a/tests/alerters/dingtalk_test.py b/tests/alerters/dingtalk_test.py index 77f47556..5a4a59ae 100644 --- a/tests/alerters/dingtalk_test.py +++ b/tests/alerters/dingtalk_test.py @@ -292,3 +292,53 @@ def test_dingtalk_ea_exception(): alert.alert([match]) except EAException: assert True + + +def test_dingtalk_getinfo(): + rule = { + 'name': 'Test DingTalk Rule', + 'type': 'any', + 'dingtalk_access_token': 'xxxxxxx', + 'alert': [], + 'alert_subject': 'Test DingTalk' + } + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = DingTalkAlerter(rule) + + expected_data = { + 'type': 'dingtalk', + "dingtalk_webhook_url": 'https://oapi.dingtalk.com/robot/send?access_token=xxxxxxx' + } + actual_data = alert.get_info() + assert expected_data == actual_data + + +@pytest.mark.parametrize('dingtalk_access_token,, expected_data', [ + ('', True), + ('xxxxxxx', + { + 'type': 'dingtalk', + "dingtalk_webhook_url": 'https://oapi.dingtalk.com/robot/send?access_token=xxxxxxx' + }), +]) +def test_dingtalk_key_error(dingtalk_access_token, expected_data): + try: + rule = { + 'name': 'Test DingTalk Rule', + 'type': 'any', + 'alert': [], + 'alert_subject': 'Test DingTalk' + } + + if dingtalk_access_token != '': + rule['dingtalk_access_token'] = dingtalk_access_token + + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = DingTalkAlerter(rule) + + actual_data = alert.get_info() + assert expected_data == actual_data + except KeyError: + assert expected_data diff --git a/tests/alerters/httppost_test.py b/tests/alerters/httppost_test.py index bff4aa20..e84f45fe 100644 --- a/tests/alerters/httppost_test.py +++ b/tests/alerters/httppost_test.py @@ -213,49 +213,31 @@ def test_http_alerter_headers(): assert expected_data == json.loads(mock_post_request.call_args_list[0][1]['data']) -def test_http_alerter_post_ca_certs_true(): +@pytest.mark.parametrize('ca_certs, ignore_ssl_errors, excpet_verify', [ + ('', '', True), + ('', True, False), + ('', False, True), + (True, '', True), + (True, True, True), + (True, False, True), + (False, '', True), + (False, True, False), + (False, False, True) +]) +def test_http_alerter_post_ca_certs(ca_certs, ignore_ssl_errors, excpet_verify): rule = { 'name': 'Test HTTP Post Alerter Without Payload', 'type': 'any', 'http_post_url': 'http://test.webhook.url', 'http_post_static_payload': {'name': 'somestaticname'}, - 'http_post_ca_certs': True, 'alert': [] } - rules_loader = FileRulesLoader({}) - rules_loader.load_modules(rule) - alert = HTTPPostAlerter(rule) - match = { - '@timestamp': '2017-01-01T00:00:00', - 'somefield': 'foobarbaz' - } - with mock.patch('requests.post') as mock_post_request: - alert.alert([match]) - expected_data = { - '@timestamp': '2017-01-01T00:00:00', - 'somefield': 'foobarbaz', - 'name': 'somestaticname' - } - mock_post_request.assert_called_once_with( - rule['http_post_url'], - data=mock.ANY, - headers={'Content-Type': 'application/json', 'Accept': 'application/json;charset=utf-8'}, - proxies=None, - timeout=10, - verify=True - ) - assert expected_data == json.loads(mock_post_request.call_args_list[0][1]['data']) + if ca_certs != '': + rule['http_post_ca_certs'] = ca_certs + if ignore_ssl_errors != '': + rule['http_post_ignore_ssl_errors'] = ignore_ssl_errors -def test_http_alerter_post_ca_certs_false(): - rule = { - 'name': 'Test HTTP Post Alerter Without Payload', - 'type': 'any', - 'http_post_url': 'http://test.webhook.url', - 'http_post_static_payload': {'name': 'somestaticname'}, - 'http_post_ca_certs': False, - 'alert': [] - } rules_loader = FileRulesLoader({}) rules_loader.load_modules(rule) alert = HTTPPostAlerter(rule) @@ -276,7 +258,7 @@ def test_http_alerter_post_ca_certs_false(): headers={'Content-Type': 'application/json', 'Accept': 'application/json;charset=utf-8'}, proxies=None, timeout=10, - verify=True + verify=excpet_verify ) assert expected_data == json.loads(mock_post_request.call_args_list[0][1]['data']) @@ -303,3 +285,51 @@ def test_http_alerter_post_ea_exception(): alert.alert([match]) except EAException: assert True + + +def test_http_getinfo(): + rule = { + 'name': 'Test HTTP Post Alerter Without Payload', + 'type': 'any', + 'http_post_url': 'http://test.webhook.url', + 'alert': [] + } + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = HTTPPostAlerter(rule) + + expected_data = { + 'type': 'http_post', + 'http_post_webhook_url': ['http://test.webhook.url'] + } + actual_data = alert.get_info() + assert expected_data == actual_data + + +@pytest.mark.parametrize('http_post_webhook_url, expected_data', [ + ('', True), + ('http://test.webhook.url', + { + 'type': 'http_post', + 'http_post_webhook_url': ['http://test.webhook.url'] + }), +]) +def test_http_key_error(http_post_webhook_url, expected_data): + try: + rule = { + 'name': 'Test HTTP Post Alerter Without Payload', + 'type': 'any', + 'alert': [] + } + + if http_post_webhook_url != '': + rule['http_post_webhook_url'] = http_post_webhook_url + + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = HTTPPostAlerter(rule) + + actual_data = alert.get_info() + assert expected_data == actual_data + except KeyError: + assert expected_data diff --git a/tests/alerters/teams_test.py b/tests/alerters/teams_test.py index a55a402b..72970427 100644 --- a/tests/alerters/teams_test.py +++ b/tests/alerters/teams_test.py @@ -143,3 +143,53 @@ def test_ms_teams_ea_exception(): alert.alert([match]) except EAException: assert True + + +def test_ms_teams_getinfo(): + rule = { + 'name': 'Test Rule', + 'type': 'any', + 'ms_teams_webhook_url': 'http://test.webhook.url', + 'alert_subject': 'Cool subject', + 'alert': [] + } + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = MsTeamsAlerter(rule) + + expected_data = { + 'type': 'ms_teams', + 'ms_teams_webhook_url': ['http://test.webhook.url'] + } + actual_data = alert.get_info() + assert expected_data == actual_data + + +@pytest.mark.parametrize('ms_teams_webhook_url, expected_data', [ + ('', True), + ('http://test.webhook.url', + { + 'type': 'ms_teams', + 'ms_teams_webhook_url': ['http://test.webhook.url'] + }) +]) +def test_ms_teams_key_error(ms_teams_webhook_url, expected_data): + try: + rule = { + 'name': 'Test Rule', + 'type': 'any', + 'alert_subject': 'Cool subject', + 'alert': [] + } + + if ms_teams_webhook_url != '': + rule['ms_teams_webhook_url'] = ms_teams_webhook_url + + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = MsTeamsAlerter(rule) + + actual_data = alert.get_info() + assert expected_data == actual_data + except KeyError: + assert expected_data diff --git a/tests/alerters/zabbix_test.py b/tests/alerters/zabbix_test.py index a25cae9c..1cee2ec7 100644 --- a/tests/alerters/zabbix_test.py +++ b/tests/alerters/zabbix_test.py @@ -1,3 +1,4 @@ +import pytest import mock from elastalert.alerters.zabbix import ZabbixAlerter @@ -32,3 +33,59 @@ def test_zabbix_basic(): } alerter_args = mock_zbx_send.call_args.args assert vars(alerter_args[0][0]) == zabbix_metrics + + +def test_zabbix_getinfo(): + rule = { + 'name': 'Basic Zabbix test', + 'type': 'any', + 'alert_text_type': 'alert_text_only', + 'alert': [], + 'alert_subject': 'Test Zabbix', + 'zbx_host': 'example.com', + 'zbx_key': 'example-key' + } + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = ZabbixAlerter(rule) + + expected_data = { + 'type': 'zabbix Alerter' + } + actual_data = alert.get_info() + assert expected_data == actual_data + + +@pytest.mark.parametrize('zbx_host, zbx_key, expected_data', [ + ('', '', True), + ('example.com', '', True), + ('', 'example-key', True), + ('example.com', 'example-key', + { + 'type': 'zabbix Alerter' + }) +]) +def test_zabbix_key_error(zbx_host, zbx_key, expected_data): + try: + rule = { + 'name': 'Basic Zabbix test', + 'type': 'any', + 'alert_text_type': 'alert_text_only', + 'alert': [], + 'alert_subject': 'Test Zabbix' + } + + if zbx_host != '': + rule['zbx_host'] = zbx_host + + if zbx_key != '': + rule['zbx_key'] = zbx_key + + rules_loader = FileRulesLoader({}) + rules_loader.load_modules(rule) + alert = ZabbixAlerter(rule) + + actual_data = alert.get_info() + assert expected_data == actual_data + except KeyError: + assert expected_data diff --git a/tox.ini b/tox.ini index 32df3ef9..6f42fae2 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ envlist = py39,docs [testenv] deps = -rrequirements-dev.txt commands = - pytest --cov=elastalert --cov-report=term-missing --cov-branch --strict tests/ -n 4 {posargs} + pytest --cov=elastalert --cov-report=term-missing --cov-branch --strict-markers tests/ -n 4 {posargs} flake8 . [testenv:lint]