summaryrefslogtreecommitdiff
path: root/cinder/volume/drivers/ibm/ibm_storage/ds8k_connection.py
blob: 36f283b40fe3693326425a258a11931519e650e4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#  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