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
This commit is contained in:
parent
31e9374525
commit
499ed84bd7
neutronclient
tests/unit
@ -99,8 +99,9 @@ class HTTPClient(httplib2.Http):
|
|||||||
token=None, region_name=None, timeout=None,
|
token=None, region_name=None, timeout=None,
|
||||||
endpoint_url=None, insecure=False,
|
endpoint_url=None, insecure=False,
|
||||||
endpoint_type='publicURL',
|
endpoint_type='publicURL',
|
||||||
auth_strategy='keystone', **kwargs):
|
auth_strategy='keystone', ca_cert=None, **kwargs):
|
||||||
super(HTTPClient, self).__init__(timeout=timeout)
|
super(HTTPClient, self).__init__(timeout=timeout, ca_certs=ca_cert)
|
||||||
|
|
||||||
self.username = username
|
self.username = username
|
||||||
self.tenant_name = tenant_name
|
self.tenant_name = tenant_name
|
||||||
self.tenant_id = tenant_id
|
self.tenant_id = tenant_id
|
||||||
@ -134,6 +135,8 @@ class HTTPClient(httplib2.Http):
|
|||||||
utils.http_log_req(_logger, args, kargs)
|
utils.http_log_req(_logger, args, kargs)
|
||||||
try:
|
try:
|
||||||
resp, body = self.request(*args, **kargs)
|
resp, body = self.request(*args, **kargs)
|
||||||
|
except httplib2.SSLHandshakeError as e:
|
||||||
|
raise exceptions.SslCertificateValidationError(reason=e)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Wrap the low-level connection error (socket timeout, redirect
|
# Wrap the low-level connection error (socket timeout, redirect
|
||||||
# limit, decompression error, etc) into our custom high-level
|
# limit, decompression error, etc) into our custom high-level
|
||||||
|
@ -55,7 +55,8 @@ class ClientManager(object):
|
|||||||
region_name=None,
|
region_name=None,
|
||||||
api_version=None,
|
api_version=None,
|
||||||
auth_strategy=None,
|
auth_strategy=None,
|
||||||
insecure=False
|
insecure=False,
|
||||||
|
ca_cert=None,
|
||||||
):
|
):
|
||||||
self._token = token
|
self._token = token
|
||||||
self._url = url
|
self._url = url
|
||||||
@ -70,6 +71,7 @@ class ClientManager(object):
|
|||||||
self._service_catalog = None
|
self._service_catalog = None
|
||||||
self._auth_strategy = auth_strategy
|
self._auth_strategy = auth_strategy
|
||||||
self._insecure = insecure
|
self._insecure = insecure
|
||||||
|
self._ca_cert = ca_cert
|
||||||
return
|
return
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
@ -81,7 +83,8 @@ class ClientManager(object):
|
|||||||
region_name=self._region_name,
|
region_name=self._region_name,
|
||||||
auth_url=self._auth_url,
|
auth_url=self._auth_url,
|
||||||
endpoint_type=self._endpoint_type,
|
endpoint_type=self._endpoint_type,
|
||||||
insecure=self._insecure)
|
insecure=self._insecure,
|
||||||
|
ca_cert=self._ca_cert)
|
||||||
httpclient.authenticate()
|
httpclient.authenticate()
|
||||||
# Populate other password flow attributes
|
# Populate other password flow attributes
|
||||||
self._token = httpclient.auth_token
|
self._token = httpclient.auth_token
|
||||||
|
@ -172,3 +172,7 @@ class CommandError(Exception):
|
|||||||
class NeutronClientNoUniqueMatch(NeutronClientException):
|
class NeutronClientNoUniqueMatch(NeutronClientException):
|
||||||
message = _("Multiple %(resource)s matches found for name '%(name)s',"
|
message = _("Multiple %(resource)s matches found for name '%(name)s',"
|
||||||
" use an ID to be more specific.")
|
" use an ID to be more specific.")
|
||||||
|
|
||||||
|
|
||||||
|
class SslCertificateValidationError(NeutronClientException):
|
||||||
|
message = _("SSL certificate validation has failed: %(reason)s")
|
||||||
|
@ -45,7 +45,8 @@ def make_client(instance):
|
|||||||
endpoint_url=url,
|
endpoint_url=url,
|
||||||
token=instance._token,
|
token=instance._token,
|
||||||
auth_strategy=instance._auth_strategy,
|
auth_strategy=instance._auth_strategy,
|
||||||
insecure=instance._insecure)
|
insecure=instance._insecure,
|
||||||
|
ca_cert=instance._ca_cert)
|
||||||
return client
|
return client
|
||||||
else:
|
else:
|
||||||
raise exceptions.UnsupportedVersion("API version %s is not supported" %
|
raise exceptions.UnsupportedVersion("API version %s is not supported" %
|
||||||
|
@ -345,6 +345,14 @@ class NeutronShell(app.App):
|
|||||||
'--os_url',
|
'--os_url',
|
||||||
help=argparse.SUPPRESS)
|
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(
|
parser.add_argument(
|
||||||
'--insecure',
|
'--insecure',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
@ -516,7 +524,8 @@ class NeutronShell(app.App):
|
|||||||
api_version=self.api_version,
|
api_version=self.api_version,
|
||||||
auth_strategy=self.options.os_auth_strategy,
|
auth_strategy=self.options.os_auth_strategy,
|
||||||
endpoint_type=self.options.endpoint_type,
|
endpoint_type=self.options.endpoint_type,
|
||||||
insecure=self.options.insecure, )
|
insecure=self.options.insecure,
|
||||||
|
ca_cert=self.options.os_cacert, )
|
||||||
return
|
return
|
||||||
|
|
||||||
def initialize_app(self, argv):
|
def initialize_app(self, argv):
|
||||||
|
@ -131,7 +131,8 @@ class Client(object):
|
|||||||
instantiation.(optional)
|
instantiation.(optional)
|
||||||
:param integer timeout: Allows customization of the timeout for client
|
:param integer timeout: Allows customization of the timeout for client
|
||||||
http requests. (optional)
|
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::
|
Example::
|
||||||
|
|
||||||
|
137
tests/unit/test_ssl.py
Normal file
137
tests/unit/test_ssl.py
Normal file
@ -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()
|
Loading…
x
Reference in New Issue
Block a user