Support 2-way SSL with Keystone server if it is configured to enforce

2-way SSL.  See also https://review.openstack.org/#/c/7706/ for the
corresponding review for the 2-way SSL addition to Keystone.

Change-Id: If0cb46a43d663687396d93604a7139d85a4e7114
This commit is contained in:
Liem Nguyen
2012-05-23 18:16:50 +00:00
committed by Adam Young
parent 29be6d081d
commit abc7c47c18
5 changed files with 132 additions and 6 deletions

View File

@@ -42,13 +42,32 @@ options, it is easier to just set them as environment variables:
The OpenStack Identity API version.
.. envvar:: OS_CA_CERT
The location for the CA truststore (PEM formatted) for this client.
.. envvar:: OS_CERT
The location for the keystore (PEM formatted) containing the public
key of this client. This keystore can also optionally contain the
private key of this client.
.. envvar:: OS_KEY
The location for the keystore (PEM formatted) containing the private
key of this client. This value can be empty if the private key is
included in the OS_CERT file.
For example, in Bash you'd use::
export OS_USERNAME=yourname
export OS_PASSWORD=yadayadayada
export OS_TENANT_NAME=myproject
export OS_AUTH_URL=http://example.com:5000/v2.0/
export OS_AUTH_URL=http(s)://example.com:5000/v2.0/
export OS_IDENTITY_API_VERSION=2.0
export OS_CA_CERT=/etc/keystone/yourca.pem
export OS_CERT=/etc/keystone/yourpublickey.pem
export OS_KEY=/etc/keystone/yourprivatekey.pem
From there, all shell commands take the form::

View File

@@ -38,8 +38,14 @@ class HTTPClient(httplib2.Http):
def __init__(self, username=None, tenant_id=None, tenant_name=None,
password=None, auth_url=None, region_name=None, timeout=None,
endpoint=None, token=None):
super(HTTPClient, self).__init__(timeout=timeout)
endpoint=None, token=None, cacert=None, key=None,
cert=None):
super(HTTPClient, self).__init__(timeout=timeout, ca_certs=cacert)
if cert:
if key:
self.add_certificate(key=key, cert=cert, domain='')
else:
self.add_certificate(key=cert, cert=cert, domain='')
self.username = username
self.tenant_id = tenant_id
self.tenant_name = tenant_name

View File

@@ -39,7 +39,7 @@ class ServiceCatalog(object):
return token
def url_for(self, attr=None, filter_value=None,
service_type='identity', endpoint_type='publicURL'):
service_type='identity', endpoint_type='publicURL'):
"""Fetch an endpoint from the service catalog.
Fetch the specified endpoint from the service catalog for

View File

@@ -127,6 +127,41 @@ class OpenStackIdentityShell(object):
default=env('SERVICE_ENDPOINT'),
help='Defaults to env[SERVICE_ENDPOINT]')
parser.add_argument('--os_cacert', metavar='<ca-certificate>',
default=env('OS_CA_CERT'),
help='Defaults to env[OS_CA_CERT]')
parser.add_argument('--os_cert', metavar='<certificate>',
default=env('OS_CERT'),
help='Defaults to env[OS_CERT]')
parser.add_argument('--os_key', metavar='<key>',
default=env('OS_KEY'),
help='Defaults to env[OS_KEY]')
# FIXME(dtroyer): The args below are here for diablo compatibility,
# remove them in folsum cycle
parser.add_argument('--username',
metavar='<auth-user-name>',
help='Deprecated')
parser.add_argument('--password',
metavar='<auth-password>',
help='Deprecated')
parser.add_argument('--tenant_name',
metavar='<tenant-name>',
help='Deprecated')
parser.add_argument('--auth_url',
metavar='<auth-url>',
help='Deprecated')
parser.add_argument('--region_name',
metavar='<region-name>',
help='Deprecated')
return parser
def get_subcommand_parser(self, version):
@@ -246,7 +281,10 @@ class OpenStackIdentityShell(object):
'env[OS_AUTH_URL]')
if utils.isunauthenticated(args.func):
self.cs = shell_generic.CLIENT_CLASS(endpoint=args.os_auth_url)
self.cs = shell_generic.CLIENT_CLASS(endpoint=args.os_auth_url,
cacert=args.os_cacert,
key=args.os_key,
cert=args.os_cert)
else:
token = None
endpoint = None
@@ -262,7 +300,10 @@ class OpenStackIdentityShell(object):
endpoint=endpoint,
password=args.os_password,
auth_url=args.os_auth_url,
region_name=args.os_region_name)
region_name=args.os_region_name,
cacert=args.os_cacert,
key=args.os_key,
cert=args.os_cert)
try:
args.func(self.cs, args)

60
tests/test_https.py Normal file
View File

@@ -0,0 +1,60 @@
import httplib2
import mock
from keystoneclient import client
from tests import utils
FAKE_RESPONSE = httplib2.Response({"status": 200})
FAKE_BODY = '{"hi": "there"}'
MOCK_REQUEST = mock.Mock(return_value=(FAKE_RESPONSE, FAKE_BODY))
def get_client():
cl = client.HTTPClient(username="username", password="password",
tenant_id="tenant", auth_url="auth_test",
cacert="ca.pem", key="key.pem", cert="cert.pem")
return cl
def get_authed_client():
cl = get_client()
cl.management_url = "https://127.0.0.1:5000"
cl.auth_token = "token"
return cl
class ClientTest(utils.TestCase):
def test_get(self):
cl = get_authed_client()
@mock.patch.object(httplib2.Http, "request", MOCK_REQUEST)
@mock.patch('time.time', mock.Mock(return_value=1234))
def test_get_call():
resp, body = cl.get("/hi")
headers = {"X-Auth-Token": "token",
"User-Agent": cl.USER_AGENT}
MOCK_REQUEST.assert_called_with("https://127.0.0.1:5000/hi",
"GET", headers=headers)
# Automatic JSON parsing
self.assertEqual(body, {"hi": "there"})
test_get_call()
def test_post(self):
cl = get_authed_client()
@mock.patch.object(httplib2.Http, "request", MOCK_REQUEST)
def test_post_call():
cl.post("/hi", body=[1, 2, 3])
headers = {
"X-Auth-Token": "token",
"Content-Type": "application/json",
"User-Agent": cl.USER_AGENT
}
MOCK_REQUEST.assert_called_with("https://127.0.0.1:5000/hi",
"POST", headers=headers,
body='[1, 2, 3]')
test_post_call()