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 support for jira authentication via Personal Access Token #750

Merged
merged 8 commits into from
Mar 5, 2022
Merged
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
9 changes: 5 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,24 @@

## Other changes
- Load Jinja template when loading an alert - [#654](https://github.com/jertel/elastalert2/pull/654) - @thib12
- tox 3.24.4 to 3.24.5 - [#655](https://github.com/jertel/elastalert2/pull/655) - @nsano-rururu
- sphinx 4.3.2 to 4.4.0 - [#661](https://github.com/jertel/elastalert2/pull/661) - @nsano-rururu
- Upgrade tox 3.24.4 to 3.24.5 - [#655](https://github.com/jertel/elastalert2/pull/655) - @nsano-rururu
- Upgrade sphinx 4.3.2 to 4.4.0 - [#661](https://github.com/jertel/elastalert2/pull/661) - @nsano-rururu
- [Docs] Fix Running Docker container - [#674](https://github.com/jertel/elastalert2/pull/674) - @nsano-rururu
- [Exotel] Added exotel_message_body to schema.yaml - [#685](https://github.com/jertel/elastalert2/pull/685) - @nsano-rururu
- Pytest 6.2.5 to 7.0.0 - [#696](https://github.com/jertel/elastalert2/pull/696) - @nsano-rururu
- Upgrade Pytest 6.2.5 to 7.0.0 - [#696](https://github.com/jertel/elastalert2/pull/696) - @nsano-rururu
- python-dateutil version specification change - [#704](https://github.com/jertel/elastalert2/pull/704) - @nsano-rururu
- Update minimum versions for third-party dependencies in requirements.txt and setup.py - [#705](https://github.com/jertel/elastalert2/pull/705) - @nsano-rururu
- [Docs] Document updates for Alerts and email addresses etc - [#706](https://github.com/jertel/elastalert2/pull/706) - @nsano-rururu
- [Docs] Update of RuleType Configuration Cheat Sheet - [#707](https://github.com/jertel/elastalert2/pull/707) - @nsano-rururu
- Pytest 7.0.0 to 7.0.1 - [#710](https://github.com/jertel/elastalert2/pull/710) - @nsano-rururu
- Upgrade Pytest 7.0.0 to 7.0.1 - [#710](https://github.com/jertel/elastalert2/pull/710) - @nsano-rururu
- Fixing jira_transition_to schema bug. Change property type from boolean to string - [#721](https://github.com/jertel/elastalert2/pull/721) - @toxisch
- Begin Elasticsearch 8 support - ElastAlert 2 now supports setup with fresh ES 8 instances, and works with some alert types - [#731](https://github.com/jertel/elastalert2/pull/731) - @ferozsalam
- Enable dynamic setting of rules volume in helm chart - [#732](https://github.com/jertel/elastalert2/pull/732) - @ChrisFraun
- Do not install tests via pip install - [#733](https://github.com/jertel/elastalert2/pull/733) - @buzzdeee
- [Docs] Add Elasticsearch 8 support documentation - [#735](https://github.com/jertel/elastalert2/pull/735) - @ferozsalam
- Remove download_dashboard - [#740](https://github.com/jertel/elastalert2/pull/740) - @nsano-rururu
- [Docs] Added documentation for metric|spike aggregation rule types for percentiles - [e682ea8](https://github.com/jertel/elastalert2/commit/e682ea8113bf9f413b6339e6803b5262881f2b30)- @jertel
- [Jira] Add support for Jira authentication via Personal Access Token - [#750](https://github.com/jertel/elastalert2/pull/750) - @buzzdeee

# 2.3.0

Expand Down
16 changes: 11 additions & 5 deletions docs/source/ruletypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2354,8 +2354,8 @@ Jira
~~~~

The Jira alerter will open a ticket on Jira whenever an alert is triggered. You must have a service account for ElastAlert 2 to connect with.
The credentials of the service account are loaded from a separate file. The ticket number will be written to the alert pipeline, and if it
is followed by an email alerter, a link will be included in the email.
The credentials of the service account are loaded from a separate file. Credentials can either be username and password or the Personal Access Token.
The ticket number will be written to the alert pipeline, and if it is followed by an email alerter, a link will be included in the email.

This alert requires four additional options:

Expand All @@ -2367,11 +2367,17 @@ This alert requires four additional options:

``jira_account_file``: The path to the file which contains Jira account credentials.

For an example Jira account file, see ``examples/rules/jira_acct.yaml``. The account file is also yaml formatted and must contain two fields:
For an example Jira account file, see ``examples/rules/jira_acct.yaml``. The account file is a YAML formatted file.

``user``: The username.
When using user/password authentication, the Jira account file must contain two fields:

``password``: The password.
``user``: The username to authenticate with Jira.

``password``: The password to authenticate with Jira.

When using a Personal Access Token, the Jira account file must contain a single field:

``apikey``: The Personal Access Token for authenticating with Jira.

Optional:

Expand Down
26 changes: 25 additions & 1 deletion elastalert/alerters/jira.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import datetime
import sys
import os

from elastalert.alerts import Alerter
from elastalert.alerts import BasicMatchString
from elastalert.util import (elastalert_logger, lookup_es_key, pretty_ts, ts_now,
ts_to_dt, EAException)
from elastalert.yaml import read_yaml
from jira.client import JIRA
from jira.exceptions import JIRAError

Expand Down Expand Up @@ -103,7 +105,10 @@ def __init__(self, rule):
self.reset_jira_args()

try:
self.client = JIRA(self.server, basic_auth=(self.user, self.password))
if hasattr(self, 'apikey'):
self.client = JIRA(self.server, token_auth=(self.apikey))
else:
self.client = JIRA(self.server, basic_auth=(self.user, self.password))
self.get_priorities()
self.jira_fields = self.client.fields()
self.get_arbitrary_fields()
Expand Down Expand Up @@ -393,3 +398,22 @@ def create_default_title(self, matches, for_search=False):

def get_info(self):
return {'type': 'jira'}

def get_account(self, account_file):
""" Gets the username and password, or the apikey, from an account file.

:param account_file: Path to the file which contains the credentials.
It can be either an absolute file path or one that is relative to the given rule.
"""
if os.path.isabs(account_file):
account_file_path = account_file
else:
account_file_path = os.path.join(os.path.dirname(self.rule['rule_file']), account_file)
account_conf = read_yaml(account_file_path)
if not (('user' in account_conf and 'password' in account_conf) or 'apikey' in account_conf):
raise EAException('Account file must have user and password fields, or apikey field')
if 'apikey' in account_conf:
self.apikey = account_conf['apikey']
else:
self.user = account_conf['user']
self.password = account_conf['password']
53 changes: 41 additions & 12 deletions tests/alerters/jira_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def test_jira(caplog):
mock_priority = mock.Mock(id='5')

with mock.patch('elastalert.alerters.jira.JIRA') as mock_jira, \
mock.patch('elastalert.alerts.read_yaml') as mock_open:
mock.patch('elastalert.alerters.jira.read_yaml') as mock_open:
mock_open.return_value = {'user': 'jirauser', 'password': 'jirapassword'}
mock_jira.return_value.priorities.return_value = [mock_priority]
mock_jira.return_value.fields.return_value = []
Expand Down Expand Up @@ -85,7 +85,7 @@ def test_jira(caplog):
# Search called if jira_bump_tickets
rule['jira_bump_tickets'] = True
with mock.patch('elastalert.alerters.jira.JIRA') as mock_jira, \
mock.patch('elastalert.alerts.read_yaml') as mock_open:
mock.patch('elastalert.alerters.jira.read_yaml') as mock_open:
mock_open.return_value = {'user': 'jirauser', 'password': 'jirapassword'}
mock_jira.return_value = mock.Mock()
mock_jira.return_value.search_issues.return_value = []
Expand All @@ -101,7 +101,7 @@ def test_jira(caplog):
# Remove a field if jira_ignore_in_title set
rule['jira_ignore_in_title'] = 'test_term'
with mock.patch('elastalert.alerters.jira.JIRA') as mock_jira, \
mock.patch('elastalert.alerts.read_yaml') as mock_open:
mock.patch('elastalert.alerters.jira.read_yaml') as mock_open:
mock_open.return_value = {'user': 'jirauser', 'password': 'jirapassword'}
mock_jira.return_value = mock.Mock()
mock_jira.return_value.search_issues.return_value = []
Expand All @@ -115,7 +115,7 @@ def test_jira(caplog):

# Issue is still created if search_issues throws an exception
with mock.patch('elastalert.alerters.jira.JIRA') as mock_jira, \
mock.patch('elastalert.alerts.read_yaml') as mock_open:
mock.patch('elastalert.alerters.jira.read_yaml') as mock_open:
mock_open.return_value = {'user': 'jirauser', 'password': 'jirapassword'}
mock_jira.return_value = mock.Mock()
mock_jira.return_value.search_issues.side_effect = JIRAError
Expand All @@ -138,7 +138,7 @@ def test_jira(caplog):
# Check ticket is bumped if it is updated 4 days ago
mock_issue.fields.updated = str(ts_now() - datetime.timedelta(days=4))
with mock.patch('elastalert.alerters.jira.JIRA') as mock_jira, \
mock.patch('elastalert.alerts.read_yaml') as mock_open:
mock.patch('elastalert.alerters.jira.read_yaml') as mock_open:
mock_open.return_value = {'user': 'jirauser', 'password': 'jirapassword'}
mock_jira.return_value = mock.Mock()
mock_jira.return_value.search_issues.return_value = [mock_issue]
Expand All @@ -154,7 +154,7 @@ def test_jira(caplog):
# Check ticket is bumped is not bumped if ticket is updated right now
mock_issue.fields.updated = str(ts_now())
with mock.patch('elastalert.alerters.jira.JIRA') as mock_jira, \
mock.patch('elastalert.alerts.read_yaml') as mock_open:
mock.patch('elastalert.alerters.jira.read_yaml') as mock_open:
mock_open.return_value = {'user': 'jirauser', 'password': 'jirapassword'}
mock_jira.return_value = mock.Mock()
mock_jira.return_value.search_issues.return_value = [mock_issue]
Expand Down Expand Up @@ -189,7 +189,7 @@ def test_jira(caplog):
{'name': 'affected user', 'id': 'affected_user_id', 'schema': {'type': 'string'}}
]
with mock.patch('elastalert.alerters.jira.JIRA') as mock_jira, \
mock.patch('elastalert.alerts.read_yaml') as mock_open:
mock.patch('elastalert.alerters.jira.read_yaml') as mock_open:
mock_open.return_value = {'user': 'jirauser', 'password': 'jirapassword'}
mock_jira.return_value = mock.Mock()
mock_jira.return_value.search_issues.return_value = [mock_issue]
Expand Down Expand Up @@ -262,7 +262,7 @@ def test_jira_arbitrary_field_support():
]

with mock.patch('elastalert.alerters.jira.JIRA') as mock_jira, \
mock.patch('elastalert.alerts.read_yaml') as mock_open:
mock.patch('elastalert.alerters.jira.read_yaml') as mock_open:
mock_open.return_value = {'user': 'jirauser', 'password': 'jirapassword'}
mock_jira.return_value.priorities.return_value = [mock_priority]
mock_jira.return_value.fields.return_value = mock_fields
Expand Down Expand Up @@ -303,7 +303,7 @@ def test_jira_arbitrary_field_support():
rule['jira_nonexistent_field'] = 'nonexistent field value'

with mock.patch('elastalert.alerters.jira.JIRA') as mock_jira, \
mock.patch('elastalert.alerts.read_yaml') as mock_open:
mock.patch('elastalert.alerters.jira.read_yaml') as mock_open:
mock_open.return_value = {'user': 'jirauser', 'password': 'jirapassword'}
mock_jira.return_value.priorities.return_value = [mock_priority]
mock_jira.return_value.fields.return_value = mock_fields
Expand All @@ -319,7 +319,7 @@ def test_jira_arbitrary_field_support():
rule['jira_watchers'] = 'invalid_watcher'

with mock.patch('elastalert.alerters.jira.JIRA') as mock_jira, \
mock.patch('elastalert.alerts.read_yaml') as mock_open:
mock.patch('elastalert.alerters.jira.read_yaml') as mock_open:
mock_open.return_value = {'user': 'jirauser', 'password': 'jirapassword'}
mock_jira.return_value.priorities.return_value = [mock_priority]
mock_jira.return_value.fields.return_value = mock_fields
Expand Down Expand Up @@ -356,7 +356,7 @@ def test_jira_getinfo():
mock_priority = mock.Mock(id='5')

with mock.patch('elastalert.alerters.jira.JIRA') as mock_jira, \
mock.patch('elastalert.alerts.read_yaml') as mock_open:
mock.patch('elastalert.alerters.jira.read_yaml') as mock_open:
mock_open.return_value = {'user': 'jirauser', 'password': 'jirapassword'}
mock_jira.return_value.priorities.return_value = [mock_priority]
mock_jira.return_value.fields.return_value = []
Expand Down Expand Up @@ -387,7 +387,7 @@ def test_jira_set_priority(caplog):
'rule_file': '/tmp/foo.yaml'
}
with mock.patch('elastalert.alerters.jira.JIRA'), \
mock.patch('elastalert.alerts.read_yaml') as mock_open:
mock.patch('elastalert.alerters.jira.read_yaml') as mock_open:
mock_open.return_value = {'user': 'jirauser', 'password': 'jirapassword'}
alert = JiraAlerter(rule)
alert.set_priority
Expand All @@ -396,3 +396,32 @@ def test_jira_set_priority(caplog):
'Priority 0 not found. Valid priorities are []') == caplog.record_tuples[0]
assert ('elastalert', logging.ERROR,
'Priority 0 not found. Valid priorities are []') == caplog.record_tuples[1]


def test_jira_auth_token(caplog):
description_txt = "Test authentication via apitoken"
rule = {
'name': 'test alert',
'jira_account_file': 'jirafile',
'type': mock_rule(),
'jira_project': 'testproject',
'jira_priority': 0,
'jira_issuetype': 'testtype',
'jira_server': 'jiraserver',
'jira_description': description_txt,
'jira_assignee': 'testuser',
'timestamp_field': '@timestamp',
'alert_subject': 'Issue {0} occurred at {1}',
'alert_subject_args': ['test_term', '@timestamp'],
'rule_file': '/tmp/foo.yaml'
}
with mock.patch('elastalert.alerters.jira.JIRA') as mock_jira, \
mock.patch('elastalert.alerters.jira.read_yaml') as mock_open:
mock_open.return_value = {'apikey': 'theapikey'}
alert = JiraAlerter(rule)
alert.set_priority
expected = [
mock.call('jiraserver', token_auth=('theapikey')),
]
# we only want to test authentication via apikey, the rest we don't care of
assert mock_jira.mock_calls[:1] == expected