From 91352c012bc396aae9b1ed264eb9de5f09ff3817 Mon Sep 17 00:00:00 2001 From: Ian Main Date: Tue, 12 Jun 2012 16:37:26 -0700 Subject: [PATCH] Pass Full Credentials to Engine In order to support HA operations, eg restarting an instance, we need to have full credentials in the engine. This patch passes in the credential information into the engine and uses it to validate the the user. A future patch will have this information stored in database and associated with each stack. It also assigns the username in the case of EC2 style authentication allowing us to support per-user stacks with EC2 auth. Change-Id: I4b92f83d4d10a2bfebd4ddedc8a4f53b3e1217fe Signed-off-by: Ian Main --- heat/api/v1/__init__.py | 2 ++ heat/common/context.py | 41 ++++++++++++++++++++++++++--- heat/engine/manager.py | 58 ++++++++++++++++++++++++++++++++++++----- 3 files changed, 90 insertions(+), 11 deletions(-) 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): """