summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLiem Nguyen <liem.m.nguyen@gmail.com>2012-05-23 18:16:50 +0000
committerAdam Young <ayoung@redhat.com>2012-07-03 17:26:34 -0400
commitabc7c47c18f54c33668e9862fac614b7ce1d6d0a (patch)
treec3391d09de94271bd69a7dc59fa84234cb603aa7
parent29be6d081df065e3075f963199641c59b23007cc (diff)
downloadpython-keystoneclient-abc7c47c18f54c33668e9862fac614b7ce1d6d0a.tar.gz
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
-rw-r--r--doc/source/shell.rst21
-rw-r--r--keystoneclient/client.py10
-rw-r--r--keystoneclient/service_catalog.py2
-rw-r--r--keystoneclient/shell.py45
-rw-r--r--tests/test_https.py60
5 files changed, 132 insertions, 6 deletions
diff --git a/doc/source/shell.rst b/doc/source/shell.rst
index f86af72..209d681 100644
--- a/doc/source/shell.rst
+++ b/doc/source/shell.rst
@@ -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::
diff --git a/keystoneclient/client.py b/keystoneclient/client.py
index 337f048..b53d7cf 100644
--- a/keystoneclient/client.py
+++ b/keystoneclient/client.py
@@ -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
diff --git a/keystoneclient/service_catalog.py b/keystoneclient/service_catalog.py
index b78d019..faff74b 100644
--- a/keystoneclient/service_catalog.py
+++ b/keystoneclient/service_catalog.py
@@ -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
diff --git a/keystoneclient/shell.py b/keystoneclient/shell.py
index 4c4c5eb..b1752e2 100644
--- a/keystoneclient/shell.py
+++ b/keystoneclient/shell.py
@@ -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)
diff --git a/tests/test_https.py b/tests/test_https.py
new file mode 100644
index 0000000..4d433c6
--- /dev/null
+++ b/tests/test_https.py
@@ -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()