Proper SSL implementation, including optional certificate verification
This commit is contained in:
+3
-3
@@ -36,8 +36,8 @@ Connection Selector
|
||||
:members:
|
||||
|
||||
|
||||
Connection
|
||||
----------
|
||||
Urllib3HttpConnection (default connection_class)
|
||||
------------------------------------------------
|
||||
|
||||
.. autoclass:: Connection
|
||||
.. autoclass:: Urllib3HttpConnection
|
||||
:members:
|
||||
|
||||
@@ -6,7 +6,7 @@ except ImportError:
|
||||
REQUESTS_AVAILABLE = False
|
||||
|
||||
from .base import Connection
|
||||
from ..exceptions import ConnectionError, ImproperlyConfigured, ConnectionTimeout
|
||||
from ..exceptions import ConnectionError, ImproperlyConfigured, ConnectionTimeout, SSLError
|
||||
from ..compat import urlencode
|
||||
|
||||
class RequestsHttpConnection(Connection):
|
||||
@@ -16,8 +16,12 @@ class RequestsHttpConnection(Connection):
|
||||
:arg http_auth: optional http auth information as either ':' separated
|
||||
string or a tuple
|
||||
: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:
|
||||
raise ImproperlyConfigured("Please install requests to use RequestsHttpConnection.")
|
||||
|
||||
@@ -32,7 +36,11 @@ class RequestsHttpConnection(Connection):
|
||||
's' if use_ssl else '',
|
||||
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=()):
|
||||
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)
|
||||
duration = time.time() - start
|
||||
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:
|
||||
self.log_request_fail(method, url, body, time.time() - start, exception=e)
|
||||
raise ConnectionTimeout('TIMEOUT', str(e), e)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import time
|
||||
import urllib3
|
||||
from urllib3.exceptions import ReadTimeoutError
|
||||
from urllib3.exceptions import ReadTimeoutError, SSLError as UrllibSSLError
|
||||
|
||||
from .base import Connection
|
||||
from ..exceptions import ConnectionError, ConnectionTimeout
|
||||
from ..exceptions import ConnectionError, ImproperlyConfigured, ConnectionTimeout, SSLError
|
||||
from ..compat import urlencode
|
||||
|
||||
class Urllib3HttpConnection(Connection):
|
||||
@@ -13,10 +13,17 @@ class Urllib3HttpConnection(Connection):
|
||||
:arg http_auth: optional http auth information as either ':' separated
|
||||
string or a tuple
|
||||
: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
|
||||
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)
|
||||
self.headers = {}
|
||||
if http_auth is not None:
|
||||
@@ -25,10 +32,17 @@ class Urllib3HttpConnection(Connection):
|
||||
self.headers = urllib3.make_headers(basic_auth=http_auth)
|
||||
|
||||
pool_class = urllib3.HTTPConnectionPool
|
||||
kw = {}
|
||||
if use_ssl:
|
||||
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=()):
|
||||
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)
|
||||
duration = time.time() - start
|
||||
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:
|
||||
self.log_request_fail(method, full_url, body, time.time() - start, exception=e)
|
||||
raise ConnectionTimeout('TIMEOUT', str(e), e)
|
||||
|
||||
@@ -8,6 +8,7 @@ class ImproperlyConfigured(Exception):
|
||||
Exception raised when the config passed to the client is inconsistent or invalid.
|
||||
"""
|
||||
|
||||
|
||||
class ElasticsearchException(Exception):
|
||||
"""
|
||||
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)
|
||||
|
||||
|
||||
class SSLError(ConnectionError):
|
||||
""" Error raised when encountering SSL errors. """
|
||||
|
||||
|
||||
class ConnectionTimeout(ConnectionError):
|
||||
""" A network timeout. """
|
||||
def __str__(self):
|
||||
@@ -79,9 +84,19 @@ class ConflictError(TransportError):
|
||||
class RequestError(TransportError):
|
||||
""" 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
|
||||
HTTP_EXCEPTIONS = {
|
||||
400: RequestError,
|
||||
401: AuthenticationException,
|
||||
403: AuthorizationException,
|
||||
404: NotFoundError,
|
||||
409: ConflictError,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user