diff --git a/heat/engine/auth.py b/heat/engine/auth.py new file mode 100644 index 0000000000..c8b27cfdc8 --- /dev/null +++ b/heat/engine/auth.py @@ -0,0 +1,81 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging +import json +import httplib +import urlparse +from novaclient.v1_1 import client +from novaclient.exceptions import BadRequest +from novaclient.exceptions import NotFound +from novaclient.exceptions import AuthorizationFailure +from heat.common import context + +logger = logging.getLogger('heat.engine.auth') + + +def authenticate(con, service_type='heat', service_name='heat'): + """ Authenticate a user context. This authenticates either an + EC2 style key context or a keystone user/pass context. + + In the case of EC2 style authentication this will also set the + username in the context so we can use it to key in the database. + """ + + if con.password is not None: + nova = client.Client(con.username, con.password, + con.tenant, con.auth_url, + service_type=service_type, + service_name=service_name) + nova.authenticate() + return nova + else: + # We'll have to do AWS style auth which is more complex. + # First step is to get a token from the AWS creds. + headers = {'Content-Type': 'application/json'} + + o = urlparse.urlparse(con.aws_auth_uri) + if o.scheme == 'http': + conn = httplib.HTTPConnection(o.netloc) + else: + conn = httplib.HTTPSConnection(o.netloc) + conn.request('POST', o.path, body=con.aws_creds, headers=headers) + response = conn.getresponse().read() + conn.close() + + result = json.loads(response) + try: + token_id = result['access']['token']['id'] + # We grab the username here because with token auth and EC2 + # we never get it normally. We could pass it in but then We + # are relying on user input to give us the correct username. + # This one is the result of the authentication and is verified. + username = result['access']['user']['username'] + con.username = username + + logger.info("AWS authentication successful.") + except (AttributeError, KeyError): + # FIXME: Should be 404 I think. + logger.info("AWS authentication failure.") + raise exception.AuthorizationFailure() + + nova = client.Client(con.service_user, con.service_password, + con.tenant, con.auth_url, + proxy_token=token_id, + proxy_tenant_id=con.tenant_id, + service_type=service_type, + service_name=service_name) + nova.authenticate() + return nova diff --git a/heat/engine/manager.py b/heat/engine/manager.py index 0fee2e77ef..fd8b44a5e1 100644 --- a/heat/engine/manager.py +++ b/heat/engine/manager.py @@ -32,6 +32,7 @@ from heat.common import context as ctxtlib from heat.engine import parser from heat.engine import resources from heat.engine import watchrule +from heat.engine import auth from heat.openstack.common import timeutils from novaclient.v1_1 import client @@ -77,60 +78,6 @@ class EngineManager(manager.Manager): """Load configuration options and connect to the hypervisor.""" pass - def _authenticate(self, con): - """ Authenticate against the 'heat' service. This should be - the first call made in an endpoint call. I like to see this - done explicitly so that it is clear there is an authentication - request at the entry to the call. - - In the case of EC2 style authentication this will also set the - username in the context so we can use it to key in the database. - """ - - if con.password is not None: - nova = client.Client(con.username, con.password, - con.tenant, con.auth_url, - service_type='heat', - service_name='heat') - nova.authenticate() - else: - # We'll have to do AWS style auth which is more complex. - # First step is to get a token from the AWS creds. - headers = {'Content-Type': 'application/json'} - - o = urlparse.urlparse(con.aws_auth_uri) - if o.scheme == 'http': - conn = httplib.HTTPConnection(o.netloc) - else: - conn = httplib.HTTPSConnection(o.netloc) - conn.request('POST', o.path, body=con.aws_creds, headers=headers) - response = conn.getresponse().read() - conn.close() - - result = json.loads(response) - try: - token_id = result['access']['token']['id'] - # We grab the username here because with token auth and EC2 - # we never get it normally. We could pass it in but then We - # are relying on user input to give us the correct username. - # This one is the result of the authentication and is verified. - username = result['access']['user']['username'] - con.username = username - - logger.info("AWS authentication successful.") - except (AttributeError, KeyError): - # FIXME: Should be 404 I think. - logger.info("AWS authentication failure.") - raise exception.AuthorizationFailure() - - nova = client.Client(con.service_user, con.service_password, - con.tenant, con.auth_url, - proxy_token=token_id, - proxy_tenant_id=con.tenant_id, - service_type='heat', - service_name='heat') - nova.authenticate() - def list_stacks(self, context, params): """ The list_stacks method is the end point that actually implements @@ -139,7 +86,7 @@ class EngineManager(manager.Manager): arg2 -> Dict of http request parameters passed in from API side. """ - self._authenticate(context) + auth.authenticate(context) res = {'stacks': []} stacks = db_api.stack_get_by_user(context) @@ -167,7 +114,7 @@ class EngineManager(manager.Manager): arg2 -> Name of the stack you want to see. arg3 -> Dict of http request parameters passed in from API side. """ - self._authenticate(context) + auth.authenticate(context) res = {'stacks': []} s = db_api.stack_get_by_name(context, stack_name) @@ -209,7 +156,7 @@ class EngineManager(manager.Manager): """ logger.info('template is %s' % template) - self._authenticate(context) + auth.authenticate(context) if db_api.stack_get_by_name(None, stack_name): return {'Error': 'Stack already exists with that name.'} @@ -267,7 +214,7 @@ class EngineManager(manager.Manager): arg4 -> Params passed from API. """ - self._authenticate(context) + auth.authenticate(context) logger.info('validate_template') if template is None: @@ -297,7 +244,7 @@ class EngineManager(manager.Manager): arg2 -> Name of the stack you want to see. arg3 -> Dict of http request parameters passed in from API side. """ - self._authenticate(context) + auth.authenticate(context) s = db_api.stack_get_by_name(context, stack_name) if s: return s.raw_template.template @@ -311,7 +258,7 @@ class EngineManager(manager.Manager): arg3 -> Params passed from API. """ - self._authenticate(context) + auth.authenticate(context) st = db_api.stack_get_by_name(context, stack_name) if not st: @@ -347,7 +294,7 @@ class EngineManager(manager.Manager): arg3 -> Params passed from API. """ - self._authenticate(context) + auth.authenticate(context) if stack_name is not None: st = db_api.stack_get_by_name(context, stack_name) @@ -362,7 +309,7 @@ class EngineManager(manager.Manager): def event_create(self, context, event): - self._authenticate(context) + auth.authenticate(context) stack_name = event['stack'] resource_name = event['resource'] @@ -391,7 +338,7 @@ class EngineManager(manager.Manager): return [msg, None] def describe_stack_resource(self, context, stack_name, resource_name): - self._authenticate(context) + auth.authenticate(context) stack = db_api.stack_get_by_name(context, stack_name) if not stack: @@ -405,7 +352,7 @@ class EngineManager(manager.Manager): def describe_stack_resources(self, context, stack_name, physical_resource_id, logical_resource_id): - self._authenticate(context) + auth.authenticate(context) if stack_name: stack = db_api.stack_get_by_name(context, stack_name) @@ -433,7 +380,7 @@ class EngineManager(manager.Manager): return resources def list_stack_resources(self, context, stack_name): - self._authenticate(context) + auth.authenticate(context) stack = db_api.stack_get_by_name(context, stack_name) if not stack: diff --git a/heat/engine/resources.py b/heat/engine/resources.py index c9072a1aa9..b577b80279 100644 --- a/heat/engine/resources.py +++ b/heat/engine/resources.py @@ -24,6 +24,7 @@ from heat.common import exception from heat.common.config import HeatEngineConfigOpts from heat.db import api as db_api from heat.engine import checkeddict +from heat.engine import auth logger = logging.getLogger('heat.engine.resources') @@ -121,13 +122,9 @@ class Resource(object): return self._nova[service_type] con = self.stack.context - self._nova[service_type] = nc.Client(con.username, - con.password, - con.tenant, - con.auth_url, - proxy_token=con.auth_token, - proxy_tenant_id=con.tenant_id, - service_type=service_type) + self._nova[service_type] = auth.authenticate(con, + service_type=service_type, + service_name=None) return self._nova[service_type] def calculate_properties(self): diff --git a/heat/tests/test_stacks.py b/heat/tests/test_stacks.py index 1a785467f5..7daeb815fa 100644 --- a/heat/tests/test_stacks.py +++ b/heat/tests/test_stacks.py @@ -15,6 +15,7 @@ from heat.engine import instance as instances import heat.db as db_api from heat.engine import parser from heat.engine import manager +from heat.engine import auth @attr(tag=['unit', 'resource']) @@ -149,8 +150,8 @@ class stacksTest(unittest.TestCase): ctx = context.get_admin_context() self.m.StubOutWithMock(ctx, 'username') ctx.username = 'fred' - self.m.StubOutWithMock(manager.EngineManager, '_authenticate') - manager.EngineManager._authenticate(ctx).AndReturn(True) + self.m.StubOutWithMock(auth, 'authenticate') + auth.authenticate(ctx).AndReturn(True) s = {} s['name'] = stack.name diff --git a/heat/tests/test_validate.py b/heat/tests/test_validate.py index a073935ac8..9455701771 100644 --- a/heat/tests/test_validate.py +++ b/heat/tests/test_validate.py @@ -16,6 +16,7 @@ from heat.engine import volume as volumes from heat.engine import manager as managers import heat.db as db_api from heat.engine import parser +from heat.engine import auth test_template_volumeattach = ''' { @@ -212,8 +213,8 @@ class validateTest(unittest.TestCase): def test_validate_volumeattach_valid(self): t = json.loads(test_template_volumeattach % 'vdq') - self.m.StubOutWithMock(managers.EngineManager, '_authenticate') - managers.EngineManager._authenticate(None).AndReturn(True) + self.m.StubOutWithMock(auth, 'authenticate') + auth.authenticate(None).AndReturn(True) params = {} stack = parser.Stack(None, 'test_stack', t, 0, params) @@ -227,8 +228,8 @@ class validateTest(unittest.TestCase): def test_validate_volumeattach_invalid(self): t = json.loads(test_template_volumeattach % 'sda') - self.m.StubOutWithMock(managers.EngineManager, '_authenticate') - managers.EngineManager._authenticate(None).AndReturn(True) + self.m.StubOutWithMock(auth, 'authenticate') + auth.authenticate(None).AndReturn(True) params = {} stack = parser.Stack(None, 'test_stack', t, 0, params) @@ -244,8 +245,8 @@ class validateTest(unittest.TestCase): t = json.loads(test_template_ref % 'WikiDatabase') t['Parameters']['KeyName']['Value'] = 'test' params = {} - self.m.StubOutWithMock(managers.EngineManager, '_authenticate') - managers.EngineManager._authenticate(None).AndReturn(True) + self.m.StubOutWithMock(auth, 'authenticate') + auth.authenticate(None).AndReturn(True) self.m.StubOutWithMock(instances.Instance, 'nova') instances.Instance.nova().AndReturn(self.fc) @@ -261,8 +262,8 @@ class validateTest(unittest.TestCase): t = json.loads(test_template_ref % 'WikiDatabasez') t['Parameters']['KeyName']['Value'] = 'test' params = {} - self.m.StubOutWithMock(managers.EngineManager, '_authenticate') - managers.EngineManager._authenticate(None).AndReturn(True) + self.m.StubOutWithMock(auth, 'authenticate') + auth.authenticate(None).AndReturn(True) self.m.StubOutWithMock(instances.Instance, 'nova') instances.Instance.nova().AndReturn(self.fc) @@ -277,8 +278,8 @@ class validateTest(unittest.TestCase): t = json.loads(test_template_findinmap_valid) t['Parameters']['KeyName']['Value'] = 'test' params = {} - self.m.StubOutWithMock(managers.EngineManager, '_authenticate') - managers.EngineManager._authenticate(None).AndReturn(True) + self.m.StubOutWithMock(auth, 'authenticate') + auth.authenticate(None).AndReturn(True) self.m.StubOutWithMock(instances.Instance, 'nova') instances.Instance.nova().AndReturn(self.fc) @@ -293,8 +294,8 @@ class validateTest(unittest.TestCase): t = json.loads(test_template_findinmap_invalid) t['Parameters']['KeyName']['Value'] = 'test' params = {} - self.m.StubOutWithMock(managers.EngineManager, '_authenticate') - managers.EngineManager._authenticate(None).AndReturn(True) + self.m.StubOutWithMock(auth, 'authenticate') + auth.authenticate(None).AndReturn(True) self.m.StubOutWithMock(instances.Instance, 'nova') instances.Instance.nova().AndReturn(self.fc)