Merge "adding policy checks for cinder"
This commit is contained in:
commit
586002b7f2
|
@ -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": []
|
||||
}
|
|
@ -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": "",
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue