Skip to content

Commit

Permalink
Client certificate support (#675)
Browse files Browse the repository at this point in the history
Adds the `ssl_client_cert`, `ssl_client_key`, `ssl_client_key_password`
and `ssl_client_ca` service parameters to the JIRA service, which allow
to connect to JIRA instances that are secured by SSL client certificates
issued by a private / self-signed CA.

The certificate key file may optionally be encrypted using a password,
which can be retrieved from an `@oracle` like regular passwords.

Closes: #675
  • Loading branch information
fdcds committed Jun 28, 2019
1 parent b30d84e commit 4007803
Showing 1 changed file with 48 additions and 6 deletions.
54 changes: 48 additions & 6 deletions bugwarrior/services/jira.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
from builtins import str


import six
import six, ssl
from jinja2 import Template
from jira.client import JIRA as BaseJIRA
from requests.cookies import RequestsCookieJar
from requests.adapters import HTTPAdapter
from dateutil.tz.tz import tzutc

from bugwarrior.config import asbool, die
Expand All @@ -15,6 +16,26 @@
log = logging.getLogger(__name__)


class SSLAdapter(HTTPAdapter):
def __init__(self, certfile, keyfile, password=None, cafile=None, *args, **kwargs):
self._certfile = certfile
self._keyfile = keyfile
self._password = password
self._cafile = cafile
super(self.__class__, self).__init__(*args, **kwargs)

def init_poolmanager(self, *args, **kwargs):
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
if self._certfile is not None and self._keyfile is not None:
context.load_cert_chain(certfile=self._certfile,
keyfile=self._keyfile,
password=self._password)
if self._cafile:
context.load_verify_locations(cafile=self._cafile)
kwargs['ssl_context'] = context
return super(self.__class__, self).init_poolmanager(*args, **kwargs)


# The below `ObliviousCookieJar` and `JIRA` classes are MIT Licensed.
# They were taken from this wonderful commit by @GaretJax
# https://github.com/GaretJax/lancet/commit/f175cb2ec9a2135fb78188cf0b9f621b51d88977
Expand All @@ -38,6 +59,12 @@ def _create_http_basic_session(self, *args, **kwargs):
# need cookies when accessing the API anyway, just ignore all of them.
self._session.cookies = ObliviousCookieJar()

client_cert = self._options.get('ssl_client_cert')
client_key = self._options.get('ssl_client_key')
client_key_password = self._options.get('ssl_client_key_password')
client_ca = self._options.get('ssl_client_ca')
self._session.mount('https://', SSLAdapter(client_cert, client_key, client_key_password, client_ca))

def close(self):
self._session.close()

Expand Down Expand Up @@ -273,12 +300,27 @@ def __init__(self, *args, **kw):
auth = dict(kerberos=True)
else:
auth = dict(basic_auth=(self.username, password))

options={
'server': self.config.get('base_uri'),
'rest_api_version': 'latest',
'verify': self.config.get('verify_ssl', default=True, to_type=asbool),
}
ssl_client_cert = self.config.get('ssl_client_cert')
ssl_client_key = self.config.get('ssl_client_key')
if ssl_client_cert is not None and ssl_client_key is not None:
ssl_client_key_password = None
if self.config.get('ssl_client_key_password') is not None:
ssl_client_key_password = self.get_password('ssl_client_key_password')
ssl_client_ca = self.config.get('ssl_client_ca')
options.update({
'ssl_client_cert': ssl_client_cert,
'ssl_client_key': ssl_client_key,
'ssl_client_key_password': ssl_client_key_password,
'ssl_client_ca': ssl_client_ca,
})
self.jira = JIRA(
options={
'server': self.config.get('base_uri'),
'rest_api_version': 'latest',
'verify': self.config.get('verify_ssl', default=True, to_type=asbool),
},
options=options,
**auth
)
self.import_labels_as_tags = self.config.get(
Expand Down

0 comments on commit 4007803

Please sign in to comment.