Skip to content

Commit fedcfaf

Browse files
committed
Proper SSL implementation, including optional certificate verification
1 parent d9296e8 commit fedcfaf

File tree

4 files changed

+53
-10
lines changed

4 files changed

+53
-10
lines changed

docs/connection.rst

+3-3
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ Connection Selector
3636
:members:
3737

3838

39-
Connection
40-
----------
39+
Urllib3HttpConnection (default connection_class)
40+
------------------------------------------------
4141

42-
.. autoclass:: Connection
42+
.. autoclass:: Urllib3HttpConnection
4343
:members:

elasticsearch/connection/http_requests.py

+14-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
REQUESTS_AVAILABLE = False
77

88
from .base import Connection
9-
from ..exceptions import ConnectionError, ImproperlyConfigured, ConnectionTimeout
9+
from ..exceptions import ConnectionError, ImproperlyConfigured, ConnectionTimeout, SSLError
1010
from ..compat import urlencode
1111

1212
class RequestsHttpConnection(Connection):
@@ -16,8 +16,12 @@ class RequestsHttpConnection(Connection):
1616
:arg http_auth: optional http auth information as either ':' separated
1717
string or a tuple
1818
:arg use_ssl: use ssl for the connection if `True`
19+
:arg verify_certs: whether to verify SSL certificates
20+
:arg ca_certs: optional path to CA bundle. By default standard requests'
21+
bundle will be used.
1922
"""
20-
def __init__(self, host='localhost', port=9200, http_auth=None, use_ssl=False, **kwargs):
23+
def __init__(self, host='localhost', port=9200, http_auth=None,
24+
use_ssl=False, verify_certs=False, ca_certs=None, **kwargs):
2125
if not REQUESTS_AVAILABLE:
2226
raise ImproperlyConfigured("Please install requests to use RequestsHttpConnection.")
2327

@@ -32,7 +36,11 @@ def __init__(self, host='localhost', port=9200, http_auth=None, use_ssl=False, *
3236
's' if use_ssl else '',
3337
host, port, self.url_prefix
3438
)
35-
39+
self.session.verify = verify_certs
40+
if ca_certs:
41+
if not verify_certs:
42+
raise ImproperlyConfigured("You cannot pass CA certificates when verify SSL is off.")
43+
self.session.verify = ca_certs
3644

3745
def perform_request(self, method, url, params=None, body=None, timeout=None, ignore=()):
3846
url = self.base_url + url
@@ -44,6 +52,9 @@ def perform_request(self, method, url, params=None, body=None, timeout=None, ign
4452
response = self.session.request(method, url, data=body, timeout=timeout or self.timeout)
4553
duration = time.time() - start
4654
raw_data = response.text
55+
except requests.exceptions.SSLError as e:
56+
self.log_request_fail(method, url, body, time.time() - start, exception=e)
57+
raise SSLError('N/A', str(e), e)
4758
except requests.Timeout as e:
4859
self.log_request_fail(method, url, body, time.time() - start, exception=e)
4960
raise ConnectionTimeout('TIMEOUT', str(e), e)

elasticsearch/connection/http_urllib3.py

+21-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import time
22
import urllib3
3-
from urllib3.exceptions import ReadTimeoutError
3+
from urllib3.exceptions import ReadTimeoutError, SSLError as UrllibSSLError
44

55
from .base import Connection
6-
from ..exceptions import ConnectionError, ConnectionTimeout
6+
from ..exceptions import ConnectionError, ImproperlyConfigured, ConnectionTimeout, SSLError
77
from ..compat import urlencode
88

99
class Urllib3HttpConnection(Connection):
@@ -13,10 +13,17 @@ class Urllib3HttpConnection(Connection):
1313
:arg http_auth: optional http auth information as either ':' separated
1414
string or a tuple
1515
:arg use_ssl: use ssl for the connection if `True`
16+
:arg verify_certs: whether to verify SSL certificates
17+
:arg ca_certs: optional path to CA bundle. See
18+
http://urllib3.readthedocs.org/en/latest/security.html#using-certifi-with-urllib3
19+
for instructions how to get default set
1620
:arg maxsize: the maximum number of connections which will be kept open to
1721
this host.
1822
"""
19-
def __init__(self, host='localhost', port=9200, http_auth=None, use_ssl=False, maxsize=10, **kwargs):
23+
def __init__(self, host='localhost', port=9200, http_auth=None,
24+
use_ssl=False, verify_certs=False, ca_certs=None, maxsize=10,
25+
**kwargs):
26+
2027
super(Urllib3HttpConnection, self).__init__(host=host, port=port, **kwargs)
2128
self.headers = {}
2229
if http_auth is not None:
@@ -25,10 +32,17 @@ def __init__(self, host='localhost', port=9200, http_auth=None, use_ssl=False, m
2532
self.headers = urllib3.make_headers(basic_auth=http_auth)
2633

2734
pool_class = urllib3.HTTPConnectionPool
35+
kw = {}
2836
if use_ssl:
2937
pool_class = urllib3.HTTPSConnectionPool
3038

31-
self.pool = pool_class(host, port=port, timeout=self.timeout, maxsize=maxsize)
39+
if verify_certs:
40+
kw['cert_reqs'] = 'CERT_REQUIRED'
41+
kw['ca_certs'] = ca_certs
42+
elif ca_certs:
43+
raise ImproperlyConfigured("You cannot pass CA certificates when verify SSL is off.")
44+
45+
self.pool = pool_class(host, port=port, timeout=self.timeout, maxsize=maxsize, **kw)
3246

3347
def perform_request(self, method, url, params=None, body=None, timeout=None, ignore=()):
3448
url = self.url_prefix + url
@@ -50,6 +64,9 @@ def perform_request(self, method, url, params=None, body=None, timeout=None, ign
5064
response = self.pool.urlopen(method, url, body, retries=False, headers=self.headers, **kw)
5165
duration = time.time() - start
5266
raw_data = response.data.decode('utf-8')
67+
except UrllibSSLError as e:
68+
self.log_request_fail(method, full_url, body, time.time() - start, exception=e)
69+
raise SSLError('N/A', str(e), e)
5370
except ReadTimeoutError as e:
5471
self.log_request_fail(method, full_url, body, time.time() - start, exception=e)
5572
raise ConnectionTimeout('TIMEOUT', str(e), e)

elasticsearch/exceptions.py

+15
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ class ImproperlyConfigured(Exception):
88
Exception raised when the config passed to the client is inconsistent or invalid.
99
"""
1010

11+
1112
class ElasticsearchException(Exception):
1213
"""
1314
Base class for all exceptions raised by this package's operations (doesn't
@@ -61,6 +62,10 @@ def __str__(self):
6162
self.error, self.info.__class__.__name__, self.info)
6263

6364

65+
class SSLError(ConnectionError):
66+
""" Error raised when encountering SSL errors. """
67+
68+
6469
class ConnectionTimeout(ConnectionError):
6570
""" A network timeout. """
6671
def __str__(self):
@@ -79,9 +84,19 @@ class ConflictError(TransportError):
7984
class RequestError(TransportError):
8085
""" Exception representing a 400 status code. """
8186

87+
88+
class AuthenticationException(TransportError):
89+
""" Exception representing a 401 status code. """
90+
91+
92+
class AuthorizationException(TransportError):
93+
""" Exception representing a 403 status code. """
94+
8295
# more generic mappings from status_code to python exceptions
8396
HTTP_EXCEPTIONS = {
8497
400: RequestError,
98+
401: AuthenticationException,
99+
403: AuthorizationException,
85100
404: NotFoundError,
86101
409: ConflictError,
87102
}

0 commit comments

Comments
 (0)