Add granularity for volume_extension:volume_type_encryption
Use granular rules: volume_extension:volume_type_encryption:create volume_extension:volume_type_encryption:delete volume_extension:volume_type_encryption:update volume_extension:volume_type_encryption:get for the corresponding create, delete, update, and get volume_type_encryption test cases. Depends-On: Iba58e785df934d1c4175c0877d266193ac0167b7 Change-Id: Ie5159166505d9bee3e99ca0d51949f6391c569b9
This commit is contained in:
parent
787fbd7254
commit
8c04bd8780
|
@ -29,6 +29,9 @@ function install_patrole_tempest_plugin {
|
|||
|
||||
# These policies were removed in Stein but are available in Pike.
|
||||
iniset $TEMPEST_CONFIG policy-feature-enabled removed_nova_policies_stein False
|
||||
|
||||
# TODO(cl566n): Policies used by Patrole testing. Remove these once stable/pike becomes EOL.
|
||||
iniset $TEMPEST_CONFIG policy-feature-enabled added_cinder_policies_stein False
|
||||
fi
|
||||
|
||||
if [[ ${DEVSTACK_SERIES} == 'queens' ]]; then
|
||||
|
@ -38,6 +41,14 @@ function install_patrole_tempest_plugin {
|
|||
|
||||
# These policies were removed in Stein but are available in Queens.
|
||||
iniset $TEMPEST_CONFIG policy-feature-enabled removed_nova_policies_stein False
|
||||
|
||||
# TODO(cl566n): Policies used by Patrole testing. Remove these once stable/queens becomes EOL.
|
||||
iniset $TEMPEST_CONFIG policy-feature-enabled added_cinder_policies_stein False
|
||||
fi
|
||||
|
||||
if [[ ${DEVSTACK_SERIES} == 'rocky' ]]; then
|
||||
# TODO(cl566n): Policies used by Patrole testing. Remove these once stable/rocky becomes EOL.
|
||||
iniset $TEMPEST_CONFIG policy-feature-enabled added_cinder_policies_stein False
|
||||
fi
|
||||
|
||||
iniset $TEMPEST_CONFIG patrole rbac_test_role $RBAC_TEST_ROLE
|
||||
|
|
|
@ -162,6 +162,11 @@ was changed in a backwards-incompatible way."""),
|
|||
help="""Are the Nova API extension policies available in the
|
||||
cloud (e.g. os_compute_api:os-extended-availability-zone)? These policies were
|
||||
removed in Stein because Nova API extension concept was removed in Pike."""),
|
||||
cfg.BoolOpt('added_cinder_policies_stein',
|
||||
default=True,
|
||||
help="""Are the Cinder API extension policies available in the
|
||||
cloud (e.g. [create|update|get|delete]_encryption_policy)? These policies are
|
||||
added in Stein.""")
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -38,8 +38,11 @@ _DEFAULT_ERROR_CODE = 403
|
|||
RBACLOG = logging.getLogger('rbac_reporting')
|
||||
|
||||
|
||||
def action(service, rule='', rules=None,
|
||||
expected_error_code=_DEFAULT_ERROR_CODE, expected_error_codes=None,
|
||||
def action(service,
|
||||
rule='',
|
||||
rules=None,
|
||||
expected_error_code=_DEFAULT_ERROR_CODE,
|
||||
expected_error_codes=None,
|
||||
extra_target_data=None):
|
||||
"""A decorator for verifying OpenStack policy enforcement.
|
||||
|
||||
|
@ -72,16 +75,18 @@ def action(service, rule='', rules=None,
|
|||
As such, negative and positive testing can be applied using this decorator.
|
||||
|
||||
:param str service: An OpenStack service. Examples: "nova" or "neutron".
|
||||
:param str rule: (DEPRECATED) A policy action defined in a policy.json file
|
||||
or in code.
|
||||
:param list rules: A list of policy actions defined in a policy.json file
|
||||
:param rule: (DEPRECATED) A policy action defined in a policy.json file
|
||||
or in code. Also accepts a callable that returns a policy action.
|
||||
:type rule: str or callable
|
||||
:param rules: A list of policy actions defined in a policy.json file
|
||||
or in code. The rules are logical-ANDed together to derive the expected
|
||||
result.
|
||||
result. Also accepts list of callables that return a policy action.
|
||||
|
||||
.. note::
|
||||
|
||||
Patrole currently only supports custom JSON policy files.
|
||||
|
||||
:type rules: list[str] or list[callable]
|
||||
:param int expected_error_code: (DEPRECATED) Overrides default value of 403
|
||||
(Forbidden) with endpoint-specific error code. Currently only supports
|
||||
403 and 404. Support for 404 is needed because some services, like
|
||||
|
@ -316,7 +321,11 @@ def _prepare_multi_policy(rule, rules, exp_error_code, exp_error_codes):
|
|||
for i in range(num_rules - num_ecs):
|
||||
exp_error_codes.append(_DEFAULT_ERROR_CODE)
|
||||
|
||||
return rules, exp_error_codes
|
||||
evaluated_rules = [
|
||||
r() if callable(r) else r for r in rules
|
||||
]
|
||||
|
||||
return evaluated_rules, exp_error_codes
|
||||
|
||||
|
||||
def _is_authorized(test_obj, service, rule, extra_target_data):
|
||||
|
|
|
@ -13,12 +13,36 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import functools
|
||||
|
||||
from tempest.common import utils
|
||||
from tempest import config
|
||||
from tempest.lib import decorators
|
||||
|
||||
from patrole_tempest_plugin import rbac_rule_validation
|
||||
from patrole_tempest_plugin.tests.api.volume import rbac_base
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
def _get_volume_type_encryption_policy(action):
|
||||
feature_flag = CONF.policy_feature_enabled.added_cinder_policies_stein
|
||||
|
||||
if feature_flag:
|
||||
return "volume_extension:volume_type_encryption:%s" % action
|
||||
|
||||
return "volume_extension:volume_type_encryption"
|
||||
|
||||
|
||||
_CREATE_VOLUME_TYPE_ENCRYPTION = functools.partial(
|
||||
_get_volume_type_encryption_policy, "create")
|
||||
_SHOW_VOLUME_TYPE_ENCRYPTION = functools.partial(
|
||||
_get_volume_type_encryption_policy, "get")
|
||||
_UPDATE_VOLUME_TYPE_ENCRYPTION = functools.partial(
|
||||
_get_volume_type_encryption_policy, "update")
|
||||
_DELETE_VOLUME_TYPE_ENCRYPTION = functools.partial(
|
||||
_get_volume_type_encryption_policy, "delete")
|
||||
|
||||
|
||||
class EncryptionTypesV3RbacTest(rbac_base.BaseVolumeRbacTest):
|
||||
|
||||
|
@ -45,7 +69,7 @@ class EncryptionTypesV3RbacTest(rbac_base.BaseVolumeRbacTest):
|
|||
@decorators.idempotent_id('ffd94ce5-c24b-4b6c-84c9-c5aad8c3010c')
|
||||
@rbac_rule_validation.action(
|
||||
service="cinder",
|
||||
rule="volume_extension:volume_type_encryption")
|
||||
rule=_CREATE_VOLUME_TYPE_ENCRYPTION)
|
||||
def test_create_volume_type_encryption(self):
|
||||
vol_type_id = self.create_volume_type()['id']
|
||||
with self.rbac_utils.override_role(self):
|
||||
|
@ -57,7 +81,7 @@ class EncryptionTypesV3RbacTest(rbac_base.BaseVolumeRbacTest):
|
|||
@decorators.idempotent_id('6599e72e-acef-4c0d-a9b2-463fca30d1da')
|
||||
@rbac_rule_validation.action(
|
||||
service="cinder",
|
||||
rule="volume_extension:volume_type_encryption")
|
||||
rule=_DELETE_VOLUME_TYPE_ENCRYPTION)
|
||||
def test_delete_volume_type_encryption(self):
|
||||
vol_type_id = self._create_volume_type_encryption()
|
||||
with self.rbac_utils.override_role(self):
|
||||
|
@ -66,7 +90,7 @@ class EncryptionTypesV3RbacTest(rbac_base.BaseVolumeRbacTest):
|
|||
@decorators.idempotent_id('42da9fec-32fd-4dca-9242-8a53b2fed25a')
|
||||
@rbac_rule_validation.action(
|
||||
service="cinder",
|
||||
rule="volume_extension:volume_type_encryption")
|
||||
rule=_UPDATE_VOLUME_TYPE_ENCRYPTION)
|
||||
def test_update_volume_type_encryption(self):
|
||||
vol_type_id = self._create_volume_type_encryption()
|
||||
with self.rbac_utils.override_role(self):
|
||||
|
@ -77,7 +101,7 @@ class EncryptionTypesV3RbacTest(rbac_base.BaseVolumeRbacTest):
|
|||
@decorators.idempotent_id('1381a3dc-248f-4282-b231-c9399018c804')
|
||||
@rbac_rule_validation.action(
|
||||
service="cinder",
|
||||
rule="volume_extension:volume_type_encryption")
|
||||
rule=_SHOW_VOLUME_TYPE_ENCRYPTION)
|
||||
def test_show_volume_type_encryption(self):
|
||||
vol_type_id = self._create_volume_type_encryption()
|
||||
with self.rbac_utils.override_role(self):
|
||||
|
@ -86,7 +110,7 @@ class EncryptionTypesV3RbacTest(rbac_base.BaseVolumeRbacTest):
|
|||
@decorators.idempotent_id('d4ed3cf8-52b2-4fa2-910d-e405361f0881')
|
||||
@rbac_rule_validation.action(
|
||||
service="cinder",
|
||||
rule="volume_extension:volume_type_encryption")
|
||||
rule=_SHOW_VOLUME_TYPE_ENCRYPTION)
|
||||
def test_show_encryption_specs_item(self):
|
||||
vol_type_id = self._create_volume_type_encryption()
|
||||
with self.rbac_utils.override_role(self):
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import functools
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
|
||||
|
@ -80,7 +81,6 @@ class RBACRuleValidationTest(BaseRBACRuleValidationTest):
|
|||
pass
|
||||
|
||||
test_policy(self.mock_test_args)
|
||||
mock_log.warning.assert_not_called()
|
||||
mock_log.error.assert_not_called()
|
||||
|
||||
@mock.patch.object(rbac_rv, 'LOG', autospec=True)
|
||||
|
@ -99,7 +99,6 @@ class RBACRuleValidationTest(BaseRBACRuleValidationTest):
|
|||
raise exceptions.Forbidden()
|
||||
|
||||
test_policy(self.mock_test_args)
|
||||
mock_log.warning.assert_not_called()
|
||||
mock_log.error.assert_not_called()
|
||||
|
||||
@mock.patch.object(rbac_rv, 'LOG', autospec=True)
|
||||
|
@ -130,7 +129,8 @@ class RBACRuleValidationTest(BaseRBACRuleValidationTest):
|
|||
@mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
|
||||
def test_rule_validation_rbac_malformed_response_positive(
|
||||
self, mock_authority, mock_log):
|
||||
"""Test RbacMalformedResponse error is thrown without permission passes.
|
||||
"""Test RbacMalformedResponse error is thrown without permission
|
||||
passes.
|
||||
|
||||
Positive test case: if RbacMalformedResponse is thrown and the user is
|
||||
not allowed to perform the action, then this is a success.
|
||||
|
@ -143,7 +143,6 @@ class RBACRuleValidationTest(BaseRBACRuleValidationTest):
|
|||
raise rbac_exceptions.RbacMalformedResponse()
|
||||
|
||||
mock_log.error.assert_not_called()
|
||||
mock_log.warning.assert_not_called()
|
||||
|
||||
@mock.patch.object(rbac_rv, 'LOG', autospec=True)
|
||||
@mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
|
||||
|
@ -171,7 +170,8 @@ class RBACRuleValidationTest(BaseRBACRuleValidationTest):
|
|||
@mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
|
||||
def test_rule_validation_rbac_conflicting_policies_positive(
|
||||
self, mock_authority, mock_log):
|
||||
"""Test RbacConflictingPolicies error is thrown without permission passes.
|
||||
"""Test RbacConflictingPolicies error is thrown without permission
|
||||
passes.
|
||||
|
||||
Positive test case: if RbacConflictingPolicies is thrown and the user
|
||||
is not allowed to perform the action, then this is a success.
|
||||
|
@ -184,7 +184,6 @@ class RBACRuleValidationTest(BaseRBACRuleValidationTest):
|
|||
raise rbac_exceptions.RbacConflictingPolicies()
|
||||
|
||||
mock_log.error.assert_not_called()
|
||||
mock_log.warning.assert_not_called()
|
||||
|
||||
@mock.patch.object(rbac_rv, 'LOG', autospec=True)
|
||||
@mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
|
||||
|
@ -448,6 +447,66 @@ class RBACRuleValidationLoggingTest(BaseRBACRuleValidationTest):
|
|||
"Allowed",
|
||||
"Allowed")
|
||||
|
||||
@mock.patch.object(rbac_rv, 'LOG', autospec=True)
|
||||
@mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
|
||||
def test_rule_validation_with_callable_rule(self, mock_authority,
|
||||
mock_log):
|
||||
"""Test that a callable as the rule is evaluated correctly."""
|
||||
mock_authority.PolicyAuthority.return_value.allowed.return_value = True
|
||||
|
||||
@rbac_rv.action(mock.sentinel.service,
|
||||
rule=lambda: mock.sentinel.action)
|
||||
def test_policy(*args):
|
||||
pass
|
||||
|
||||
test_policy(self.mock_test_args)
|
||||
|
||||
policy_authority = mock_authority.PolicyAuthority.return_value
|
||||
policy_authority.allowed.assert_called_with(
|
||||
mock.sentinel.action,
|
||||
CONF.patrole.rbac_test_role)
|
||||
|
||||
mock_log.error.assert_not_called()
|
||||
|
||||
@mock.patch.object(rbac_rv, 'LOG', autospec=True)
|
||||
@mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
|
||||
def test_rule_validation_with_conditional_callable_rule(
|
||||
self, mock_authority, mock_log):
|
||||
"""Test that a complex callable with conditional logic as the rule is
|
||||
evaluated correctly.
|
||||
"""
|
||||
mock_authority.PolicyAuthority.return_value.allowed.return_value = True
|
||||
|
||||
def partial_func(x):
|
||||
return "foo" if x == "bar" else "qux"
|
||||
foo_callable = functools.partial(partial_func, "bar")
|
||||
bar_callable = functools.partial(partial_func, "baz")
|
||||
|
||||
@rbac_rv.action(mock.sentinel.service,
|
||||
rule=foo_callable)
|
||||
def test_foo_policy(*args):
|
||||
pass
|
||||
|
||||
@rbac_rv.action(mock.sentinel.service,
|
||||
rule=bar_callable)
|
||||
def test_bar_policy(*args):
|
||||
pass
|
||||
|
||||
test_foo_policy(self.mock_test_args)
|
||||
policy_authority = mock_authority.PolicyAuthority.return_value
|
||||
policy_authority.allowed.assert_called_with(
|
||||
"foo",
|
||||
CONF.patrole.rbac_test_role)
|
||||
policy_authority.allowed.reset_mock()
|
||||
|
||||
test_bar_policy(self.mock_test_args)
|
||||
policy_authority = mock_authority.PolicyAuthority.return_value
|
||||
policy_authority.allowed.assert_called_with(
|
||||
"qux",
|
||||
CONF.patrole.rbac_test_role)
|
||||
|
||||
mock_log.error.assert_not_called()
|
||||
|
||||
|
||||
class RBACRuleValidationNegativeTest(BaseRBACRuleValidationTest):
|
||||
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
features:
|
||||
- |
|
||||
Added new Cinder feature flag (``CONF.policy_feature_enabled.added_cinder_policies_stein``)
|
||||
for the following newly introduced granular Cinder policies:
|
||||
|
||||
- ``volume_extension:volume_type_encryption:create``
|
||||
- ``volume_extension:volume_type_encryption:get``
|
||||
- ``volume_extension:volume_type_encryption:update``
|
||||
- ``volume_extension:volume_type_encryption:delete``
|
||||
|
||||
The corresponding Patrole test cases are modified to support
|
||||
the granularity. The test cases also support backward
|
||||
compatibility with the old single rule:
|
||||
``volume_extension:volume_type_encryption``
|
||||
|
||||
The ``rules`` parameter in ``rbac_rule_validation.action``
|
||||
decorator now also accepts a list of callables; each callable
|
||||
should return a policy action (str).
|
Loading…
Reference in New Issue