summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoman Podolyaka <rpodolyaka@mirantis.com>2013-03-19 10:45:54 +0200
committerRoman Podolyaka <rpodolyaka@mirantis.com>2013-07-23 16:15:19 +0300
commit499ed84bd70ce12658b546be360c82cd758a3caa (patch)
treec755d6623c00a1a147ee00b246405442b83c45e8
parent31e9374525fde51701e1d9d311bd12c4380b93e6 (diff)
downloadpython-neutronclient-499ed84bd70ce12658b546be360c82cd758a3caa.tar.gz
Handle host side SSL certificates validation
- add --os-cacert option which allows to set a file containing certificates of root CAs (certificate authorities) that are required for validation of HTTPS servers SSL certificates - wrap httplib2 SSL certificates validation errors with a custom quantumclient exception Blueprint: quantum-client-ssl Change-Id: I4e6a7d177ba14314ba9bed613ec2684bffc35222
-rw-r--r--neutronclient/client.py7
-rw-r--r--neutronclient/common/clientmanager.py7
-rw-r--r--neutronclient/common/exceptions.py4
-rw-r--r--neutronclient/neutron/client.py3
-rw-r--r--neutronclient/shell.py11
-rw-r--r--neutronclient/v2_0/client.py3
-rw-r--r--tests/unit/test_ssl.py137
7 files changed, 165 insertions, 7 deletions
diff --git a/neutronclient/client.py b/neutronclient/client.py
index fefa07b..daea317 100644
--- a/neutronclient/client.py
+++ b/neutronclient/client.py
@@ -99,8 +99,9 @@ class HTTPClient(httplib2.Http):
token=None, region_name=None, timeout=None,
endpoint_url=None, insecure=False,
endpoint_type='publicURL',
- auth_strategy='keystone', **kwargs):
- super(HTTPClient, self).__init__(timeout=timeout)
+ auth_strategy='keystone', ca_cert=None, **kwargs):
+ super(HTTPClient, self).__init__(timeout=timeout, ca_certs=ca_cert)
+
self.username = username
self.tenant_name = tenant_name
self.tenant_id = tenant_id
@@ -134,6 +135,8 @@ class HTTPClient(httplib2.Http):
utils.http_log_req(_logger, args, kargs)
try:
resp, body = self.request(*args, **kargs)
+ except httplib2.SSLHandshakeError as e:
+ raise exceptions.SslCertificateValidationError(reason=e)
except Exception as e:
# Wrap the low-level connection error (socket timeout, redirect
# limit, decompression error, etc) into our custom high-level
diff --git a/neutronclient/common/clientmanager.py b/neutronclient/common/clientmanager.py
index f9e886e..ca89627 100644
--- a/neutronclient/common/clientmanager.py
+++ b/neutronclient/common/clientmanager.py
@@ -55,7 +55,8 @@ class ClientManager(object):
region_name=None,
api_version=None,
auth_strategy=None,
- insecure=False
+ insecure=False,
+ ca_cert=None,
):
self._token = token
self._url = url
@@ -70,6 +71,7 @@ class ClientManager(object):
self._service_catalog = None
self._auth_strategy = auth_strategy
self._insecure = insecure
+ self._ca_cert = ca_cert
return
def initialize(self):
@@ -81,7 +83,8 @@ class ClientManager(object):
region_name=self._region_name,
auth_url=self._auth_url,
endpoint_type=self._endpoint_type,
- insecure=self._insecure)
+ insecure=self._insecure,
+ ca_cert=self._ca_cert)
httpclient.authenticate()
# Populate other password flow attributes
self._token = httpclient.auth_token
diff --git a/neutronclient/common/exceptions.py b/neutronclient/common/exceptions.py
index c62d253..c2b8b3c 100644
--- a/neutronclient/common/exceptions.py
+++ b/neutronclient/common/exceptions.py
@@ -172,3 +172,7 @@ class CommandError(Exception):
class NeutronClientNoUniqueMatch(NeutronClientException):
message = _("Multiple %(resource)s matches found for name '%(name)s',"
" use an ID to be more specific.")
+
+
+class SslCertificateValidationError(NeutronClientException):
+ message = _("SSL certificate validation has failed: %(reason)s")
diff --git a/neutronclient/neutron/client.py b/neutronclient/neutron/client.py
index daf0481..2c87517 100644
--- a/neutronclient/neutron/client.py
+++ b/neutronclient/neutron/client.py
@@ -45,7 +45,8 @@ def make_client(instance):
endpoint_url=url,
token=instance._token,
auth_strategy=instance._auth_strategy,
- insecure=instance._insecure)
+ insecure=instance._insecure,
+ ca_cert=instance._ca_cert)
return client
else:
raise exceptions.UnsupportedVersion("API version %s is not supported" %
diff --git a/neutronclient/shell.py b/neutronclient/shell.py
index 89c7adf..22cefbf 100644
--- a/neutronclient/shell.py
+++ b/neutronclient/shell.py
@@ -346,6 +346,14 @@ class NeutronShell(app.App):
help=argparse.SUPPRESS)
parser.add_argument(
+ '--os-cacert',
+ metavar='<ca-certificate>',
+ default=env('OS_CACERT', default=None),
+ help="Specify a CA bundle file to use in "
+ "verifying a TLS (https) server certificate. "
+ "Defaults to env[OS_CACERT]")
+
+ parser.add_argument(
'--insecure',
action='store_true',
default=env('NEUTRONCLIENT_INSECURE', default=False),
@@ -516,7 +524,8 @@ class NeutronShell(app.App):
api_version=self.api_version,
auth_strategy=self.options.os_auth_strategy,
endpoint_type=self.options.endpoint_type,
- insecure=self.options.insecure, )
+ insecure=self.options.insecure,
+ ca_cert=self.options.os_cacert, )
return
def initialize_app(self, argv):
diff --git a/neutronclient/v2_0/client.py b/neutronclient/v2_0/client.py
index e2e1c79..0f2d399 100644
--- a/neutronclient/v2_0/client.py
+++ b/neutronclient/v2_0/client.py
@@ -131,7 +131,8 @@ class Client(object):
instantiation.(optional)
:param integer timeout: Allows customization of the timeout for client
http requests. (optional)
- :param insecure: ssl certificate validation. (optional)
+ :param bool insecure: SSL certificate validation. (optional)
+ :param string ca_cert: SSL CA bundle file to use. (optional)
Example::
diff --git a/tests/unit/test_ssl.py b/tests/unit/test_ssl.py
new file mode 100644
index 0000000..0370ae6
--- /dev/null
+++ b/tests/unit/test_ssl.py
@@ -0,0 +1,137 @@
+# Copyright (C) 2013 OpenStack Foundation.
+# 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.
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+import fixtures
+import httplib2
+import mox
+import testtools
+
+from neutronclient.client import HTTPClient
+from neutronclient.common.clientmanager import ClientManager
+from neutronclient.common import exceptions
+from neutronclient import shell as openstack_shell
+
+
+AUTH_TOKEN = 'test_token'
+END_URL = 'test_url'
+METHOD = 'GET'
+URL = 'http://test.test:1234/v2.0/'
+CA_CERT = '/tmp/test/path'
+
+
+class TestSSL(testtools.TestCase):
+ def setUp(self):
+ super(TestSSL, self).setUp()
+
+ self.useFixture(fixtures.EnvironmentVariable('OS_TOKEN', AUTH_TOKEN))
+ self.useFixture(fixtures.EnvironmentVariable('OS_URL', END_URL))
+
+ self.mox = mox.Mox()
+ self.addCleanup(self.mox.UnsetStubs)
+
+ def test_ca_cert_passed(self):
+ self.mox.StubOutWithMock(ClientManager, '__init__')
+ self.mox.StubOutWithMock(openstack_shell.NeutronShell, 'interact')
+
+ ClientManager.__init__(
+ ca_cert=CA_CERT,
+ # we are not really interested in other args
+ api_version=mox.IgnoreArg(),
+ auth_strategy=mox.IgnoreArg(),
+ auth_url=mox.IgnoreArg(),
+ endpoint_type=mox.IgnoreArg(),
+ insecure=mox.IgnoreArg(),
+ password=mox.IgnoreArg(),
+ region_name=mox.IgnoreArg(),
+ tenant_id=mox.IgnoreArg(),
+ tenant_name=mox.IgnoreArg(),
+ token=mox.IgnoreArg(),
+ url=mox.IgnoreArg(),
+ username=mox.IgnoreArg()
+ )
+ openstack_shell.NeutronShell.interact().AndReturn(0)
+ self.mox.ReplayAll()
+
+ openstack_shell.NeutronShell('2.0').run(['--os-cacert', CA_CERT])
+ self.mox.VerifyAll()
+
+ def test_ca_cert_passed_as_env_var(self):
+ self.useFixture(fixtures.EnvironmentVariable('OS_CACERT', CA_CERT))
+
+ self.mox.StubOutWithMock(ClientManager, '__init__')
+ self.mox.StubOutWithMock(openstack_shell.NeutronShell, 'interact')
+
+ ClientManager.__init__(
+ ca_cert=CA_CERT,
+ # we are not really interested in other args
+ api_version=mox.IgnoreArg(),
+ auth_strategy=mox.IgnoreArg(),
+ auth_url=mox.IgnoreArg(),
+ endpoint_type=mox.IgnoreArg(),
+ insecure=mox.IgnoreArg(),
+ password=mox.IgnoreArg(),
+ region_name=mox.IgnoreArg(),
+ tenant_id=mox.IgnoreArg(),
+ tenant_name=mox.IgnoreArg(),
+ token=mox.IgnoreArg(),
+ url=mox.IgnoreArg(),
+ username=mox.IgnoreArg()
+ )
+ openstack_shell.NeutronShell.interact().AndReturn(0)
+ self.mox.ReplayAll()
+
+ openstack_shell.NeutronShell('2.0').run([])
+ self.mox.VerifyAll()
+
+ def test_client_manager_properly_creates_httpclient_instance(self):
+ self.mox.StubOutWithMock(HTTPClient, '__init__')
+ HTTPClient.__init__(
+ ca_cert=CA_CERT,
+ # we are not really interested in other args
+ auth_strategy=mox.IgnoreArg(),
+ auth_url=mox.IgnoreArg(),
+ endpoint_url=mox.IgnoreArg(),
+ insecure=mox.IgnoreArg(),
+ password=mox.IgnoreArg(),
+ region_name=mox.IgnoreArg(),
+ tenant_name=mox.IgnoreArg(),
+ token=mox.IgnoreArg(),
+ username=mox.IgnoreArg(),
+ )
+ self.mox.ReplayAll()
+
+ version = {'network': '2.0'}
+ ClientManager(ca_cert=CA_CERT,
+ api_version=version,
+ url=END_URL,
+ token=AUTH_TOKEN).neutron
+ self.mox.VerifyAll()
+
+ def test_proper_exception_is_raised_when_cert_validation_fails(self):
+ http = HTTPClient(token=AUTH_TOKEN, endpoint_url=END_URL)
+
+ self.mox.StubOutWithMock(httplib2.Http, 'request')
+ httplib2.Http.request(
+ URL, METHOD, headers=mox.IgnoreArg()
+ ).AndRaise(httplib2.SSLHandshakeError)
+ self.mox.ReplayAll()
+
+ self.assertRaises(
+ exceptions.SslCertificateValidationError,
+ http._cs_request,
+ URL, METHOD
+ )
+ self.mox.VerifyAll()