154 lines
5.8 KiB
Python
154 lines
5.8 KiB
Python
# Copyright (c) 2016 IBM Corporation
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
#
|
|
import datetime
|
|
import hashlib
|
|
import re
|
|
import ssl
|
|
|
|
from oslo_log import log as logging
|
|
from requests.packages.urllib3 import connection
|
|
from requests.packages.urllib3 import connectionpool
|
|
from requests.packages.urllib3 import poolmanager
|
|
|
|
from cinder.i18n import _
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
try:
|
|
from OpenSSL.crypto import FILETYPE_ASN1
|
|
from OpenSSL.crypto import load_certificate
|
|
except ImportError:
|
|
load_certificate = None
|
|
FILETYPE_ASN1 = None
|
|
|
|
_PEM_RE = re.compile(u"""-----BEGIN CERTIFICATE-----\r?
|
|
.+?\r?-----END CERTIFICATE-----\r?\n?""", re.DOTALL)
|
|
|
|
|
|
class DS8KHTTPSConnection(connection.VerifiedHTTPSConnection):
|
|
"""Extend the HTTPS Connection to do our own Certificate Verification."""
|
|
|
|
def _verify_cert(self, sock, ca_certs):
|
|
# If they asked us to not verify the Certificate then nothing to do
|
|
if not ca_certs:
|
|
return
|
|
|
|
# Retrieve the Existing Certificates from the File in Binary Form
|
|
peercert = sock.getpeercert(True)
|
|
try:
|
|
with open(ca_certs, 'r') as f:
|
|
certs_str = f.read()
|
|
except Exception:
|
|
raise ssl.SSLError(_("Failed to read certificate from %s")
|
|
% ca_certs)
|
|
|
|
# Verify the Existing Certificates
|
|
found = False
|
|
certs = [match.group(0) for match in _PEM_RE.finditer(certs_str)]
|
|
for cert in certs:
|
|
existcert = ssl.PEM_cert_to_DER_cert(cert)
|
|
# First check to make sure the 2 certificates are the same ones
|
|
if (hashlib.sha256(existcert).digest() ==
|
|
hashlib.sha256(peercert).digest()):
|
|
found = True
|
|
break
|
|
if not found:
|
|
raise ssl.SSLError(
|
|
_("The certificate doesn't match the trusted one "
|
|
"in %s.") % ca_certs)
|
|
|
|
if load_certificate is None and FILETYPE_ASN1 is None:
|
|
raise ssl.SSLError(
|
|
_("Missing 'pyOpenSSL' python module, ensure the "
|
|
"library is installed."))
|
|
|
|
# Throw an exception if the certificate given to us has expired
|
|
x509 = load_certificate(FILETYPE_ASN1, peercert)
|
|
if x509.has_expired():
|
|
raise ssl.SSLError(
|
|
_("The certificate expired: %s") % x509.get_notAfter())
|
|
|
|
def connect(self):
|
|
"""Override the Connect Method to fix the Certificate Verification."""
|
|
# Add certificate verification
|
|
conn = self._new_conn()
|
|
|
|
if getattr(self, '_tunnel_host', None):
|
|
# _tunnel_host was added in Python 2.6.3
|
|
# (See: http://hg.python.org/cpython/rev/0f57b30a152f)
|
|
|
|
self.sock = conn
|
|
# Calls self._set_hostport(), so self.host is
|
|
# self._tunnel_host below.
|
|
#
|
|
# disable pylint because pylint doesn't support importing
|
|
# from six.moves yet. see:
|
|
# https://bitbucket.org/logilab/pylint/issue/550/
|
|
self._tunnel() # pylint: disable=E1101
|
|
# Mark this connection as not reusable
|
|
self.auto_open = 0
|
|
|
|
# The RECENT_DATE is originally taken from requests. The date is just
|
|
# an arbitrary value that is used as a sanity test to identify hosts
|
|
# that are using the default time after bootup (e.g. 1970), and
|
|
# provides information for debugging
|
|
RECENT_DATE = datetime.date(2014, 1, 1)
|
|
is_time_off = datetime.date.today() < RECENT_DATE
|
|
if is_time_off:
|
|
LOG.warning('System time is way off (before %s). This will '
|
|
'probably lead to SSL verification errors.',
|
|
RECENT_DATE)
|
|
|
|
# Wrap socket using verification with the root certs in
|
|
# trusted_root_certs
|
|
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
|
|
self.sock = context.wrap_socket(conn)
|
|
|
|
self._verify_cert(self.sock, self.ca_certs)
|
|
self.is_verified = True
|
|
|
|
def putrequest(self, method, url, **kwargs):
|
|
"""Override the Put Request method take the DS8K off of the URL."""
|
|
if url and url.startswith('httpsds8k://'):
|
|
url = 'https://' + url[12:]
|
|
return super(DS8KHTTPSConnection,
|
|
self).putrequest(method, url, **kwargs)
|
|
|
|
def request(self, method, url, **kwargs):
|
|
"""Override the Request method take the DS8K off of the URL."""
|
|
if url and url.startswith('httpsds8k://'):
|
|
url = 'https://' + url[12:]
|
|
return super(DS8KHTTPSConnection, self).request(method, url, **kwargs)
|
|
|
|
|
|
class DS8KConnectionPool(connectionpool.HTTPSConnectionPool):
|
|
"""Extend the HTTPS Connection Pool to our own Certificate verification."""
|
|
|
|
scheme = 'httpsds8k'
|
|
ConnectionCls = DS8KHTTPSConnection
|
|
|
|
def urlopen(self, method, url, **kwargs):
|
|
"""Override URL Open method to take DS8K out of the URL protocol."""
|
|
if url and url.startswith('httpsds8k://'):
|
|
url = 'https://' + url[12:]
|
|
return super(DS8KConnectionPool, self).urlopen(method, url, **kwargs)
|
|
|
|
|
|
if hasattr(poolmanager, 'key_fn_by_scheme'):
|
|
poolmanager.key_fn_by_scheme["httpsds8k"] = (
|
|
poolmanager.key_fn_by_scheme["https"])
|
|
poolmanager.pool_classes_by_scheme["httpsds8k"] = DS8KConnectionPool
|