Merge "adding policy checks for cinder"

This commit is contained in:
Jenkins 2014-01-12 18:18:29 +00:00 committed by Gerrit Code Review
commit 586002b7f2
8 changed files with 153 additions and 7 deletions

View File

@ -0,0 +1,58 @@
{
"context_is_admin": [["role:admin"]],
"admin_or_owner": [["is_admin:True"], ["project_id:%(project_id)s"]],
"default": [["rule:admin_or_owner"]],
"admin_api": [["is_admin:True"]],
"volume:create": [],
"volume:delete": [["rule:default"]],
"volume:get_all": [],
"volume:get_volume_metadata": [],
"volume:get_volume_admin_metadata": [["rule:admin_api"]],
"volume:delete_volume_admin_metadata": [["rule:admin_api"]],
"volume:update_volume_admin_metadata": [["rule:admin_api"]],
"volume:create_snapshot": [["rule:default"]],
"volume:delete_snapshot": [["rule:default"]],
"volume:get_snapshot": [],
"volume:get_all_snapshots": [],
"volume:extend": [],
"volume_extension:types_manage": [["rule:admin_api"]],
"volume_extension:types_extra_specs": [["rule:admin_api"]],
"volume_extension:volume_type_encryption": [["rule:admin_api"]],
"volume_extension:volume_encryption_metadata": [["rule:admin_api"]],
"volume_extension:extended_snapshot_attributes": [],
"volume_extension:volume_image_metadata": [],
"volume_extension:quotas:show": [],
"volume_extension:quotas:update": [["rule:admin_api"]],
"volume_extension:quota_classes": [],
"volume_extension:volume_admin_actions:reset_status": [["rule:admin_api"]],
"volume_extension:snapshot_admin_actions:reset_status": [["rule:admin_api"]],
"volume_extension:volume_admin_actions:force_delete": [["rule:admin_api"]],
"volume_extension:snapshot_admin_actions:force_delete": [["rule:admin_api"]],
"volume_extension:volume_admin_actions:migrate_volume": [["rule:admin_api"]],
"volume_extension:volume_admin_actions:migrate_volume_completion": [["rule:admin_api"]],
"volume_extension:volume_host_attribute": [["rule:admin_api"]],
"volume_extension:volume_tenant_attribute": [["rule:admin_api"]],
"volume_extension:volume_mig_status_attribute": [["rule:admin_api"]],
"volume_extension:hosts": [["rule:admin_api"]],
"volume_extension:services": [["rule:admin_api"]],
"volume:services": [["rule:admin_api"]],
"volume:create_transfer": [],
"volume:accept_transfer": [],
"volume:delete_transfer": [],
"volume:get_all_transfers": [],
"backup:create" : [],
"backup:delete": [],
"backup:get": [],
"backup:get_all": [],
"backup:restore": [],
"snapshot_extension:snapshot_actions:update_snapshot_status": []
}

View File

@ -12,6 +12,8 @@
"compute:get_all": "",
"compute:get_all_tenants": "",
"compute:unlock_override": "rule:admin_api",
"compute:attach_volume" : "rule:default",
"compute:detach_volume" : "rule:default",
"compute:shelve": "",
"compute:shelve_offload": "",

View File

@ -23,11 +23,13 @@ class CreateVolumeType(tables.LinkAction):
verbose_name = _("Create Volume Type")
url = "horizon:admin:volumes:create_type"
classes = ("ajax-modal", "btn-create")
policy_rules = (("volume", "volume_extension:types_manage"),)
class DeleteVolumeType(tables.DeleteAction):
data_type_singular = _("Volume Type")
data_type_plural = _("Volume Types")
policy_rules = (("volume", "volume_extension:types_manage"),)
def delete(self, request, obj_id):
cinder.volume_type_delete(request, obj_id)

View File

@ -33,6 +33,15 @@ class DeleteVolumeSnapshot(tables.DeleteAction):
data_type_singular = _("Volume Snapshot")
data_type_plural = _("Volume Snapshots")
action_past = _("Scheduled deletion of %(data_type)s")
policy_rules = (("volume", "volume:delete_snapshot"),)
def get_policy_target(self, request, datum=None):
project_id = None
if datum:
project_id = getattr(datum,
"os-extended-snapshot-attributes:project_id",
None)
return {"project_id": project_id}
def delete(self, request, obj_id):
api.cinder.volume_snapshot_delete(request, obj_id)
@ -43,6 +52,7 @@ class CreateVolumeFromSnapshot(tables.LinkAction):
verbose_name = _("Create Volume")
url = "horizon:project:volumes:create"
classes = ("ajax-modal", "btn-camera")
policy_rules = (("volume", "volume:create"),)
def get_link_url(self, datum):
base_url = reverse(self.url)

View File

@ -28,6 +28,7 @@ from horizon import tables
from openstack_dashboard import api
from openstack_dashboard.api import cinder
from openstack_dashboard import policy
from openstack_dashboard.usage import quotas
@ -38,6 +39,13 @@ class DeleteVolume(tables.DeleteAction):
data_type_singular = _("Volume")
data_type_plural = _("Volumes")
action_past = _("Scheduled deletion of %(data_type)s")
policy_rules = (("volume", "volume:delete"),)
def get_policy_target(self, request, datum=None):
project_id = None
if datum:
project_id = getattr(datum, "os-vol-tenant-attr:tenant_id", None)
return {"project_id": project_id}
def delete(self, request, obj_id):
obj = self.table.get_object_by_id(obj_id)
@ -61,6 +69,7 @@ class CreateVolume(tables.LinkAction):
verbose_name = _("Create Volume")
url = "horizon:project:volumes:create"
classes = ("ajax-modal", "btn-create")
policy_rules = (("volume", "volume:create"),)
def allowed(self, request, volume=None):
usages = quotas.tenant_quota_usages(request)
@ -84,7 +93,20 @@ class EditAttachments(tables.LinkAction):
classes = ("ajax-modal", "btn-edit")
def allowed(self, request, volume=None):
return volume.status in ("available", "in-use")
if volume:
project_id = getattr(volume, "os-vol-tenant-attr:tenant_id", None)
attach_allowed = \
policy.check((("compute", "compute:attach_volume"),),
request,
{"project_id": project_id})
detach_allowed = \
policy.check((("compute", "compute:detach_volume"),),
request,
{"project_id": project_id})
if attach_allowed or detach_allowed:
return volume.status in ("available", "in-use")
return False
class CreateSnapshot(tables.LinkAction):
@ -92,6 +114,13 @@ class CreateSnapshot(tables.LinkAction):
verbose_name = _("Create Snapshot")
url = "horizon:project:volumes:create_snapshot"
classes = ("ajax-modal", "btn-camera")
policy_rules = (("volume", "volume:create_snapshot"),)
def get_policy_target(self, request, datum=None):
project_id = None
if datum:
project_id = getattr(datum, "os-vol-tenant-attr:tenant_id", None)
return {"project_id": project_id}
def allowed(self, request, volume=None):
return volume.status in ("available", "in-use")
@ -219,6 +248,7 @@ class DetachVolume(tables.BatchAction):
data_type_singular = _("Volume")
data_type_plural = _("Volumes")
classes = ('btn-danger', 'btn-detach')
policy_rules = (("compute", "compute:detach_volume"),)
def action(self, request, obj_id):
attachment = self.table.get_object_by_id(obj_id)

View File

@ -97,6 +97,23 @@ def check(actions, request, target={}):
:returns: boolean if the user has permission or not for the actions.
"""
user = auth_utils.get_user(request)
# Several service policy engines default to a project id check for
# ownership. Since the user is already scoped to a project, if a
# different project id has not been specified use the currently scoped
# project's id.
#
# The reason is the operator can edit the local copies of the service
# policy file. If a rule is removed, then the default rule is used. We
# don't want to block all actions because the operator did not fully
# understand the implication of editing the policy file. Additionally,
# the service APIs will correct us if we are too permissive.
if 'project_id' not in target:
target['project_id'] = user.project_id
# same for user_id
if 'user_id' not in target:
target['user_id'] = user.id
credentials = _user_to_credentials(request, user)
enforcer = _get_enforcer()
@ -106,7 +123,17 @@ def check(actions, request, target={}):
if scope in enforcer:
# if any check fails return failure
if not enforcer[scope].enforce(action, target, credentials):
return False
# to match service implementations, if a rule is not found,
# use the default rule for that service policy
#
# waiting to make the check because the first call to
# enforce loads the rules
if action not in enforcer[scope].rules:
if not enforcer[scope].enforce('default',
target, credentials):
return False
else:
return False
# if no policy for scope, allow action, underlying API will
# ultimately block the action if not permitted, treat as though
# allowed

View File

@ -208,7 +208,8 @@ POLICY_FILES_PATH = os.path.join(ROOT_PATH, "conf")
# Map of local copy of service policy files
POLICY_FILES = {
'identity': 'keystone_policy.json',
'compute': 'nova_policy.json'
'compute': 'nova_policy.json',
'volume': 'cinder_policy.json'
}
SECRET_KEY = None

View File

@ -38,9 +38,17 @@ class PolicyTestCase(test.TestCase):
request=self.request)
self.assertFalse(value)
def test_check_nova_admin_required_false(self):
def test_check_identity_rule_not_found_false(self):
policy.reset()
value = policy.check((("compute", "admin__or_owner"),),
value = policy.check((("identity", "i_dont_exist"),),
request=self.request)
# this should fail because the default check for
# identity is admin_required
self.assertFalse(value)
def test_check_nova_context_is_admin_false(self):
policy.reset()
value = policy.check((("compute", "context_is_admin"),),
request=self.request)
self.assertFalse(value)
@ -65,6 +73,14 @@ class PolicyTestCaseAdmin(test.BaseAdminViewTests):
request=self.request)
self.assertTrue(value)
def test_check_identity_rule_not_found_true(self):
policy.reset()
value = policy.check((("identity", "i_dont_exist"),),
request=self.request)
# this should succeed because the default check for
# identity is admin_required
self.assertTrue(value)
def test_compound_check_true(self):
policy.reset()
value = policy.check((("identity", "admin_required"),
@ -72,8 +88,8 @@ class PolicyTestCaseAdmin(test.BaseAdminViewTests):
request=self.request)
self.assertTrue(value)
def test_check_nova_admin_required_true(self):
def test_check_nova_context_is_admin_true(self):
policy.reset()
value = policy.check((("compute", "admin__or_owner"),),
value = policy.check((("compute", "context_is_admin"),),
request=self.request)
self.assertTrue(value)