Proper SSL implementation, including optional certificate verification

This commit is contained in:
Honza Král
2014-11-11 03:20:16 +01:00
parent d9296e85b9
commit fedcfafe7e
4 changed files with 53 additions and 10 deletions
+3 -3
View File
@@ -36,8 +36,8 @@ Connection Selector
:members: :members:
Connection Urllib3HttpConnection (default connection_class)
---------- ------------------------------------------------
.. autoclass:: Connection .. autoclass:: Urllib3HttpConnection
:members: :members:
+14 -3
View File
@@ -6,7 +6,7 @@ except ImportError:
REQUESTS_AVAILABLE = False REQUESTS_AVAILABLE = False
from .base import Connection from .base import Connection
from ..exceptions import ConnectionError, ImproperlyConfigured, ConnectionTimeout from ..exceptions import ConnectionError, ImproperlyConfigured, ConnectionTimeout, SSLError
from ..compat import urlencode from ..compat import urlencode
class RequestsHttpConnection(Connection): class RequestsHttpConnection(Connection):
@@ -16,8 +16,12 @@ class RequestsHttpConnection(Connection):
:arg http_auth: optional http auth information as either ':' separated :arg http_auth: optional http auth information as either ':' separated
string or a tuple string or a tuple
:arg use_ssl: use ssl for the connection if `True` :arg use_ssl: use ssl for the connection if `True`
:arg verify_certs: whether to verify SSL certificates
:arg ca_certs: optional path to CA bundle. By default standard requests'
bundle will be used.
""" """
def __init__(self, host='localhost', port=9200, http_auth=None, use_ssl=False, **kwargs): def __init__(self, host='localhost', port=9200, http_auth=None,
use_ssl=False, verify_certs=False, ca_certs=None, **kwargs):
if not REQUESTS_AVAILABLE: if not REQUESTS_AVAILABLE:
raise ImproperlyConfigured("Please install requests to use RequestsHttpConnection.") raise ImproperlyConfigured("Please install requests to use RequestsHttpConnection.")
@@ -32,7 +36,11 @@ class RequestsHttpConnection(Connection):
's' if use_ssl else '', 's' if use_ssl else '',
host, port, self.url_prefix host, port, self.url_prefix
) )
self.session.verify = verify_certs
if ca_certs:
if not verify_certs:
raise ImproperlyConfigured("You cannot pass CA certificates when verify SSL is off.")
self.session.verify = ca_certs
def perform_request(self, method, url, params=None, body=None, timeout=None, ignore=()): def perform_request(self, method, url, params=None, body=None, timeout=None, ignore=()):
url = self.base_url + url url = self.base_url + url
@@ -44,6 +52,9 @@ class RequestsHttpConnection(Connection):
response = self.session.request(method, url, data=body, timeout=timeout or self.timeout) response = self.session.request(method, url, data=body, timeout=timeout or self.timeout)
duration = time.time() - start duration = time.time() - start
raw_data = response.text raw_data = response.text
except requests.exceptions.SSLError as e:
self.log_request_fail(method, url, body, time.time() - start, exception=e)
raise SSLError('N/A', str(e), e)
except requests.Timeout as e: except requests.Timeout as e:
self.log_request_fail(method, url, body, time.time() - start, exception=e) self.log_request_fail(method, url, body, time.time() - start, exception=e)
raise ConnectionTimeout('TIMEOUT', str(e), e) raise ConnectionTimeout('TIMEOUT', str(e), e)
+21 -4
View File
@@ -1,9 +1,9 @@
import time import time
import urllib3 import urllib3
from urllib3.exceptions import ReadTimeoutError from urllib3.exceptions import ReadTimeoutError, SSLError as UrllibSSLError
from .base import Connection from .base import Connection
from ..exceptions import ConnectionError, ConnectionTimeout from ..exceptions import ConnectionError, ImproperlyConfigured, ConnectionTimeout, SSLError
from ..compat import urlencode from ..compat import urlencode
class Urllib3HttpConnection(Connection): class Urllib3HttpConnection(Connection):
@@ -13,10 +13,17 @@ class Urllib3HttpConnection(Connection):
:arg http_auth: optional http auth information as either ':' separated :arg http_auth: optional http auth information as either ':' separated
string or a tuple string or a tuple
:arg use_ssl: use ssl for the connection if `True` :arg use_ssl: use ssl for the connection if `True`
:arg verify_certs: whether to verify SSL certificates
:arg ca_certs: optional path to CA bundle. See
http://urllib3.readthedocs.org/en/latest/security.html#using-certifi-with-urllib3
for instructions how to get default set
:arg maxsize: the maximum number of connections which will be kept open to :arg maxsize: the maximum number of connections which will be kept open to
this host. this host.
""" """
def __init__(self, host='localhost', port=9200, http_auth=None, use_ssl=False, maxsize=10, **kwargs): def __init__(self, host='localhost', port=9200, http_auth=None,
use_ssl=False, verify_certs=False, ca_certs=None, maxsize=10,
**kwargs):
super(Urllib3HttpConnection, self).__init__(host=host, port=port, **kwargs) super(Urllib3HttpConnection, self).__init__(host=host, port=port, **kwargs)
self.headers = {} self.headers = {}
if http_auth is not None: if http_auth is not None:
@@ -25,10 +32,17 @@ class Urllib3HttpConnection(Connection):
self.headers = urllib3.make_headers(basic_auth=http_auth) self.headers = urllib3.make_headers(basic_auth=http_auth)
pool_class = urllib3.HTTPConnectionPool pool_class = urllib3.HTTPConnectionPool
kw = {}
if use_ssl: if use_ssl:
pool_class = urllib3.HTTPSConnectionPool pool_class = urllib3.HTTPSConnectionPool
self.pool = pool_class(host, port=port, timeout=self.timeout, maxsize=maxsize) if verify_certs:
kw['cert_reqs'] = 'CERT_REQUIRED'
kw['ca_certs'] = ca_certs
elif ca_certs:
raise ImproperlyConfigured("You cannot pass CA certificates when verify SSL is off.")
self.pool = pool_class(host, port=port, timeout=self.timeout, maxsize=maxsize, **kw)
def perform_request(self, method, url, params=None, body=None, timeout=None, ignore=()): def perform_request(self, method, url, params=None, body=None, timeout=None, ignore=()):
url = self.url_prefix + url url = self.url_prefix + url
@@ -50,6 +64,9 @@ class Urllib3HttpConnection(Connection):
response = self.pool.urlopen(method, url, body, retries=False, headers=self.headers, **kw) response = self.pool.urlopen(method, url, body, retries=False, headers=self.headers, **kw)
duration = time.time() - start duration = time.time() - start
raw_data = response.data.decode('utf-8') raw_data = response.data.decode('utf-8')
except UrllibSSLError as e:
self.log_request_fail(method, full_url, body, time.time() - start, exception=e)
raise SSLError('N/A', str(e), e)
except ReadTimeoutError as e: except ReadTimeoutError as e:
self.log_request_fail(method, full_url, body, time.time() - start, exception=e) self.log_request_fail(method, full_url, body, time.time() - start, exception=e)
raise ConnectionTimeout('TIMEOUT', str(e), e) raise ConnectionTimeout('TIMEOUT', str(e), e)
+15
View File
@@ -8,6 +8,7 @@ class ImproperlyConfigured(Exception):
Exception raised when the config passed to the client is inconsistent or invalid. Exception raised when the config passed to the client is inconsistent or invalid.
""" """
class ElasticsearchException(Exception): class ElasticsearchException(Exception):
""" """
Base class for all exceptions raised by this package's operations (doesn't Base class for all exceptions raised by this package's operations (doesn't
@@ -61,6 +62,10 @@ class ConnectionError(TransportError):
self.error, self.info.__class__.__name__, self.info) self.error, self.info.__class__.__name__, self.info)
class SSLError(ConnectionError):
""" Error raised when encountering SSL errors. """
class ConnectionTimeout(ConnectionError): class ConnectionTimeout(ConnectionError):
""" A network timeout. """ """ A network timeout. """
def __str__(self): def __str__(self):
@@ -79,9 +84,19 @@ class ConflictError(TransportError):
class RequestError(TransportError): class RequestError(TransportError):
""" Exception representing a 400 status code. """ """ Exception representing a 400 status code. """
class AuthenticationException(TransportError):
""" Exception representing a 401 status code. """
class AuthorizationException(TransportError):
""" Exception representing a 403 status code. """
# more generic mappings from status_code to python exceptions # more generic mappings from status_code to python exceptions
HTTP_EXCEPTIONS = { HTTP_EXCEPTIONS = {
400: RequestError, 400: RequestError,
401: AuthenticationException,
403: AuthorizationException,
404: NotFoundError, 404: NotFoundError,
409: ConflictError, 409: ConflictError,
} }