diff --git a/heat/api/v1/__init__.py b/heat/api/v1/__init__.py index ceeec986ea..f54a184331 100644 --- a/heat/api/v1/__init__.py +++ b/heat/api/v1/__init__.py @@ -104,8 +104,10 @@ class EC2Token(wsgi.Middleware): raise webob.exc.HTTPBadRequest() # Authenticated! + req.headers['X-Auth-EC2-Creds'] = creds_json req.headers['X-Auth-Token'] = token_id req.headers['X-Auth-URL'] = self.conf['auth_uri'] + req.headers['X-Auth-EC2_URL'] = self.conf['keystone_ec2_uri'] return self.application diff --git a/heat/common/context.py b/heat/common/context.py index b815a9143f..d452bb2b04 100644 --- a/heat/common/context.py +++ b/heat/common/context.py @@ -19,6 +19,7 @@ from heat.common import wsgi from heat.openstack.common import cfg from heat.openstack.common import importutils from heat.common import utils as heat_utils +import json def generate_request_id(): @@ -32,8 +33,10 @@ class RequestContext(object): """ def __init__(self, auth_token=None, username=None, password=None, - tenant=None, tenant_id=None, auth_url=None, roles=None, - is_admin=False, read_only=False, show_deleted=False, + aws_creds=None, aws_auth_uri=None, + service_user=None, service_password=None, tenant=None, + tenant_id=None, auth_url=None, roles=None, is_admin=False, + read_only=False, show_deleted=False, owner_is_tenant=True, overwrite=True, **kwargs): """ :param overwrite: Set to False to ensure that the greenthread local @@ -46,6 +49,10 @@ class RequestContext(object): self.auth_token = auth_token self.username = username self.password = password + self.aws_creds = aws_creds + self.aws_auth_uri = aws_auth_uri + self.service_user = service_user + self.service_password = service_password self.tenant = tenant self.tenant_id = tenant_id self.auth_url = auth_url @@ -64,6 +71,10 @@ class RequestContext(object): return {'auth_token': self.auth_token, 'username': self.username, 'password': self.password, + 'aws_creds': self.aws_creds, + 'aws_auth_uri': self.aws_auth_uri, + 'service_user': self.service_user, + 'service_password': self.service_password, 'tenant': self.tenant, 'tenant_id': self.tenant_id, 'auth_url': self.auth_url, @@ -148,9 +159,27 @@ class ContextMiddleware(wsgi.Middleware): the username. """ + username = None + password = None + aws_creds = None + aws_auth_uri = None + + if headers.get('X-Auth-EC2-Creds') is not None: + aws_creds = headers.get('X-Auth-EC2-Creds') + aws_auth_uri = headers.get('X-Auth-EC2-Url') + else: + # XXX: The eval here is a bit scary, it's possible someone + # could put something malicious in here I would think. + # I Haven't tested to see if WSGI stuff would escape + # everything to make this safe. However, I haven't found + # a better way to do this either. + creds = eval(req.params['KeyStoneCreds']) + username = creds['username'] + password = creds['password'] + token = headers.get('X-Auth-Token') - username = headers.get('X-Admin-User') - password = headers.get('X-Admin-Pass') + service_user = headers.get('X-Admin-User') + service_password = headers.get('X-Admin-Pass') tenant = headers.get('X-Tenant') tenant_id = headers.get('X-Tenant-Id') auth_url = headers.get('X-Auth-Url') @@ -160,7 +189,11 @@ class ContextMiddleware(wsgi.Middleware): req.context = self.make_context(auth_token=token, tenant=tenant, tenant_id=tenant_id, + aws_creds=aws_creds, + aws_auth_uri=aws_auth_uri, username=username, password=password, + service_user=service_user, + service_password=service_password, auth_url=auth_url, roles=roles, is_admin=True) diff --git a/heat/engine/manager.py b/heat/engine/manager.py index 409b37511e..f8bb9c516e 100644 --- a/heat/engine/manager.py +++ b/heat/engine/manager.py @@ -18,6 +18,10 @@ from copy import deepcopy import datetime import logging import webob +import json +import urlparse +import httplib + from heat import manager from heat.db import api as db_api from heat.common import config @@ -29,6 +33,7 @@ from heat.openstack.common import timeutils from novaclient.v1_1 import client from novaclient.exceptions import BadRequest from novaclient.exceptions import NotFound +from novaclient.exceptions import AuthorizationFailure logger = logging.getLogger('heat.engine.manager') @@ -53,15 +58,54 @@ class EngineManager(manager.Manager): 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. """ - nova = client.Client(con.username, con.password, - con.tenant, con.auth_url, - proxy_token=con.auth_token, - proxy_tenant_id=con.tenant_id, - service_type='heat', - service_name='heat') - nova.authenticate() + 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): """