Proper SSL implementation, including optional certificate verification
This commit is contained in:
+3
-3
@@ -36,8 +36,8 @@ Connection Selector
|
|||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
|
||||||
Connection
|
Urllib3HttpConnection (default connection_class)
|
||||||
----------
|
------------------------------------------------
|
||||||
|
|
||||||
.. autoclass:: Connection
|
.. autoclass:: Urllib3HttpConnection
|
||||||
:members:
|
:members:
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user