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 <imain@redhat.com>
This commit is contained in:
Ian Main 2012-06-12 16:37:26 -07:00
parent 709cf56ef7
commit 91352c012b
3 changed files with 90 additions and 11 deletions

View File

@ -104,8 +104,10 @@ class EC2Token(wsgi.Middleware):
raise webob.exc.HTTPBadRequest() raise webob.exc.HTTPBadRequest()
# Authenticated! # Authenticated!
req.headers['X-Auth-EC2-Creds'] = creds_json
req.headers['X-Auth-Token'] = token_id req.headers['X-Auth-Token'] = token_id
req.headers['X-Auth-URL'] = self.conf['auth_uri'] req.headers['X-Auth-URL'] = self.conf['auth_uri']
req.headers['X-Auth-EC2_URL'] = self.conf['keystone_ec2_uri']
return self.application return self.application

View File

@ -19,6 +19,7 @@ from heat.common import wsgi
from heat.openstack.common import cfg from heat.openstack.common import cfg
from heat.openstack.common import importutils from heat.openstack.common import importutils
from heat.common import utils as heat_utils from heat.common import utils as heat_utils
import json
def generate_request_id(): def generate_request_id():
@ -32,8 +33,10 @@ class RequestContext(object):
""" """
def __init__(self, auth_token=None, username=None, password=None, def __init__(self, auth_token=None, username=None, password=None,
tenant=None, tenant_id=None, auth_url=None, roles=None, aws_creds=None, aws_auth_uri=None,
is_admin=False, read_only=False, show_deleted=False, 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): owner_is_tenant=True, overwrite=True, **kwargs):
""" """
:param overwrite: Set to False to ensure that the greenthread local :param overwrite: Set to False to ensure that the greenthread local
@ -46,6 +49,10 @@ class RequestContext(object):
self.auth_token = auth_token self.auth_token = auth_token
self.username = username self.username = username
self.password = password 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 = tenant
self.tenant_id = tenant_id self.tenant_id = tenant_id
self.auth_url = auth_url self.auth_url = auth_url
@ -64,6 +71,10 @@ class RequestContext(object):
return {'auth_token': self.auth_token, return {'auth_token': self.auth_token,
'username': self.username, 'username': self.username,
'password': self.password, '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': self.tenant,
'tenant_id': self.tenant_id, 'tenant_id': self.tenant_id,
'auth_url': self.auth_url, 'auth_url': self.auth_url,
@ -148,9 +159,27 @@ class ContextMiddleware(wsgi.Middleware):
the username. 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') token = headers.get('X-Auth-Token')
username = headers.get('X-Admin-User') service_user = headers.get('X-Admin-User')
password = headers.get('X-Admin-Pass') service_password = headers.get('X-Admin-Pass')
tenant = headers.get('X-Tenant') tenant = headers.get('X-Tenant')
tenant_id = headers.get('X-Tenant-Id') tenant_id = headers.get('X-Tenant-Id')
auth_url = headers.get('X-Auth-Url') auth_url = headers.get('X-Auth-Url')
@ -160,7 +189,11 @@ class ContextMiddleware(wsgi.Middleware):
req.context = self.make_context(auth_token=token, req.context = self.make_context(auth_token=token,
tenant=tenant, tenant_id=tenant_id, tenant=tenant, tenant_id=tenant_id,
aws_creds=aws_creds,
aws_auth_uri=aws_auth_uri,
username=username, username=username,
password=password, password=password,
service_user=service_user,
service_password=service_password,
auth_url=auth_url, roles=roles, auth_url=auth_url, roles=roles,
is_admin=True) is_admin=True)

View File

@ -18,6 +18,10 @@ from copy import deepcopy
import datetime import datetime
import logging import logging
import webob import webob
import json
import urlparse
import httplib
from heat import manager from heat import manager
from heat.db import api as db_api from heat.db import api as db_api
from heat.common import config from heat.common import config
@ -29,6 +33,7 @@ from heat.openstack.common import timeutils
from novaclient.v1_1 import client from novaclient.v1_1 import client
from novaclient.exceptions import BadRequest from novaclient.exceptions import BadRequest
from novaclient.exceptions import NotFound from novaclient.exceptions import NotFound
from novaclient.exceptions import AuthorizationFailure
logger = logging.getLogger('heat.engine.manager') 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 the first call made in an endpoint call. I like to see this
done explicitly so that it is clear there is an authentication done explicitly so that it is clear there is an authentication
request at the entry to the call. 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, if con.password is not None:
con.tenant, con.auth_url, nova = client.Client(con.username, con.password,
proxy_token=con.auth_token, con.tenant, con.auth_url,
proxy_tenant_id=con.tenant_id, service_type='heat',
service_type='heat', service_name='heat')
service_name='heat') nova.authenticate()
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): def list_stacks(self, context, params):
""" """