Merge "Support client certificate/key"
This commit is contained in:
@@ -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)
|
||||||
|
|
||||||
|
@@ -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'])
|
||||||
|
|
||||||
|
|
||||||
|
@@ -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']:
|
||||||
|
@@ -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'}
|
||||||
|
@@ -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()
|
||||||
|
|
||||||
|
@@ -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)
|
||||||
|
Reference in New Issue
Block a user