Add policy granularity to the encryption API

Add granularity to the volume_extension:volume_type_encryption
policy with the addition of actions for create, get, update, and delete.

To address backwards compatibility, the new rules added to the
cinder/policies/volume_type.py policy file, default to the existing rule
(volume_extension:volume_type_encryption). That way across upgrades this
should ensure if an existing admin has customised the rule, it keeps
working, but folks that know about the new setting can override the
default rule.

In addtion, a verify_deprecated_policy method is added
to see if the old policy action is being configured instead of the
new actions. This verify_deprecated_policy method is adapted from
previous nova commit from this patch: https://review.openstack.org/#/c/449288

Change-Id: Iba58e785df934d1c4175c0877d266193ac0167b7
This commit is contained in:
Chi Lo 2018-05-25 09:27:51 -05:00
parent 1aa9231cb2
commit ebc9a12a19
5 changed files with 147 additions and 6 deletions

View File

@ -25,7 +25,9 @@ from cinder.api import validation
from cinder import db
from cinder import exception
from cinder.i18n import _
from cinder.policies import base
from cinder.policies import volume_type as policy
from cinder import policy as cinder_policy
from cinder import rpc
from cinder.volume import volume_types
@ -56,10 +58,26 @@ class VolumeTypeEncryptionController(wsgi.Controller):
else:
return False
def _authorize_policy(self, context, new_policy):
# TODO(cl566n): In future release, this _authorize_policy function
# can be removed. The call to it can be replaced by
# context.authorize(new_policy) once the old
# policy.ENCRYPTION_POLICY is deprecated.
using_old_action = cinder_policy.verify_deprecated_policy(
policy.ENCRYPTION_POLICY,
new_policy,
base.RULE_ADMIN_API,
context)
if not using_old_action:
context.authorize(new_policy)
def index(self, req, type_id):
"""Returns the encryption specs for a given volume type."""
context = req.environ['cinder.context']
context.authorize(policy.ENCRYPTION_POLICY)
self._authorize_policy(context, policy.GET_ENCRYPTION_POLICY)
self._check_type(context, type_id)
return self._get_volume_type_encryption(context, type_id)
@ -67,7 +85,7 @@ class VolumeTypeEncryptionController(wsgi.Controller):
def create(self, req, type_id, body):
"""Create encryption specs for an existing volume type."""
context = req.environ['cinder.context']
context.authorize(policy.ENCRYPTION_POLICY)
self._authorize_policy(context, policy.CREATE_ENCRYPTION_POLICY)
key_size = body['encryption'].get('key_size')
if key_size is not None:
@ -95,7 +113,7 @@ class VolumeTypeEncryptionController(wsgi.Controller):
def update(self, req, type_id, id, body):
"""Update encryption specs for a given volume type."""
context = req.environ['cinder.context']
context.authorize(policy.ENCRYPTION_POLICY)
self._authorize_policy(context, policy.UPDATE_ENCRYPTION_POLICY)
key_size = body['encryption'].get('key_size')
if key_size is not None:
@ -119,7 +137,7 @@ class VolumeTypeEncryptionController(wsgi.Controller):
def show(self, req, type_id, id):
"""Return a single encryption item."""
context = req.environ['cinder.context']
context.authorize(policy.ENCRYPTION_POLICY)
self._authorize_policy(context, policy.GET_ENCRYPTION_POLICY)
self._check_type(context, type_id)
@ -133,7 +151,7 @@ class VolumeTypeEncryptionController(wsgi.Controller):
def delete(self, req, type_id, id):
"""Delete encryption specs for a given volume type."""
context = req.environ['cinder.context']
context.authorize(policy.ENCRYPTION_POLICY)
self._authorize_policy(context, policy.DELETE_ENCRYPTION_POLICY)
if self._encrypted_type_in_use(context, type_id):
expl = _('Cannot delete encryption specs. Volume type in use.')

View File

@ -20,6 +20,11 @@ from cinder.policies import base
MANAGE_POLICY = "volume_extension:types_manage"
ENCRYPTION_POLICY = "volume_extension:volume_type_encryption"
BASE_POLICY_RULE = 'rule:%s' % ENCRYPTION_POLICY
CREATE_ENCRYPTION_POLICY = "volume_extension:volume_type_encryption:create"
GET_ENCRYPTION_POLICY = "volume_extension:volume_type_encryption:get"
UPDATE_ENCRYPTION_POLICY = "volume_extension:volume_type_encryption:update"
DELETE_ENCRYPTION_POLICY = "volume_extension:volume_type_encryption:delete"
QOS_POLICY = "volume_extension:access_types_qos_specs_id"
EXTRA_SPEC_POLICY = "volume_extension:access_types_extra_specs"
@ -46,7 +51,8 @@ volume_type_policies = [
name=ENCRYPTION_POLICY,
check_str=base.RULE_ADMIN_API,
description="List, show, create, update and delete volume "
"type encryption.",
"type encryption. This is deprecated in the Stein "
"release and will be removed in the future.",
operations=[
{
'method': 'POST',
@ -69,6 +75,50 @@ volume_type_policies = [
'path': '/types/{type_id}/encryption/{encryption_id}'
}
]),
policy.DocumentedRuleDefault(
name=CREATE_ENCRYPTION_POLICY,
check_str=BASE_POLICY_RULE,
description="Create volume type encryption.",
operations=[
{
'method': 'POST',
'path': '/types/{type_id}/encryption'
}
]),
policy.DocumentedRuleDefault(
name=GET_ENCRYPTION_POLICY,
check_str=BASE_POLICY_RULE,
description="Show, list volume type encryption.",
operations=[
{
'method': 'GET',
'path': '/types/{type_id}/encryption/{encryption_id}'
},
{
'method': 'GET',
'path': '/types/{type_id}/encryption'
}
]),
policy.DocumentedRuleDefault(
name=UPDATE_ENCRYPTION_POLICY,
check_str=BASE_POLICY_RULE,
description="Update volume type encryption.",
operations=[
{
'method': 'PUT',
'path': '/types/{type_id}/encryption/{encryption_id}'
}
]),
policy.DocumentedRuleDefault(
name=DELETE_ENCRYPTION_POLICY,
check_str=BASE_POLICY_RULE,
description="Delete volume type encryption.",
operations=[
{
'method': 'DELETE',
'path': '/types/{type_id}/encryption/{encryption_id}'
}
]),
policy.DocumentedRuleDefault(
name=EXTRA_SPEC_POLICY,
check_str=base.RULE_ADMIN_API,

View File

@ -178,3 +178,33 @@ def check_is_admin(context):
credentials = context.to_policy_values()
target = credentials
return _ENFORCER.authorize('context_is_admin', target, credentials)
def verify_deprecated_policy(old_policy, new_policy, default_rule, context):
"""Check the rule of the deprecated policy action
If the current rule of the deprecated policy action is set to a non-default
value, then a warning message is logged stating that the new policy
action should be used to dictate permissions as the old policy action is
being deprecated.
:param old_policy: policy action that is being deprecated
:param new_policy: policy action that is replacing old_policy
:param default_rule: the old_policy action default rule value
:param context: the cinder context
"""
if _ENFORCER:
current_rule = str(_ENFORCER.rules[old_policy])
else:
current_rule = None
if current_rule != default_rule:
LOG.warning('Start using the new action %(new_policy)s. The existing '
'action %(old_policy)s is being deprecated and will be '
'removed in future release.',
{'new_policy': new_policy, 'old_policy': old_policy})
context.authorize(old_policy)
return True
return False

View File

@ -14,6 +14,7 @@
# under the License.
import os.path
import mock
from oslo_config import cfg
from oslo_config import fixture as config_fixture
from oslo_policy import policy as oslo_policy
@ -75,6 +76,9 @@ class PolicyTestCase(test.TestCase):
"role:admin"),
oslo_policy.RuleDefault("test:uppercase_admin",
"role:ADMIN"),
oslo_policy.RuleDefault("old_action_not_default", "@"),
oslo_policy.RuleDefault("new_action", "@"),
oslo_policy.RuleDefault("old_action_default", "rule:admin_api"),
]
policy.reset()
policy.init()
@ -129,3 +133,26 @@ class PolicyTestCase(test.TestCase):
roles=['AdMiN'])
policy.authorize(admin_context, lowercase_action, self.target)
policy.authorize(admin_context, uppercase_action, self.target)
@mock.patch.object(policy.LOG, 'warning')
def test_verify_deprecated_policy_using_old_action(self, mock_warning):
old_policy = "old_action_not_default"
new_policy = "new_action"
default_rule = "rule:admin_api"
using_old_action = policy.verify_deprecated_policy(
old_policy, new_policy, default_rule, self.context)
self.assertTrue(mock_warning.called)
self.assertTrue(using_old_action)
def test_verify_deprecated_policy_using_new_action(self):
old_policy = "old_action_default"
new_policy = "new_action"
default_rule = "rule:admin_api"
using_old_action = policy.verify_deprecated_policy(
old_policy, new_policy, default_rule, self.context)
self.assertFalse(using_old_action)

View File

@ -0,0 +1,16 @@
---
upgrade:
- |
Add granularity to the ``volume_extension:volume_type_encryption``
policy with the addition of distinct actions for create, get, update,
and delete:
- ``volume_extension:volume_type_encryption:create``
- ``volume_extension:volume_type_encryption:get``
- ``volume_extension:volume_type_encryption:update``
- ``volume_extension:volume_type_encryption:delete``
To address backwards compatibility, the new rules added to the
volume_type.py policy file, default to the existing rule,
``volume_extension:volume_type_encryption``, if it is set to a
non-default value.