Allow for stack users in _authorize_stack_user

This change allows stack user credentials to be used to call
describe_stack_resource (heat resource-metadata)

It makes the following changes:
* _authorize_stack_user first attempts authorize with
  stack.access_allowed using the context user_id before falling back
  to looking for ec2 credentials
* context middleware sets the user_id on the context even when the
  username is not specified

This change also adds missing test coverage to ContextMiddleware

Change-Id: Idb655e403ba11a3144dacf34eba0feb59ab8d911
Closes-Bug: #1299982
This commit is contained in:
Steve Baker 2014-04-07 16:56:13 +12:00
parent 5e7344b2e3
commit 57f5089083
5 changed files with 163 additions and 5 deletions

View File

@ -132,17 +132,16 @@ class ContextMiddleware(wsgi.Middleware):
try:
username = None
user_id = None
password = None
aws_creds = None
if headers.get('X-Auth-User') is not None:
username = headers.get('X-Auth-User')
user_id = headers.get('X-User-Id')
password = headers.get('X-Auth-Key')
elif headers.get('X-Auth-EC2-Creds') is not None:
aws_creds = headers.get('X-Auth-EC2-Creds')
user_id = headers.get('X-User-Id')
token = headers.get('X-Auth-Token')
tenant = headers.get('X-Tenant-Name')
tenant_id = headers.get('X-Tenant-Id')

View File

@ -839,8 +839,11 @@ class EngineService(service.Service):
- The user must map to a User resource defined in the requested stack
- The user resource must validate OK against any Policy specified
'''
# We're expecting EC2 credentials because all in-instance credentials
# are deployed as ec2 keypairs
# first check whether access is allowd by context user_id
if stack.access_allowed(cnxt.user_id, resource_name):
return True
# fall back to looking for EC2 credentials in the context
try:
ec2_creds = json.loads(cnxt.aws_creds).get('ec2Credentials')
except (TypeError, AttributeError):

View File

@ -13,8 +13,12 @@
import mock
import os
from oslo.config import cfg
import webob
from heat.common import context
from heat.common import exception
from heat.openstack.common import policy as base_policy
from heat.tests.common import HeatTestCase
policy_path = os.path.dirname(os.path.realpath(__file__)) + "/policy/"
@ -94,3 +98,122 @@ class TestRequestContext(HeatTestCase):
pc.return_value = False
ctx = context.RequestContext(roles=['notadmin'])
self.assertFalse(ctx.is_admin)
class RequestContextMiddlewareTest(HeatTestCase):
scenarios = [(
'empty_headers',
dict(
headers={},
expected_exception=None,
context_dict={
'auth_token': None,
'auth_url': None,
'aws_creds': None,
'is_admin': False,
'password': None,
'roles': [],
'show_deleted': False,
'tenant': None,
'tenant_id': None,
'trust_id': None,
'trustor_user_id': None,
'user': None,
'user_id': None,
'username': None
})
), (
'username_password',
dict(
headers={
'X-Auth-User': 'my_username',
'X-Auth-Key': 'my_password',
'X-Auth-EC2-Creds': '{"ec2Credentials": {}}',
'X-User-Id': '7a87ff18-31c6-45ce-a186-ec7987f488c3',
'X-Auth-Token': 'atoken',
'X-Tenant-Name': 'my_tenant',
'X-Tenant-Id': 'db6808c8-62d0-4d92-898c-d644a6af20e9',
'X-Auth-Url': 'http://192.0.2.1:5000/v1',
'X-Roles': 'role1,role2,role3'
},
expected_exception=None,
context_dict={
'auth_token': 'atoken',
'auth_url': 'http://192.0.2.1:5000/v1',
'aws_creds': None,
'is_admin': False,
'password': 'my_password',
'roles': ['role1', 'role2', 'role3'],
'show_deleted': False,
'tenant': 'my_tenant',
'tenant_id': 'db6808c8-62d0-4d92-898c-d644a6af20e9',
'trust_id': None,
'trustor_user_id': None,
'user': 'my_username',
'user_id': '7a87ff18-31c6-45ce-a186-ec7987f488c3',
'username': 'my_username'
})
), (
'aws_creds',
dict(
headers={
'X-Auth-EC2-Creds': '{"ec2Credentials": {}}',
'X-User-Id': '7a87ff18-31c6-45ce-a186-ec7987f488c3',
'X-Auth-Token': 'atoken',
'X-Tenant-Name': 'my_tenant',
'X-Tenant-Id': 'db6808c8-62d0-4d92-898c-d644a6af20e9',
'X-Auth-Url': 'http://192.0.2.1:5000/v1',
'X-Roles': 'role1,role2,role3',
},
expected_exception=None,
context_dict={
'auth_token': 'atoken',
'auth_url': 'http://192.0.2.1:5000/v1',
'aws_creds': '{"ec2Credentials": {}}',
'is_admin': False,
'password': None,
'roles': ['role1', 'role2', 'role3'],
'show_deleted': False,
'tenant': 'my_tenant',
'tenant_id': 'db6808c8-62d0-4d92-898c-d644a6af20e9',
'trust_id': None,
'trustor_user_id': None,
'user': None,
'user_id': '7a87ff18-31c6-45ce-a186-ec7987f488c3',
'username': None
})
), (
'malformed_roles',
dict(
headers={
'X-Roles': [],
},
expected_exception=exception.NotAuthenticated)
)]
def setUp(self):
super(RequestContextMiddlewareTest, self).setUp()
opts = [
cfg.StrOpt('config_dir', default=policy_path),
cfg.StrOpt('config_file', default='foo'),
cfg.StrOpt('project', default='heat'),
]
cfg.CONF.register_opts(opts)
pf = policy_path + 'check_admin.json'
self.m.StubOutWithMock(base_policy.Enforcer, '_get_policy_path')
base_policy.Enforcer._get_policy_path().MultipleTimes().AndReturn(pf)
self.m.ReplayAll()
def test_context_middleware(self):
middleware = context.ContextMiddleware(None, None)
request = webob.Request.blank('/stacks', headers=self.headers)
if self.expected_exception:
self.assertRaises(
self.expected_exception, middleware.process_request, request)
else:
self.assertIsNone(middleware.process_request(request))
ctx = request.context.to_dict()
for k, v in self.context_dict.items():
self.assertEqual(v, ctx[k], 'Key %s values do not match' % k)

View File

@ -163,6 +163,13 @@ user_policy_template = '''
}
'''
server_config_template = '''
heat_template_version: 2013-05-23
resources:
WebServer:
type: OS::Nova::Server
'''
def get_wordpress_stack(stack_name, ctx):
t = template_format.parse(wp_template)
@ -1372,6 +1379,31 @@ class StackServiceAuthorizeTest(HeatTestCase):
self.stack.delete()
self.m.VerifyAll()
def test_stack_authorize_stack_user_user_id(self):
self.ctx = utils.dummy_context(user_id=str(uuid.uuid4()))
stack = get_stack('stack_authorize_stack_user',
self.ctx,
server_config_template)
self.stack = stack
def handler(resource_name):
return resource_name == 'WebServer'
self.stack.register_access_allowed_handler(self.ctx.user_id, handler)
# matching credential_id and resource_name
self.assertTrue(self.eng._authorize_stack_user(
self.ctx, self.stack, 'WebServer'))
# not matching resource_name
self.assertFalse(self.eng._authorize_stack_user(
self.ctx, self.stack, 'NoSuchResource'))
# not matching credential_id
self.ctx.user_id = str(uuid.uuid4())
self.assertFalse(self.eng._authorize_stack_user(
self.ctx, self.stack, 'WebServer'))
class StackServiceTest(HeatTestCase):

View File

@ -133,11 +133,12 @@ def reset_dummy_db():
def dummy_context(user='test_username', tenant_id='test_tenant_id',
password='password', roles=[]):
password='password', roles=[], user_id=None):
return context.RequestContext.from_dict({
'tenant_id': tenant_id,
'tenant': 'test_tenant',
'username': user,
'user_id': user_id,
'password': password,
'roles': roles,
'is_admin': False,