[policy in code] Add support for backup resource

This patch adds policy in code support for backup
resources and depends on the basic patch [1].

[1]: https://review.openstack.org/#/c/506976/

Change-Id: I9a79b5ececc587e80129cc980930e168e805b139
Partial-Implements: blueprint policy-in-code
This commit is contained in:
TommyLike 2017-09-25 16:15:29 +08:00
parent 274ce06f58
commit 1462d9c2e4
8 changed files with 201 additions and 41 deletions

View File

@ -79,10 +79,10 @@ class AdminController(wsgi.Controller):
return update return update
def authorize(self, context, action_name): def authorize(self, context, action_name):
# NOTE(tommylikehu): We have two different ways to authorize during # TODO(tommylike): We have two different ways to authorize during
# implementing code base policies, the if/else statement can be # implementing code base policies, the if/else statement can be
# removed when all resources are upgraded. # removed when all resources are upgraded.
if self.resource_name in ['snapshot']: if self.resource_name in ['backup', 'snapshot']:
context.authorize( context.authorize(
'volume_extension:%(resource)s_admin_actions:%(action)s' % 'volume_extension:%(resource)s_admin_actions:%(action)s' %
{'resource': self.resource_name, {'resource': self.resource_name,

View File

@ -22,9 +22,9 @@ from cinder.api.contrib import backups as backups_v2
from cinder.api import microversions as mv from cinder.api import microversions as mv
from cinder.api.openstack import wsgi from cinder.api.openstack import wsgi
from cinder.api.v3.views import backups as backup_views from cinder.api.v3.views import backups as backup_views
from cinder.backup import api as backup_api
from cinder import exception from cinder import exception
from cinder.i18n import _ from cinder.i18n import _
from cinder.policies import backups as policy
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -81,7 +81,7 @@ class BackupsController(backups_v2.BackupsController):
resp_backup = self._view_builder.detail(req, backup) resp_backup = self._view_builder.detail(req, backup)
if req_version.matches(mv.BACKUP_PROJECT): if req_version.matches(mv.BACKUP_PROJECT):
try: try:
backup_api.check_policy(context, 'backup_project_attribute') context.authorize(policy.BACKUP_ATTRIBUTES_POLICY)
self._add_backup_project_attribute(req, resp_backup['backup']) self._add_backup_project_attribute(req, resp_backup['backup'])
except exception.PolicyNotAuthorized: except exception.PolicyNotAuthorized:
pass pass
@ -94,7 +94,7 @@ class BackupsController(backups_v2.BackupsController):
if req_version.matches(mv.BACKUP_PROJECT): if req_version.matches(mv.BACKUP_PROJECT):
try: try:
backup_api.check_policy(context, 'backup_project_attribute') context.authorize(policy.BACKUP_ATTRIBUTES_POLICY)
for bak in resp_backup['backups']: for bak in resp_backup['backups']:
self._add_backup_project_attribute(req, bak) self._add_backup_project_attribute(req, bak)
except exception.PolicyNotAuthorized: except exception.PolicyNotAuthorized:

View File

@ -36,6 +36,7 @@ from cinder import exception
from cinder.i18n import _ from cinder.i18n import _
from cinder import objects from cinder import objects
from cinder.objects import fields from cinder.objects import fields
from cinder.policies import backups as policy
import cinder.policy import cinder.policy
from cinder import quota from cinder import quota
from cinder import quota_utils from cinder import quota_utils
@ -56,15 +57,6 @@ QUOTAS = quota.QUOTAS
IMPORT_VOLUME_ID = '00000000-0000-0000-0000-000000000000' IMPORT_VOLUME_ID = '00000000-0000-0000-0000-000000000000'
def check_policy(context, action):
target = {
'project_id': context.project_id,
'user_id': context.user_id,
}
_action = 'backup:%s' % action
cinder.policy.enforce(context, _action, target)
class API(base.Base): class API(base.Base):
"""API for interacting with the volume backup manager.""" """API for interacting with the volume backup manager."""
@ -74,7 +66,7 @@ class API(base.Base):
super(API, self).__init__(db) super(API, self).__init__(db)
def get(self, context, backup_id): def get(self, context, backup_id):
check_policy(context, 'get') context.authorize(policy.GET_POLICY)
return objects.Backup.get_by_id(context, backup_id) return objects.Backup.get_by_id(context, backup_id)
def _check_support_to_force_delete(self, context, backup_host): def _check_support_to_force_delete(self, context, backup_host):
@ -93,7 +85,7 @@ class API(base.Base):
:raises BackupDriverException: :raises BackupDriverException:
:raises ServiceNotFound: :raises ServiceNotFound:
""" """
check_policy(context, 'delete') context.authorize(policy.DELETE_POLICY)
if not force and backup.status not in [fields.BackupStatus.AVAILABLE, if not force and backup.status not in [fields.BackupStatus.AVAILABLE,
fields.BackupStatus.ERROR]: fields.BackupStatus.ERROR]:
msg = _('Backup status must be available or error') msg = _('Backup status must be available or error')
@ -118,7 +110,7 @@ class API(base.Base):
def get_all(self, context, search_opts=None, marker=None, limit=None, def get_all(self, context, search_opts=None, marker=None, limit=None,
offset=None, sort_keys=None, sort_dirs=None): offset=None, sort_keys=None, sort_dirs=None):
check_policy(context, 'get_all') context.authorize(policy.GET_ALL_POLICY)
search_opts = search_opts or {} search_opts = search_opts or {}
@ -204,7 +196,7 @@ class API(base.Base):
container, incremental=False, availability_zone=None, container, incremental=False, availability_zone=None,
force=False, snapshot_id=None, metadata=None): force=False, snapshot_id=None, metadata=None):
"""Make the RPC call to create a volume backup.""" """Make the RPC call to create a volume backup."""
check_policy(context, 'create') context.authorize(policy.CREATE_POLICY)
utils.check_metadata_properties(metadata) utils.check_metadata_properties(metadata)
volume = self.volume_api.get(context, volume_id) volume = self.volume_api.get(context, volume_id)
snapshot = None snapshot = None
@ -341,7 +333,7 @@ class API(base.Base):
def restore(self, context, backup_id, volume_id=None, name=None): def restore(self, context, backup_id, volume_id=None, name=None):
"""Make the RPC call to restore a volume backup.""" """Make the RPC call to restore a volume backup."""
check_policy(context, 'restore') context.authorize(policy.RESTORE_POLICY)
backup = self.get(context, backup_id) backup = self.get(context, backup_id)
if backup['status'] != fields.BackupStatus.AVAILABLE: if backup['status'] != fields.BackupStatus.AVAILABLE:
msg = _('Backup status must be available') msg = _('Backup status must be available')
@ -444,7 +436,7 @@ class API(base.Base):
:returns: contains 'backup_url' and 'backup_service' :returns: contains 'backup_url' and 'backup_service'
:raises InvalidBackup: :raises InvalidBackup:
""" """
check_policy(context, 'backup-export') context.authorize(policy.EXPORT_POLICY)
backup = self.get(context, backup_id) backup = self.get(context, backup_id)
if backup['status'] != fields.BackupStatus.AVAILABLE: if backup['status'] != fields.BackupStatus.AVAILABLE:
msg = (_('Backup status must be available and not %s.') % msg = (_('Backup status must be available and not %s.') %
@ -537,7 +529,7 @@ class API(base.Base):
:raises ServiceNotFound: :raises ServiceNotFound:
:raises InvalidInput: :raises InvalidInput:
""" """
check_policy(context, 'backup-import') context.authorize(policy.IMPORT_POLICY)
# NOTE(ronenkat): since we don't have a backup-scheduler # NOTE(ronenkat): since we don't have a backup-scheduler
# we need to find a host that support the backup service # we need to find a host that support the backup service
@ -563,7 +555,7 @@ class API(base.Base):
return backup return backup
def update(self, context, backup_id, fields): def update(self, context, backup_id, fields):
check_policy(context, 'update') context.authorize(policy.UPDATE_POLICY)
backup = self.get(context, backup_id) backup = self.get(context, backup_id)
backup.update(fields) backup.update(fields)
backup.save() backup.save()

View File

@ -16,6 +16,8 @@
import itertools import itertools
from cinder.policies import attachments from cinder.policies import attachments
from cinder.policies import backup_actions
from cinder.policies import backups
from cinder.policies import base from cinder.policies import base
from cinder.policies import clusters from cinder.policies import clusters
from cinder.policies import manageable_snapshots from cinder.policies import manageable_snapshots
@ -37,4 +39,6 @@ def list_rules():
snapshots.list_rules(), snapshots.list_rules(),
snapshot_actions.list_rules(), snapshot_actions.list_rules(),
manageable_snapshots.list_rules(), manageable_snapshots.list_rules(),
backups.list_rules(),
backup_actions.list_rules(),
) )

View File

@ -0,0 +1,49 @@
# Copyright (c) 2017 Huawei Technologies Co., Ltd.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_policy import policy
from cinder.policies import base
BASE_POLICY_NAME = 'volume_extension:backup_admin_actions:%s'
backup_actions_policies = [
policy.DocumentedRuleDefault(
name=BASE_POLICY_NAME % 'reset_status',
check_str=base.RULE_ADMIN_API,
description="Reset status of a backup.",
operations=[
{
'method': 'POST',
'path': '/backups/{backup_id}/action (os-reset_status)'
}
]),
policy.DocumentedRuleDefault(
name=BASE_POLICY_NAME % 'force_delete',
check_str=base.RULE_ADMIN_API,
description="Force delete a backup.",
operations=[
{
'method': 'POST',
'path': '/backups/{backup_id}/action (os-force_delete)'
}
]),
]
def list_rules():
return backup_actions_policies

134
cinder/policies/backups.py Normal file
View File

@ -0,0 +1,134 @@
# Copyright (c) 2017 Huawei Technologies Co., Ltd.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_policy import policy
from cinder.policies import base
GET_ALL_POLICY = 'backup:get_all'
GET_POLICY = 'backup:get'
CREATE_POLICY = 'backup:create'
UPDATE_POLICY = 'backup:update'
DELETE_POLICY = 'backup:delete'
RESTORE_POLICY = 'backup:restore'
IMPORT_POLICY = 'backup:backup-import'
EXPORT_POLICY = 'backup:export-import'
BACKUP_ATTRIBUTES_POLICY = 'backup:backup_project_attribute'
backups_policies = [
policy.DocumentedRuleDefault(
name=GET_ALL_POLICY,
check_str=base.RULE_ADMIN_OR_OWNER,
description="List backups.",
operations=[
{
'method': 'GET',
'path': '/backups'
},
{
'method': 'GET',
'path': '/backups/detail'
}
]),
policy.DocumentedRuleDefault(
name=BACKUP_ATTRIBUTES_POLICY,
check_str=base.RULE_ADMIN_API,
description="List backups or show backup with project attributes.",
operations=[
{
'method': 'GET',
'path': '/backups/{backup_id}'
},
{
'method': 'GET',
'path': '/backups/detail'
}
]),
policy.DocumentedRuleDefault(
name=CREATE_POLICY,
check_str="",
description="Create backup.",
operations=[
{
'method': 'POST',
'path': '/backups'
}
]),
policy.DocumentedRuleDefault(
name=GET_POLICY,
check_str=base.RULE_ADMIN_OR_OWNER,
description="Show backup.",
operations=[
{
'method': 'GET',
'path': '/backups/{backup_id}'
}
]),
policy.DocumentedRuleDefault(
name=UPDATE_POLICY,
check_str=base.RULE_ADMIN_OR_OWNER,
description="Update backup.",
operations=[
{
'method': 'PUT',
'path': '/backups/{backup_id}'
}
]),
policy.DocumentedRuleDefault(
name=DELETE_POLICY,
check_str=base.RULE_ADMIN_OR_OWNER,
description="Delete backup.",
operations=[
{
'method': 'DELETE',
'path': '/backups/{backup_id}'
}
]),
policy.DocumentedRuleDefault(
name=RESTORE_POLICY,
check_str=base.RULE_ADMIN_OR_OWNER,
description="Restore backup.",
operations=[
{
'method': 'POST',
'path': '/backups/{backup_id}/restore'
}
]),
policy.DocumentedRuleDefault(
name=IMPORT_POLICY,
check_str=base.RULE_ADMIN_API,
description="Import backup.",
operations=[
{
'method': 'POST',
'path': '/backups/{backup_id}/import_record'
}
]),
policy.DocumentedRuleDefault(
name=EXPORT_POLICY,
check_str=base.RULE_ADMIN_API,
description="Export backup.",
operations=[
{
'method': 'POST',
'path': '/backups/{backup_id}/export_record'
}
]),
]
def list_rules():
return backups_policies

View File

@ -44,8 +44,6 @@
"volume:thaw_host": "rule:admin_api", "volume:thaw_host": "rule:admin_api",
"volume:revert_to_snapshot": "", "volume:revert_to_snapshot": "",
"volume_extension:volume_admin_actions:reset_status": "rule:admin_api", "volume_extension:volume_admin_actions:reset_status": "rule:admin_api",
"volume_extension:backup_admin_actions:reset_status": "rule:admin_api",
"volume_extension:backup_admin_actions:force_delete": "rule:admin_api",
"volume_extension:volume_admin_actions:force_delete": "rule:admin_api", "volume_extension:volume_admin_actions:force_delete": "rule:admin_api",
"volume_extension:volume_admin_actions:force_detach": "rule:admin_api", "volume_extension:volume_admin_actions:force_detach": "rule:admin_api",
"volume_extension:volume_admin_actions:migrate_volume": "rule:admin_api", "volume_extension:volume_admin_actions:migrate_volume": "rule:admin_api",
@ -95,16 +93,10 @@
"volume:get_transfer": "", "volume:get_transfer": "",
"volume:get_all_transfers": "", "volume:get_all_transfers": "",
"backup:create" : "",
"backup:delete": "", "backup:delete": "",
"backup:get": "", "backup:get": "",
"backup:get_all": "", "backup:get_all": "",
"backup:restore": "", "backup:restore": "",
"backup:backup-import": "rule:admin_api",
"backup:backup-export": "rule:admin_api",
"backup:update": "rule:admin_or_owner",
"backup:backup_project_attribute": "rule:admin_api",
"consistencygroup:create" : "", "consistencygroup:create" : "",
"consistencygroup:delete": "", "consistencygroup:delete": "",

View File

@ -46,7 +46,6 @@
"volume_extension:quota_classes:validate_setup_for_nested_quota_use": "rule:admin_api", "volume_extension:quota_classes:validate_setup_for_nested_quota_use": "rule:admin_api",
"volume_extension:volume_admin_actions:reset_status": "rule:admin_api", "volume_extension:volume_admin_actions:reset_status": "rule:admin_api",
"volume_extension:backup_admin_actions:reset_status": "rule:admin_api",
"volume_extension:volume_admin_actions:force_delete": "rule:admin_api", "volume_extension:volume_admin_actions:force_delete": "rule:admin_api",
"volume_extension:volume_admin_actions:force_detach": "rule:admin_api", "volume_extension:volume_admin_actions:force_detach": "rule:admin_api",
"volume_extension:backup_admin_actions:force_delete": "rule:admin_api", "volume_extension:backup_admin_actions:force_delete": "rule:admin_api",
@ -79,16 +78,6 @@
"volume:freeze_host": "rule:admin_api", "volume:freeze_host": "rule:admin_api",
"volume:thaw_host": "rule:admin_api", "volume:thaw_host": "rule:admin_api",
"backup:create" : "",
"backup:delete": "rule:admin_or_owner",
"backup:get": "rule:admin_or_owner",
"backup:get_all": "rule:admin_or_owner",
"backup:restore": "rule:admin_or_owner",
"backup:backup-import": "rule:admin_api",
"backup:backup-export": "rule:admin_api",
"backup:update": "rule:admin_or_owner",
"backup:backup_project_attribute": "rule:admin_api",
"consistencygroup:create" : "group:nobody", "consistencygroup:create" : "group:nobody",
"consistencygroup:delete": "group:nobody", "consistencygroup:delete": "group:nobody",
"consistencygroup:update": "group:nobody", "consistencygroup:update": "group:nobody",