Merge "Support client certificate/key"

This commit is contained in:
Jenkins
2016-05-19 22:20:17 +00:00
committed by Gerrit Code Review
6 changed files with 98 additions and 6 deletions

View File

@@ -339,7 +339,8 @@ class _RetryBody(_ObjectBody):
class HTTPConnection(object): class HTTPConnection(object):
def __init__(self, url, proxy=None, cacert=None, insecure=False, def __init__(self, url, proxy=None, cacert=None, insecure=False,
ssl_compression=False, default_user_agent=None, timeout=None): cert=None, cert_key=None, ssl_compression=False,
default_user_agent=None, timeout=None):
""" """
Make an HTTPConnection or HTTPSConnection Make an HTTPConnection or HTTPSConnection
@@ -350,6 +351,9 @@ class HTTPConnection(object):
certificate. certificate.
:param insecure: Allow to access servers without checking SSL certs. :param insecure: Allow to access servers without checking SSL certs.
The server's certificate will not be verified. The server's certificate will not be verified.
:param cert: Client certificate file to connect on SSL server
requiring SSL client certificate.
:param cert_key: Client certificate private key file.
:param ssl_compression: SSL compression should be disabled by default :param ssl_compression: SSL compression should be disabled by default
and this setting is not usable as of now. The and this setting is not usable as of now. The
parameter is kept for backward compatibility. parameter is kept for backward compatibility.
@@ -379,6 +383,14 @@ class HTTPConnection(object):
# verify requests parameter is used to pass the CA_BUNDLE file # verify requests parameter is used to pass the CA_BUNDLE file
# see: http://docs.python-requests.org/en/latest/user/advanced/ # see: http://docs.python-requests.org/en/latest/user/advanced/
self.requests_args['verify'] = cacert self.requests_args['verify'] = cacert
if cert:
# NOTE(cbrandily): cert requests parameter is used to pass client
# cert path or a tuple with client certificate/key paths.
if cert_key:
self.requests_args['cert'] = cert, cert_key
else:
self.requests_args['cert'] = cert
if proxy: if proxy:
proxy_parsed = urlparse(proxy) proxy_parsed = urlparse(proxy)
if not proxy_parsed.scheme: if not proxy_parsed.scheme:
@@ -465,8 +477,11 @@ def http_connection(*arg, **kwarg):
def get_auth_1_0(url, user, key, snet, **kwargs): def get_auth_1_0(url, user, key, snet, **kwargs):
cacert = kwargs.get('cacert', None) cacert = kwargs.get('cacert', None)
insecure = kwargs.get('insecure', False) insecure = kwargs.get('insecure', False)
cert = kwargs.get('cert')
cert_key = kwargs.get('cert_key')
timeout = kwargs.get('timeout', None) timeout = kwargs.get('timeout', None)
parsed, conn = http_connection(url, cacert=cacert, insecure=insecure, parsed, conn = http_connection(url, cacert=cacert, insecure=insecure,
cert=cert, cert_key=cert_key,
timeout=timeout) timeout=timeout)
method = 'GET' method = 'GET'
headers = {'X-Auth-User': user, 'X-Auth-Key': key} headers = {'X-Auth-User': user, 'X-Auth-Key': key}
@@ -547,6 +562,8 @@ def get_auth_keystone(auth_url, user, key, os_options, **kwargs):
project_domain_id=os_options.get('project_domain_id'), project_domain_id=os_options.get('project_domain_id'),
debug=debug, debug=debug,
cacert=kwargs.get('cacert'), cacert=kwargs.get('cacert'),
cert=kwargs.get('cert'),
key=kwargs.get('cert_key'),
auth_url=auth_url, insecure=insecure, timeout=timeout) auth_url=auth_url, insecure=insecure, timeout=timeout)
except exceptions.Unauthorized: except exceptions.Unauthorized:
msg = 'Unauthorized. Check username, password and tenant name/id.' msg = 'Unauthorized. Check username, password and tenant name/id.'
@@ -597,6 +614,8 @@ def get_auth(auth_url, user, key, **kwargs):
cacert = kwargs.get('cacert', None) cacert = kwargs.get('cacert', None)
insecure = kwargs.get('insecure', False) insecure = kwargs.get('insecure', False)
cert = kwargs.get('cert')
cert_key = kwargs.get('cert_key')
timeout = kwargs.get('timeout', None) timeout = kwargs.get('timeout', None)
if auth_version in AUTH_VERSIONS_V1: if auth_version in AUTH_VERSIONS_V1:
storage_url, token = get_auth_1_0(auth_url, storage_url, token = get_auth_1_0(auth_url,
@@ -605,6 +624,8 @@ def get_auth(auth_url, user, key, **kwargs):
kwargs.get('snet'), kwargs.get('snet'),
cacert=cacert, cacert=cacert,
insecure=insecure, insecure=insecure,
cert=cert,
cert_key=cert_key,
timeout=timeout) timeout=timeout)
elif auth_version in AUTH_VERSIONS_V2 + AUTH_VERSIONS_V3: elif auth_version in AUTH_VERSIONS_V2 + AUTH_VERSIONS_V3:
# We are handling a special use case here where the user argument # We are handling a special use case here where the user argument
@@ -628,6 +649,8 @@ def get_auth(auth_url, user, key, **kwargs):
key, os_options, key, os_options,
cacert=cacert, cacert=cacert,
insecure=insecure, insecure=insecure,
cert=cert,
cert_key=cert_key,
timeout=timeout, timeout=timeout,
auth_version=auth_version) auth_version=auth_version)
else: else:
@@ -1389,8 +1412,9 @@ class Connection(object):
preauthurl=None, preauthtoken=None, snet=False, preauthurl=None, preauthtoken=None, snet=False,
starting_backoff=1, max_backoff=64, tenant_name=None, starting_backoff=1, max_backoff=64, tenant_name=None,
os_options=None, auth_version="1", cacert=None, os_options=None, auth_version="1", cacert=None,
insecure=False, ssl_compression=True, insecure=False, cert=None, cert_key=None,
retry_on_ratelimit=False, timeout=None): ssl_compression=True, retry_on_ratelimit=False,
timeout=None):
""" """
:param authurl: authentication URL :param authurl: authentication URL
:param user: user name to authenticate as :param user: user name to authenticate as
@@ -1412,6 +1436,9 @@ class Connection(object):
service_username, service_project_name, service_key service_username, service_project_name, service_key
:param insecure: Allow to access servers without checking SSL certs. :param insecure: Allow to access servers without checking SSL certs.
The server's certificate will not be verified. The server's certificate will not be verified.
:param cert: Client certificate file to connect on SSL server
requiring SSL client certificate.
:param cert_key: Client certificate private key file.
:param ssl_compression: Whether to enable compression at the SSL layer. :param ssl_compression: Whether to enable compression at the SSL layer.
If set to 'False' and the pyOpenSSL library is If set to 'False' and the pyOpenSSL library is
present an attempt to disable SSL compression present an attempt to disable SSL compression
@@ -1447,6 +1474,8 @@ class Connection(object):
self.service_token = None self.service_token = None
self.cacert = cacert self.cacert = cacert
self.insecure = insecure self.insecure = insecure
self.cert = cert
self.cert_key = cert_key
self.ssl_compression = ssl_compression self.ssl_compression = ssl_compression
self.auth_end_time = 0 self.auth_end_time = 0
self.retry_on_ratelimit = retry_on_ratelimit self.retry_on_ratelimit = retry_on_ratelimit
@@ -1469,6 +1498,8 @@ class Connection(object):
os_options=self.os_options, os_options=self.os_options,
cacert=self.cacert, cacert=self.cacert,
insecure=self.insecure, insecure=self.insecure,
cert=self.cert,
cert_key=self.cert_key,
timeout=self.timeout) timeout=self.timeout)
return self.url, self.token return self.url, self.token
@@ -1494,6 +1525,8 @@ class Connection(object):
return http_connection(url if url else self.url, return http_connection(url if url else self.url,
cacert=self.cacert, cacert=self.cacert,
insecure=self.insecure, insecure=self.insecure,
cert=self.cert,
cert_key=self.cert_key,
ssl_compression=self.ssl_compression, ssl_compression=self.ssl_compression,
timeout=self.timeout) timeout=self.timeout)

View File

@@ -161,6 +161,8 @@ def _build_default_global_options():
"os_service_type": environ.get('OS_SERVICE_TYPE'), "os_service_type": environ.get('OS_SERVICE_TYPE'),
"os_endpoint_type": environ.get('OS_ENDPOINT_TYPE'), "os_endpoint_type": environ.get('OS_ENDPOINT_TYPE'),
"os_cacert": environ.get('OS_CACERT'), "os_cacert": environ.get('OS_CACERT'),
"os_cert": environ.get('OS_CERT'),
"os_key": environ.get('OS_KEY'),
"insecure": config_true_value(environ.get('SWIFTCLIENT_INSECURE')), "insecure": config_true_value(environ.get('SWIFTCLIENT_INSECURE')),
"ssl_compression": False, "ssl_compression": False,
'segment_threads': 10, 'segment_threads': 10,
@@ -253,6 +255,8 @@ def get_conn(options):
snet=options['snet'], snet=options['snet'],
cacert=options['os_cacert'], cacert=options['os_cacert'],
insecure=options['insecure'], insecure=options['insecure'],
cert=options['os_cert'],
cert_key=options['os_key'],
ssl_compression=options['ssl_compression']) ssl_compression=options['ssl_compression'])

View File

@@ -1253,6 +1253,8 @@ def main(arguments=None):
[--os-service-type <service-type>] [--os-service-type <service-type>]
[--os-endpoint-type <endpoint-type>] [--os-endpoint-type <endpoint-type>]
[--os-cacert <ca-certificate>] [--insecure] [--os-cacert <ca-certificate>] [--insecure]
[--os-cert <client-certificate-file>]
[--os-key <client-certificate-key-file>]
[--no-ssl-compression] [--no-ssl-compression]
<subcommand> [--help] [<subcommand options>] <subcommand> [--help] [<subcommand options>]
@@ -1494,6 +1496,16 @@ Examples:
help='Specify a CA bundle file to use in verifying a ' help='Specify a CA bundle file to use in verifying a '
'TLS (https) server certificate. ' 'TLS (https) server certificate. '
'Defaults to env[OS_CACERT].') 'Defaults to env[OS_CACERT].')
os_grp.add_argument('--os-cert',
metavar='<client-certificate-file>',
default=environ.get('OS_CERT'),
help='Specify a client certificate file (for client '
'auth). Defaults to env[OS_CERT].')
os_grp.add_argument('--os-key',
metavar='<client-certificate-key-file>',
default=environ.get('OS_KEY'),
help='Specify a client certificate key file (for '
'client auth). Defaults to env[OS_KEY].')
options, args = parse_args(parser, argv[1:], enforce_requires=False) options, args = parse_args(parser, argv[1:], enforce_requires=False)
if options['help'] or options['os_help']: if options['help'] or options['os_help']:

View File

@@ -1855,7 +1855,9 @@ class TestKeystoneOptions(MockHttpTest):
'project-id': 'projectid', 'project-id': 'projectid',
'project-domain-id': 'projectdomainid', 'project-domain-id': 'projectdomainid',
'project-domain-name': 'projectdomain', 'project-domain-name': 'projectdomain',
'cacert': 'foo'} 'cacert': 'foo',
'cert': 'minnie',
'key': 'mickey'}
catalog_opts = {'service-type': 'my-object-store', catalog_opts = {'service-type': 'my-object-store',
'endpoint-type': 'public', 'endpoint-type': 'public',
'region-name': 'my-region'} 'region-name': 'my-region'}

View File

@@ -512,6 +512,32 @@ class TestGetAuth(MockHttpTest):
os_options=os_options, auth_version='2.0', os_options=os_options, auth_version='2.0',
insecure=False) insecure=False)
def test_auth_v2_cert(self):
os_options = {'tenant_name': 'foo'}
c.get_auth_keystone = fake_get_auth_keystone(os_options, None)
auth_url_no_sslauth = 'https://www.tests.com'
auth_url_sslauth = 'https://www.tests.com/client-certificate'
url, token = c.get_auth(auth_url_no_sslauth, 'asdf', 'asdf',
os_options=os_options, auth_version='2.0')
self.assertTrue(url.startswith("http"))
self.assertTrue(token)
url, token = c.get_auth(auth_url_sslauth, 'asdf', 'asdf',
os_options=os_options, auth_version='2.0',
cert='minnie', cert_key='mickey')
self.assertTrue(url.startswith("http"))
self.assertTrue(token)
self.assertRaises(c.ClientException, c.get_auth,
auth_url_sslauth, 'asdf', 'asdf',
os_options=os_options, auth_version='2.0')
self.assertRaises(c.ClientException, c.get_auth,
auth_url_sslauth, 'asdf', 'asdf',
os_options=os_options, auth_version='2.0',
cert='minnie')
def test_auth_v3_with_tenant_name(self): def test_auth_v3_with_tenant_name(self):
# check the correct auth version is passed to get_auth_keystone # check the correct auth version is passed to get_auth_keystone
os_options = {'tenant_name': 'asdf'} os_options = {'tenant_name': 'asdf'}
@@ -1577,6 +1603,15 @@ class TestHTTPConnection(MockHttpTest):
conn = c.http_connection(u'http://www.test.com/', insecure=True) conn = c.http_connection(u'http://www.test.com/', insecure=True)
self.assertEqual(conn[1].requests_args['verify'], False) self.assertEqual(conn[1].requests_args['verify'], False)
def test_cert(self):
conn = c.http_connection(u'http://www.test.com/', cert='minnie')
self.assertEqual(conn[1].requests_args['cert'], 'minnie')
def test_cert_key(self):
conn = c.http_connection(
u'http://www.test.com/', cert='minnie', cert_key='mickey')
self.assertEqual(conn[1].requests_args['cert'], ('minnie', 'mickey'))
def test_response_connection_released(self): def test_response_connection_released(self):
_parsed_url, conn = c.http_connection(u'http://www.test.com/') _parsed_url, conn = c.http_connection(u'http://www.test.com/')
conn.resp = MockHttpResponse() conn.resp = MockHttpResponse()
@@ -2084,8 +2119,8 @@ class TestConnection(MockHttpTest):
return '' return ''
def local_http_connection(url, proxy=None, cacert=None, def local_http_connection(url, proxy=None, cacert=None,
insecure=False, ssl_compression=True, insecure=False, cert=None, cert_key=None,
timeout=None): ssl_compression=True, timeout=None):
parsed = urlparse(url) parsed = urlparse(url)
return parsed, LocalConnection() return parsed, LocalConnection()

View File

@@ -57,6 +57,11 @@ def fake_get_auth_keystone(expected_os_options=None, exc=None,
actual_kwargs['cacert'] is None: actual_kwargs['cacert'] is None:
from swiftclient import client as c from swiftclient import client as c
raise c.ClientException("unverified-certificate") raise c.ClientException("unverified-certificate")
if auth_url.startswith("https") and \
auth_url.endswith("client-certificate") and \
not (actual_kwargs['cert'] and actual_kwargs['cert_key']):
from swiftclient import client as c
raise c.ClientException("noclient-certificate")
return storage_url, token return storage_url, token
return fake_get_auth_keystone return fake_get_auth_keystone
@@ -215,6 +220,7 @@ class MockHttpTest(unittest.TestCase):
on_request = kwargs.get('on_request') on_request = kwargs.get('on_request')
def wrapper(url, proxy=None, cacert=None, insecure=False, def wrapper(url, proxy=None, cacert=None, insecure=False,
cert=None, cert_key=None,
ssl_compression=True, timeout=None): ssl_compression=True, timeout=None):
if storage_url: if storage_url:
self.assertEqual(storage_url, url) self.assertEqual(storage_url, url)