limit access to certificate and container:create

Only the user who creates the bay can get the certificate and call
the certificate signing request of the bay and create containers
in the bay, which is needed by [1].

[1] https://github.com/openstack/magnum/blob/master/specs/
    create-trustee-user-for-each-bay.rst

Change-Id: Id959b76cb136ffbb0e6bcb8c3b83e02b30de66cf
Closes-Bug: #1536883
Partially-Implements: blueprint create-trustee-user-for-each-bay
This commit is contained in:
Hua Wang 2016-02-25 18:48:03 +08:00
parent 138483a0bd
commit ce5b55dd31
7 changed files with 77 additions and 31 deletions

View File

@ -3,6 +3,7 @@
"admin_or_owner": "is_admin:True or project_id:%(project_id)s",
"default": "rule:admin_or_owner",
"admin_api": "rule:context_is_admin",
"admin_or_user": "is_admin:True or user_id:%(user_id)s",
"bay:create": "rule:default",
"bay:delete": "rule:default",
@ -40,15 +41,15 @@
"service:get_all": "rule:default",
"service:update": "rule:default",
"container:create": "rule:default",
"container:delete": "rule:default",
"container:create": "rule:admin_or_user",
"container:delete": "rule:admin_or_user",
"container:detail": "rule:default",
"container:get": "rule:default",
"container:get_all": "rule:default",
"container:update": "rule:default",
"container:update": "rule:admin_or_user",
"certificate:create": "rule:default",
"certificate:get": "rule:default",
"certificate:create": "rule:admin_or_user",
"certificate:get": "rule:admin_or_user",
"magnum-service:get_all": "rule:admin_api"
}

View File

@ -125,30 +125,34 @@ class CertificateController(rest.RestController):
}
@wsme_pecan.wsexpose(Certificate, types.uuid_or_name)
@policy.enforce_wsgi("certificate", "get")
def get_one(self, bay_ident):
"""Retrieve information about the given certificate.
:param bay_ident: UUID of a bay or
logical name of the bay.
"""
context = pecan.request.context
bay = api_utils.get_resource('Bay', bay_ident)
policy.enforce(context, 'certificate:get', bay,
action='certificate:get')
certificate = pecan.request.rpcapi.get_ca_certificate(bay)
return Certificate.convert_with_links(certificate)
@wsme_pecan.wsexpose(Certificate, body=Certificate, status_code=201)
@policy.enforce_wsgi("certificate", "create")
def post(self, certificate):
"""Create a new certificate.
:param certificate: a certificate within the request body.
"""
certificate_dict = certificate.as_dict()
context = pecan.request.context
bay = certificate.get_bay()
policy.enforce(context, 'certificate:create', bay,
action='certificate:create')
certificate_dict = certificate.as_dict()
certificate_dict['project_id'] = context.project_id
certificate_dict['user_id'] = context.user_id
cert_obj = objects.Certificate(context, **certificate_dict)
new_cert = pecan.request.rpcapi.sign_certificate(certificate.get_bay(),
new_cert = pecan.request.rpcapi.sign_certificate(bay,
cert_obj)
return Certificate.convert_with_links(new_cert)

View File

@ -363,7 +363,6 @@ class ContainersController(rest.RestController):
return Container.convert_with_links(res_container)
@expose.expose(Container, body=Container, status_code=201)
@policy.enforce_wsgi("container", "create")
@validation.enforce_bay_types('swarm')
def post(self, container):
"""Create a new container.
@ -372,6 +371,9 @@ class ContainersController(rest.RestController):
"""
container_dict = container.as_dict()
context = pecan.request.context
bay = api_utils.get_resource('Bay', container_dict['bay_uuid'])
policy.enforce(context, 'container:create', bay,
action='container:create')
container_dict['project_id'] = context.project_id
container_dict['user_id'] = context.user_id
new_container = objects.Container(context, **container_dict)
@ -386,7 +388,6 @@ class ContainersController(rest.RestController):
@wsme.validate(types.uuid, [ContainerPatchType])
@expose.expose(Container, types.uuid_or_name,
body=[ContainerPatchType])
@policy.enforce_wsgi("container", "update")
def patch(self, container_ident, patch):
"""Update an existing container.
@ -395,6 +396,10 @@ class ContainersController(rest.RestController):
"""
container = api_utils.get_resource('Container',
container_ident)
context = pecan.request.context
bay = api_utils.get_resource('Bay', container.bay_uuid)
policy.enforce(context, 'container:update', bay,
action='container:update')
try:
container_dict = container.as_dict()
new_container = Container(**api_utils.apply_jsonpatch(
@ -418,7 +423,6 @@ class ContainersController(rest.RestController):
return Container.convert_with_links(container)
@expose.expose(None, types.uuid_or_name, status_code=204)
@policy.enforce_wsgi("container")
def delete(self, container_ident):
"""Delete a container.
@ -426,5 +430,9 @@ class ContainersController(rest.RestController):
"""
container = api_utils.get_resource('Container',
container_ident)
context = pecan.request.context
bay = api_utils.get_resource('Bay', container.bay_uuid)
policy.enforce(context, 'container:delete', bay,
action='container:delete')
pecan.request.rpcapi.container_delete(container.uuid)
container.destroy()

View File

@ -90,6 +90,8 @@ def enforce(context, rule=None, target=None,
"""
enforcer = init()
credentials = context.to_dict()
if not exc:
exc = exception.PolicyNotAuthorized
if target is None:
target = {'project_id': context.project_id,
'user_id': context.user_id}

View File

@ -17,7 +17,7 @@ from magnum.api.controllers.v1 import certificate as api_cert
from magnum.common import utils
from magnum.tests import base
from magnum.tests.unit.api import base as api_base
from magnum.tests.unit.api import utils as apiutils
from magnum.tests.unit.api import utils as api_utils
from magnum.tests.unit.objects import utils as obj_utils
@ -25,7 +25,7 @@ class TestCertObject(base.TestCase):
@mock.patch('magnum.api.utils.get_resource')
def test_cert_init(self, mock_get_resource):
cert_dict = apiutils.cert_post_data()
cert_dict = api_utils.cert_post_data()
mock_bay = mock.MagicMock()
mock_bay.uuid = cert_dict['bay_uuid']
mock_get_resource.return_value = mock_bay
@ -50,7 +50,7 @@ class TestGetCertificate(api_base.FunctionalTest):
self.addCleanup(conductor_api_patcher.stop)
def test_get_one(self):
fake_cert = apiutils.cert_post_data()
fake_cert = api_utils.cert_post_data()
mock_cert = mock.MagicMock()
mock_cert.as_dict.return_value = fake_cert
self.conductor_api.get_ca_certificate.return_value = mock_cert
@ -62,7 +62,7 @@ class TestGetCertificate(api_base.FunctionalTest):
self.assertEqual(fake_cert['pem'], response['pem'])
def test_get_one_by_name(self):
fake_cert = apiutils.cert_post_data()
fake_cert = api_utils.cert_post_data()
mock_cert = mock.MagicMock()
mock_cert.as_dict.return_value = fake_cert
self.conductor_api.get_ca_certificate.return_value = mock_cert
@ -95,7 +95,7 @@ class TestGetCertificate(api_base.FunctionalTest):
self.assertTrue(response.json['error_message'])
def test_links(self):
fake_cert = apiutils.cert_post_data()
fake_cert = api_utils.cert_post_data()
mock_cert = mock.MagicMock()
mock_cert.as_dict.return_value = fake_cert
self.conductor_api.get_ca_certificate.return_value = mock_cert
@ -130,7 +130,7 @@ class TestPost(api_base.FunctionalTest):
return cert
def test_create_cert(self, ):
new_cert = apiutils.cert_post_data(bay_uuid=self.bay.uuid)
new_cert = api_utils.cert_post_data(bay_uuid=self.bay.uuid)
del new_cert['pem']
response = self.post_json('/certificates', new_cert)
@ -140,7 +140,7 @@ class TestPost(api_base.FunctionalTest):
self.assertEqual('fake-pem', response.json['pem'])
def test_create_cert_by_bay_name(self, ):
new_cert = apiutils.cert_post_data(bay_uuid=self.bay.name)
new_cert = api_utils.cert_post_data(bay_uuid=self.bay.name)
del new_cert['pem']
response = self.post_json('/certificates', new_cert)
@ -151,7 +151,7 @@ class TestPost(api_base.FunctionalTest):
self.assertEqual('fake-pem', response.json['pem'])
def test_create_cert_bay_not_found(self, ):
new_cert = apiutils.cert_post_data(bay_uuid='not_found')
new_cert = api_utils.cert_post_data(bay_uuid='not_found')
del new_cert['pem']
response = self.post_json('/certificates', new_cert,
@ -168,7 +168,7 @@ class TestCertPolicyEnforcement(api_base.FunctionalTest):
super(TestCertPolicyEnforcement, self).setUp()
def _common_policy_check(self, rule, func, *arg, **kwarg):
self.policy.set_rules({rule: "project:non_fake"})
self.policy.set_rules({rule: "project_id:non_fake"})
response = func(*arg, **kwarg)
self.assertEqual(403, response.status_int)
self.assertEqual('application/json', response.content_type)
@ -177,14 +177,15 @@ class TestCertPolicyEnforcement(api_base.FunctionalTest):
json.loads(response.json['error_message'])['faultstring'])
def test_policy_disallow_get_one(self):
bay = obj_utils.create_test_bay(self.context)
self._common_policy_check(
"certificate:get", self.get_json,
'/certificates/ce5da569-4f65-4272-9199-fac8c9fbc9d4',
'/certificates/%s' % bay.uuid,
expect_errors=True)
def test_policy_disallow_create(self):
bay = obj_utils.create_test_bay(self.context)
cert = apiutils.cert_post_data(bay_uuid=bay.uuid)
cert = api_utils.cert_post_data(bay_uuid=bay.uuid)
self._common_policy_check(
"certificate:create", self.post_json, '/certificates', cert,
expect_errors=True)

View File

@ -635,7 +635,7 @@ class TestContainerController(api_base.FunctionalTest):
class TestContainerEnforcement(api_base.FunctionalTest):
def _common_policy_check(self, rule, func, *arg, **kwarg):
self.policy.set_rules({rule: 'project:non_fake'})
self.policy.set_rules({rule: 'project_id:non_fake'})
response = func(*arg, **kwarg)
self.assertEqual(403, response.status_int)
self.assertEqual('application/json', response.content_type)
@ -662,21 +662,24 @@ class TestContainerEnforcement(api_base.FunctionalTest):
expect_errors=True)
def test_policy_disallow_update(self):
test_container = utils.get_test_container()
container_uuid = test_container.get('uuid')
bay = obj_utils.create_test_bay(self.context)
container = obj_utils.create_test_container(self.context,
bay_uuid=bay.uuid)
params = [{'path': '/name',
'value': 'new_name',
'op': 'replace'}]
self._common_policy_check(
'container:update', self.app.patch_json,
'/v1/containers/%s' % container_uuid, params,
'/v1/containers/%s' % container.uuid, params,
expect_errors=True)
@patch('magnum.objects.Bay.get_by_uuid')
def test_policy_disallow_create(self, mock_get_by_uuid):
def test_policy_disallow_create(self):
baymodel = obj_utils.create_test_baymodel(self.context)
bay = obj_utils.create_test_bay(self.context,
baymodel_id=baymodel.uuid)
params = ('{"name": "My Docker", "image": "ubuntu",'
'"command": "env", "memory": "512m",'
'"bay_uuid": "fff114da-3bfa-4a0f-a123-c0dffad9718e"}')
'"bay_uuid": "%s"}' % bay.uuid)
self._common_policy_check(
'container:create', self.app.post, '/v1/containers', params=params,
@ -684,7 +687,10 @@ class TestContainerEnforcement(api_base.FunctionalTest):
expect_errors=True)
def test_policy_disallow_delete(self):
bay = obj_utils.create_test_bay(self.context)
container = obj_utils.create_test_container(self.context,
bay_uuid=bay.uuid)
self._common_policy_check(
'container:delete', self.app.delete,
'/v1/containers/%s' % comm_utils.generate_uuid(),
'/v1/containers/%s' % container.uuid,
expect_errors=True)

View File

@ -198,3 +198,27 @@ def get_test_magnum_service_object(context, **kw):
for key in db_magnum_service:
setattr(magnum_service, key, db_magnum_service[key])
return magnum_service
def create_test_container(context, **kw):
"""Create and return a test container object.
Create a container in the DB and return a container object with
appropriate attributes.
"""
container = get_test_container(context, **kw)
container.create()
return container
def get_test_container(context, **kw):
"""Return a test container object with appropriate attributes.
NOTE: The object leaves the attributes marked as changed, such
that a create() could be used to commit it to the DB.
"""
db_container = db_utils.get_test_container(**kw)
container = objects.Container(context)
for key in db_container:
setattr(container, key, db_container[key])
return container