From 11d04c8d6a7bf3fb5d6de2bd47117cea3c9fe403 Mon Sep 17 00:00:00 2001 From: Anant Patil Date: Thu, 29 Jan 2015 13:08:46 +0530 Subject: [PATCH] Enable SSL for EC2Tokens. When keystone is deployed behind SSL, the ec2_authtoken options doesn't have a way to include the same SSL options that the various clients use, so it's not possible to authenticate tokens. Capability to handle SSL options is added. ec2token makes use of HTTP request object from httplib. Config options to specify CA file, client side certificate, key file and "verify server certificate option" will be listed under "ec2authtoken" group in conf file. Change-Id: Ibede73a17ae951cff00a7d9629a4c08f82208139 Closes-Bug: #1415223 --- heat/api/aws/ec2token.py | 34 +++++++++++++++++++++++++++++++-- heat/tests/test_api_ec2token.py | 33 ++++++++++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/heat/api/aws/ec2token.py b/heat/api/aws/ec2token.py index 418a0cf761..0853832e88 100644 --- a/heat/api/aws/ec2token.py +++ b/heat/api/aws/ec2token.py @@ -39,7 +39,21 @@ opts = [ default=[], help=_('Allowed keystone endpoints for auth_uri when ' 'multi_cloud is enabled. At least one endpoint needs ' - 'to be specified.')) + 'to be specified.')), + cfg.StrOpt('cert_file', + default=None, + help=_('Optional PEM-formatted certificate chain file.')), + cfg.StrOpt('key_file', + default=None, + help=_('Optional PEM-formatted file that contains the ' + 'private key.')), + cfg.StrOpt('ca_file', + default=None, + help=_('Optional CA cert file to use in SSL connections.')), + cfg.BoolOpt('insecure', + default=False, + help=_('If set, then the server\'s certificate will not ' + 'be verified.')), ] cfg.CONF.register_opts(opts, group='ec2authtoken') @@ -50,6 +64,7 @@ class EC2Token(wsgi.Middleware): def __init__(self, app, conf): self.conf = conf self.application = app + self._ssl_options = None def _conf_get(self, name): # try config from paste-deploy first @@ -131,6 +146,19 @@ class EC2Token(wsgi.Middleware): last_failure = e raise last_failure or exception.HeatAccessDeniedError() + @property + def ssl_options(self): + if not self._ssl_options: + cacert = self._conf_get('ca_file') + insecure = self._conf_get('insecure') + cert = self._conf_get('cert_file') + key = self._conf_get('key_file') + self._ssl_options = { + 'verify': cacert if cacert else not insecure, + 'cert': (cert, key) if cert else None + } + return self._ssl_options + def _authorize(self, req, auth_uri): # Read request signature and access id. # If we find X-Auth-User in the headers we ignore a key error @@ -185,7 +213,9 @@ class EC2Token(wsgi.Middleware): keystone_ec2_uri = self._conf_get_keystone_ec2_uri(auth_uri) LOG.info(_LI('Authenticating with %s'), keystone_ec2_uri) response = requests.post(keystone_ec2_uri, data=creds_json, - headers=headers) + headers=headers, + verify=self.ssl_options['verify'], + cert=self.ssl_options['cert']) result = response.json() try: token_id = result['access']['token']['id'] diff --git a/heat/tests/test_api_ec2token.py b/heat/tests/test_api_ec2token.py index ee115e79f0..82fd022e5e 100644 --- a/heat/tests/test_api_ec2token.py +++ b/heat/tests/test_api_ec2token.py @@ -60,6 +60,34 @@ class Ec2TokenTest(common.HeatTestCase): 'http://192.0.2.9/v2.0/ec2tokens', ec2._conf_get_keystone_ec2_uri('http://192.0.2.9/v2.0/')) + def test_conf_get_ssl_default_options(self): + ec2 = ec2token.EC2Token(app=None, conf={}) + self.assertTrue(ec2.ssl_options['verify'], + "SSL verify should be True by default") + self.assertIsNone(ec2.ssl_options['cert'], + "SSL client cert should be None by default") + + def test_conf_ssl_insecure_option(self): + ec2 = ec2token.EC2Token(app=None, conf={}) + cfg.CONF.set_default('insecure', 'True', group='ec2authtoken') + cfg.CONF.set_default('ca_file', None, group='ec2authtoken') + self.assertFalse(ec2.ssl_options['verify']) + + def test_conf_get_ssl_opts(self): + cfg.CONF.set_default('auth_uri', 'https://192.0.2.9/v2.0/', + group='ec2authtoken') + cfg.CONF.set_default('ca_file', '/home/user/cacert.pem', + group='ec2authtoken') + cfg.CONF.set_default('insecure', 'false', group='ec2authtoken') + cfg.CONF.set_default('cert_file', '/home/user/mycert', + group='ec2authtoken') + cfg.CONF.set_default('key_file', '/home/user/mykey', + group='ec2authtoken') + ec2 = ec2token.EC2Token(app=None, conf={}) + self.assertEqual('/home/user/cacert.pem', ec2.ssl_options['verify']) + self.assertEqual(('/home/user/mycert', '/home/user/mykey'), + ec2.ssl_options['cert']) + def test_get_signature_param_old(self): params = {'Signature': 'foo'} dummy_req = self._dummy_GET_request(params) @@ -183,7 +211,8 @@ class Ec2TokenTest(common.HeatTestCase): self.assertEqual('xyz', ec2.__call__(dummy_req)) def _stub_http_connection(self, headers=None, params=None, response=None, - req_url='http://123:5000/v2.0/ec2tokens'): + req_url='http://123:5000/v2.0/ec2tokens', + verify=True, cert=None): headers = headers or {} params = params or {} @@ -206,7 +235,7 @@ class Ec2TokenTest(common.HeatTestCase): "path": "/v1", "body_hash": body_hash}}) req_headers = {'Content-Type': 'application/json'} - requests.post(req_url, data=req_creds, + requests.post(req_url, data=req_creds, verify=verify, cert=cert, headers=req_headers).AndReturn(DummyHTTPResponse()) def test_call_ok(self):