Merge "Use oslo_policy.policy.Rules.load to load rules"
diff --git a/.zuul.yaml b/.zuul.yaml
index 66339eb..fb110f0 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -87,6 +87,11 @@
RBAC_TEST_ROLE: member
- job:
+ name: patrole-member-rocky
+ parent: patrole-member
+ override-checkout: stable/rocky
+
+- job:
name: patrole-member-queens
parent: patrole-member
override-checkout: stable/queens
@@ -151,6 +156,7 @@
tempest: true
neutron: true
neutron-segments: true
+ neutron-qos: true
- job:
name: patrole-plugin-member
@@ -184,6 +190,7 @@
jobs:
- patrole-admin
- patrole-member
+ - patrole-member-rocky
- patrole-member-queens
- patrole-member-pike
- patrole-py35-member
@@ -195,3 +202,8 @@
jobs:
- patrole-admin
- patrole-member
+ periodic-stable:
+ jobs:
+ - patrole-member-rocky
+ - patrole-member-queens
+ - patrole-member-pike
diff --git a/devstack/plugin.sh b/devstack/plugin.sh
index 4826d21..a6259f4 100644
--- a/devstack/plugin.sh
+++ b/devstack/plugin.sh
@@ -29,6 +29,9 @@
# 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,9 +41,16 @@
# 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
- iniset $TEMPEST_CONFIG patrole enable_rbac True
+ 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
}
diff --git a/doc/source/framework/policy_authority.rst b/doc/source/framework/policy_authority.rst
index 822c7b6..37b698c 100644
--- a/doc/source/framework/policy_authority.rst
+++ b/doc/source/framework/policy_authority.rst
@@ -60,3 +60,4 @@
.. automodule:: patrole_tempest_plugin.policy_authority
:members:
:undoc-members:
+ :special-members:
diff --git a/doc/source/framework/rbac_authority.rst b/doc/source/framework/rbac_authority.rst
index 84c372b..40f2a8d 100644
--- a/doc/source/framework/rbac_authority.rst
+++ b/doc/source/framework/rbac_authority.rst
@@ -35,3 +35,4 @@
.. automodule:: patrole_tempest_plugin.rbac_authority
:members:
:undoc-members:
+ :special-members:
diff --git a/doc/source/framework/rbac_utils.rst b/doc/source/framework/rbac_utils.rst
index 7143928..d0fe27e 100644
--- a/doc/source/framework/rbac_utils.rst
+++ b/doc/source/framework/rbac_utils.rst
@@ -176,3 +176,4 @@
.. automodule:: patrole_tempest_plugin.rbac_utils
:members:
:private-members:
+ :special-members:
diff --git a/doc/source/framework/rbac_validation.rst b/doc/source/framework/rbac_validation.rst
index 186dfe2..6cd1534 100644
--- a/doc/source/framework/rbac_validation.rst
+++ b/doc/source/framework/rbac_validation.rst
@@ -17,3 +17,4 @@
.. automodule:: patrole_tempest_plugin.rbac_rule_validation
:members:
:private-members:
+ :special-members:
diff --git a/doc/source/framework/requirements_authority.rst b/doc/source/framework/requirements_authority.rst
index 6c4fcc0..628f0c0 100644
--- a/doc/source/framework/requirements_authority.rst
+++ b/doc/source/framework/requirements_authority.rst
@@ -103,3 +103,4 @@
.. automodule:: patrole_tempest_plugin.requirements_authority
:members:
:undoc-members:
+ :special-members:
diff --git a/patrole_tempest_plugin/config.py b/patrole_tempest_plugin/config.py
index df2a899..56a786b 100644
--- a/patrole_tempest_plugin/config.py
+++ b/patrole_tempest_plugin/config.py
@@ -24,16 +24,6 @@
default='admin',
help="""The current RBAC role against which to run
Patrole tests."""),
- cfg.BoolOpt('enable_rbac',
- default=True,
- deprecated_for_removal=True,
- deprecated_reason="""This is a legacy option that was
-meaningful when Patrole existed downstream as a suite of tests inside Tempest.
-Installing the Patrole plugin necessarily means that RBAC tests should be run.
-This option is paradoxical with the Tempest plugin architecture.
-""",
- deprecated_since='R',
- help="Enables Patrole RBAC tests."),
cfg.ListOpt('custom_policy_files',
default=['/etc/%s/policy.json'],
help="""List of the paths to search for policy files. Each
@@ -172,6 +162,11 @@
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.""")
]
diff --git a/patrole_tempest_plugin/rbac_rule_validation.py b/patrole_tempest_plugin/rbac_rule_validation.py
index a7927fc..d3b057c 100644
--- a/patrole_tempest_plugin/rbac_rule_validation.py
+++ b/patrole_tempest_plugin/rbac_rule_validation.py
@@ -38,8 +38,11 @@
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 @@
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 @@
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):
diff --git a/patrole_tempest_plugin/rbac_utils.py b/patrole_tempest_plugin/rbac_utils.py
index c928f40..366e033 100644
--- a/patrole_tempest_plugin/rbac_utils.py
+++ b/patrole_tempest_plugin/rbac_utils.py
@@ -18,7 +18,6 @@
import time
from oslo_log import log as logging
-from oslo_log import versionutils
from oslo_utils import excutils
from tempest import clients
@@ -244,18 +243,6 @@
return [cls.os_primary.auth_provider]
@classmethod
- def skip_rbac_checks(cls):
- if not CONF.patrole.enable_rbac:
- deprecation_msg = ("The `[patrole].enable_rbac` option is "
- "deprecated and will be removed in the S "
- "release. Patrole tests will always be enabled "
- "following installation of the Patrole Tempest "
- "plugin. Use a regex to skip tests")
- versionutils.report_deprecated_feature(LOG, deprecation_msg)
- raise cls.skipException(
- 'Patrole testing not enabled so skipping %s.' % cls.__name__)
-
- @classmethod
def setup_rbac_utils(cls):
cls.rbac_utils = RbacUtils(cls)
diff --git a/patrole_tempest_plugin/tests/api/README.rst b/patrole_tempest_plugin/tests/api/README.rst
new file mode 120000
index 0000000..e2853ec
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/README.rst
@@ -0,0 +1 @@
+../../../doc/source/field_guide/rbac.rst
\ No newline at end of file
diff --git a/patrole_tempest_plugin/tests/api/compute/rbac_base.py b/patrole_tempest_plugin/tests/api/compute/rbac_base.py
index 99137cc..ab4551e 100644
--- a/patrole_tempest_plugin/tests/api/compute/rbac_base.py
+++ b/patrole_tempest_plugin/tests/api/compute/rbac_base.py
@@ -22,11 +22,6 @@
compute_base.BaseV2ComputeTest):
@classmethod
- def skip_checks(cls):
- super(BaseV2ComputeRbacTest, cls).skip_checks()
- cls.skip_rbac_checks()
-
- @classmethod
def setup_clients(cls):
super(BaseV2ComputeRbacTest, cls).setup_clients()
cls.setup_rbac_utils()
diff --git a/patrole_tempest_plugin/tests/api/identity/rbac_base.py b/patrole_tempest_plugin/tests/api/identity/rbac_base.py
index d34caaa..44f5962 100644
--- a/patrole_tempest_plugin/tests/api/identity/rbac_base.py
+++ b/patrole_tempest_plugin/tests/api/identity/rbac_base.py
@@ -28,11 +28,6 @@
base.BaseIdentityTest):
@classmethod
- def skip_checks(cls):
- super(BaseIdentityRbacTest, cls).skip_checks()
- cls.skip_rbac_checks()
-
- @classmethod
def setup_clients(cls):
super(BaseIdentityRbacTest, cls).setup_clients()
cls.setup_rbac_utils()
diff --git a/patrole_tempest_plugin/tests/api/image/rbac_base.py b/patrole_tempest_plugin/tests/api/image/rbac_base.py
index 2220335..becd564 100644
--- a/patrole_tempest_plugin/tests/api/image/rbac_base.py
+++ b/patrole_tempest_plugin/tests/api/image/rbac_base.py
@@ -20,11 +20,6 @@
image_base.BaseV2ImageTest):
@classmethod
- def skip_checks(cls):
- super(BaseV2ImageRbacTest, cls).skip_checks()
- cls.skip_rbac_checks()
-
- @classmethod
def setup_clients(cls):
super(BaseV2ImageRbacTest, cls).setup_clients()
cls.setup_rbac_utils()
diff --git a/patrole_tempest_plugin/tests/api/network/rbac_base.py b/patrole_tempest_plugin/tests/api/network/rbac_base.py
index 39ad311..6102347 100644
--- a/patrole_tempest_plugin/tests/api/network/rbac_base.py
+++ b/patrole_tempest_plugin/tests/api/network/rbac_base.py
@@ -22,11 +22,6 @@
network_base.BaseNetworkTest):
@classmethod
- def skip_checks(cls):
- super(BaseNetworkRbacTest, cls).skip_checks()
- cls.skip_rbac_checks()
-
- @classmethod
def setup_clients(cls):
super(BaseNetworkRbacTest, cls).setup_clients()
cls.setup_rbac_utils()
diff --git a/patrole_tempest_plugin/tests/api/network/test_address_scope_rbac.py b/patrole_tempest_plugin/tests/api/network/test_address_scope_rbac.py
new file mode 100644
index 0000000..cf73669
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/network/test_address_scope_rbac.py
@@ -0,0 +1,139 @@
+# Copyright 2018 AT&T Corporation.
+# 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 tempest.common import utils
+from tempest.lib.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
+from tempest.lib import decorators
+
+from patrole_tempest_plugin import rbac_rule_validation
+from patrole_tempest_plugin.tests.api.network import rbac_base as base
+
+
+class AddressScopeRbacTest(base.BaseNetworkPluginRbacTest):
+
+ @classmethod
+ def skip_checks(cls):
+ super(AddressScopeRbacTest, cls).skip_checks()
+ if not utils.is_extension_enabled('address-scope', 'network'):
+ msg = "address-scope extension not enabled."
+ raise cls.skipException(msg)
+
+ @classmethod
+ def resource_setup(cls):
+ super(AddressScopeRbacTest, cls).resource_setup()
+ cls.network = cls.create_network()
+
+ def _create_address_scope(self, name=None, **kwargs):
+ name = name or data_utils.rand_name(self.__class__.__name__)
+ address_scope = self.ntp_client.create_address_scope(name=name,
+ ip_version=6,
+ **kwargs)
+ address_scope = address_scope['address_scope']
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.ntp_client.delete_address_scope,
+ address_scope['id'])
+ return address_scope
+
+ @rbac_rule_validation.action(service="neutron",
+ rules=["create_address_scope"],
+ expected_error_codes=[403])
+ @decorators.idempotent_id('8cb2d6b5-23c2-4648-997b-7a6ae55be3ad')
+ def test_create_address_scope(self):
+
+ """Create Address Scope
+
+ RBAC test for the neutron create_address_scope policy
+ """
+ with self.rbac_utils.override_role(self):
+ self._create_address_scope()
+
+ @rbac_rule_validation.action(service="neutron",
+ rules=["create_address_scope",
+ "create_address_scope:shared"],
+ expected_error_codes=[403, 403])
+ @decorators.idempotent_id('0c3f55c0-6ebe-4251-afca-62c5cb4632ca')
+ def test_create_address_scope_shared(self):
+
+ """Create Shared Address Scope
+
+ RBAC test for the neutron create_address_scope:shared policy
+ """
+ with self.rbac_utils.override_role(self):
+ self._create_address_scope(shared=True)
+
+ @rbac_rule_validation.action(service="neutron",
+ rules=["get_address_scope"],
+ expected_error_codes=[404])
+ @decorators.idempotent_id('a53f741b-46f6-412f-936f-ac920d449da8')
+ def test_get_address_scope(self):
+
+ """Get Address Scope
+
+ RBAC test for the neutron get_address_scope policy
+ """
+ address_scope = self._create_address_scope()
+ with self.rbac_utils.override_role(self):
+ self.ntp_client.show_address_scope(address_scope['id'])
+
+ @rbac_rule_validation.action(service="neutron",
+ rules=["get_address_scope",
+ "update_address_scope"],
+ expected_error_codes=[404, 403])
+ @decorators.idempotent_id('3ce4d606-e067-4ef5-840f-96c680226e73')
+ def test_update_address_scope(self):
+
+ """Update Address Scope
+
+ RBAC test for neutron update_address_scope policy
+ """
+ address_scope = self._create_address_scope()
+ name = data_utils.rand_name(self.__class__.__name__)
+ with self.rbac_utils.override_role(self):
+ self.ntp_client.update_address_scope(address_scope['id'],
+ name=name)
+
+ @rbac_rule_validation.action(service="neutron",
+ rules=["get_address_scope",
+ "update_address_scope",
+ "update_address_scope:shared"],
+ expected_error_codes=[404, 403, 403])
+ @decorators.idempotent_id('77d3a9d2-721a-4d9f-9654-6b52f113df85')
+ def test_update_address_scope_shared(self):
+
+ """Update Shared Address Scope
+
+ RBAC test for neutron update_address_scope:shared policy
+ """
+ address_scope = self._create_address_scope(shared=True)
+ with self.rbac_utils.override_role(self):
+ self.ntp_client.update_address_scope(address_scope['id'],
+ shared=False)
+
+ @rbac_rule_validation.action(service="neutron",
+ rules=["get_address_scope",
+ "delete_address_scope"],
+ expected_error_codes=[404, 403])
+ @decorators.idempotent_id('277d8e47-e498-4452-b969-a91f747296ba')
+ def test_delete_address_scope(self):
+
+ """Delete Address Scope
+
+ RBAC test for neutron delete_address_scope policy
+ """
+ address_scope = self._create_address_scope()
+ with self.rbac_utils.override_role(self):
+ self.ntp_client.delete_address_scope(address_scope['id'])
diff --git a/patrole_tempest_plugin/tests/api/network/test_dscp_marking_rule_rbac.py b/patrole_tempest_plugin/tests/api/network/test_dscp_marking_rule_rbac.py
new file mode 100644
index 0000000..b9f8365
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/network/test_dscp_marking_rule_rbac.py
@@ -0,0 +1,106 @@
+# Copyright 2018 AT&T Corporation.
+# 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 tempest.common import utils
+from tempest.lib.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
+from tempest.lib import decorators
+
+from patrole_tempest_plugin import rbac_rule_validation
+from patrole_tempest_plugin.tests.api.network import rbac_base as base
+
+
+class DscpMarkingRulePluginRbacTest(base.BaseNetworkPluginRbacTest):
+
+ @classmethod
+ def skip_checks(cls):
+ super(DscpMarkingRulePluginRbacTest, cls).skip_checks()
+ if not utils.is_extension_enabled('qos', 'network'):
+ msg = "qos extension not enabled."
+ raise cls.skipException(msg)
+
+ @classmethod
+ def resource_setup(cls):
+ super(DscpMarkingRulePluginRbacTest, cls).resource_setup()
+ name = data_utils.rand_name(cls.__class__.__name__ + '-qos')
+ cls.policy_id = cls.ntp_client.create_qos_policy(
+ name=name)["policy"]["id"]
+ cls.addClassResourceCleanup(
+ cls.ntp_client.delete_qos_policy, cls.policy_id)
+
+ def create_policy_dscp_marking_rule(cls):
+ rule = cls.ntp_client.create_dscp_marking_rule(cls.policy_id, 10)
+ rule_id = rule['dscp_marking_rule']['id']
+ cls.addCleanup(
+ test_utils.call_and_ignore_notfound_exc,
+ cls.ntp_client.delete_dscp_marking_rule, cls.policy_id, rule_id)
+ return rule_id
+
+ @decorators.idempotent_id('2717AB75-E4CF-4CA4-AF04-5BEC0C808AA5')
+ @rbac_rule_validation.action(service="neutron",
+ rules=["create_policy_dscp_marking_rule"])
+ def test_create_policy_dscp_marking_rule(self):
+ """Create policy_dscp_marking_rule.
+
+ RBAC test for the neutron "create_policy_dscp_marking_rule" policy
+ """
+
+ with self.rbac_utils.override_role(self):
+ self.create_policy_dscp_marking_rule()
+
+ @decorators.idempotent_id('3D68F50E-B948-4B25-8A72-F6F4890BBC6F')
+ @rbac_rule_validation.action(service="neutron",
+ rules=["get_policy_dscp_marking_rule"],
+ expected_error_codes=[404])
+ def test_show_policy_dscp_marking_rule(self):
+ """Show policy_dscp_marking_rule.
+
+ RBAC test for the neutron "get_policy_dscp_marking_rule" policy
+ """
+ rule_id = self.create_policy_dscp_marking_rule()
+
+ with self.rbac_utils.override_role(self):
+ self.ntp_client.show_dscp_marking_rule(self.policy_id, rule_id)
+
+ @decorators.idempotent_id('33830794-8731-45C3-BC97-17718555DD7C')
+ @rbac_rule_validation.action(service="neutron",
+ rules=["get_policy_dscp_marking_rule",
+ "update_policy_dscp_marking_rule"],
+ expected_error_codes=[404, 403])
+ def test_update_policy_dscp_marking_rule(self):
+ """Update policy_dscp_marking_rule.
+
+ RBAC test for the neutron "update_policy_dscp_marking_rule" policy
+ """
+ rule_id = self.create_policy_dscp_marking_rule()
+
+ with self.rbac_utils.override_role(self):
+ self.ntp_client.update_dscp_marking_rule(
+ self.policy_id, rule_id, dscp_mark=16)
+
+ @decorators.idempotent_id('7BF564DD-3648-4D12-8A8B-6D5E576D1843')
+ @rbac_rule_validation.action(service="neutron",
+ rules=["get_policy_dscp_marking_rule",
+ "delete_policy_dscp_marking_rule"],
+ expected_error_codes=[404, 403])
+ def test_delete_policy_dscp_marking_rule(self):
+ """Delete policy_dscp_marking_rule.
+
+ RBAC test for the neutron "delete_policy_dscp_marking_rule" policy
+ """
+ rule_id = self.create_policy_dscp_marking_rule()
+
+ with self.rbac_utils.override_role(self):
+ self.ntp_client.delete_dscp_marking_rule(self.policy_id, rule_id)
diff --git a/patrole_tempest_plugin/tests/api/network/test_qos_rbac.py b/patrole_tempest_plugin/tests/api/network/test_qos_rbac.py
new file mode 100644
index 0000000..20f9e61
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/network/test_qos_rbac.py
@@ -0,0 +1,100 @@
+# Copyright 2018 AT&T Corporation.
+# 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 tempest.common import utils
+from tempest.lib.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
+from tempest.lib import decorators
+
+from patrole_tempest_plugin import rbac_rule_validation
+from patrole_tempest_plugin.tests.api.network import rbac_base as base
+
+
+class QosRbacTest(base.BaseNetworkPluginRbacTest):
+
+ @classmethod
+ def skip_checks(cls):
+ super(QosRbacTest, cls).skip_checks()
+ if not utils.is_extension_enabled('qos', 'network'):
+ msg = "qos extension not enabled."
+ raise cls.skipException(msg)
+
+ @classmethod
+ def resource_setup(cls):
+ super(QosRbacTest, cls).resource_setup()
+ cls.network = cls.create_network()
+
+ def create_policy(self, name=None):
+ name = name or data_utils.rand_name(self.__class__.__name__)
+ policy = self.ntp_client.create_qos_policy(name)['policy']
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.ntp_client.delete_qos_policy, policy['id'])
+ return policy
+
+ @rbac_rule_validation.action(service="neutron",
+ rules=["create_policy"],
+ expected_error_codes=[403])
+ @decorators.idempotent_id('2ade2e48-7f82-4650-a69c-933d8d594636')
+ def test_create_policy(self):
+
+ """Create Policy Test
+
+ RBAC test for the neutron create_policy policy
+ """
+ with self.rbac_utils.override_role(self):
+ self.create_policy()
+
+ @rbac_rule_validation.action(service="neutron",
+ rules=["get_policy"],
+ expected_error_codes=[404])
+ @decorators.idempotent_id('d004a8de-b226-4eb4-9fdc-8202a7f64c56')
+ def test_get_policy(self):
+
+ """Show Policy Test
+
+ RBAC test for the neutron get_policy policy
+ """
+ policy = self.create_policy()
+ with self.rbac_utils.override_role(self):
+ self.ntp_client.show_qos_policy(policy['id'])
+
+ @rbac_rule_validation.action(service="neutron",
+ rules=["get_policy", "update_policy"],
+ expected_error_codes=[404, 403])
+ @decorators.idempotent_id('fb74d56f-1dfc-490b-a9e1-454af583eefb')
+ def test_update_policy(self):
+
+ """Update Policy Test
+
+ RBAC test for the neutron update_policy policy
+ """
+ policy = self.create_policy()
+ with self.rbac_utils.override_role(self):
+ self.ntp_client.update_qos_policy(policy['id'],
+ description='updated')
+
+ @rbac_rule_validation.action(service="neutron",
+ rules=["get_policy", "delete_policy"],
+ expected_error_codes=[404, 403])
+ @decorators.idempotent_id('ef4c23a6-4095-47a6-958e-1df585f7d8db')
+ def test_delete_policy(self):
+
+ """Delete Policy Test
+
+ RBAC test for the neutron delete_policy policy
+ """
+ policy = self.create_policy()
+ with self.rbac_utils.override_role(self):
+ self.ntp_client.delete_qos_policy(policy['id'])
diff --git a/patrole_tempest_plugin/tests/api/network/test_subnetpools_rbac.py b/patrole_tempest_plugin/tests/api/network/test_subnetpools_rbac.py
index 7d02271..62735d7 100644
--- a/patrole_tempest_plugin/tests/api/network/test_subnetpools_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_subnetpools_rbac.py
@@ -65,7 +65,33 @@
@rbac_rule_validation.action(service="neutron",
rules=["create_subnetpool",
- "create_subnetpool:shared"])
+ "create_subnetpool:is_default"],
+ expected_error_codes=[403, 403])
+ @decorators.idempotent_id('1b5509fd-2c32-44a8-a786-1b6ca162dbd2')
+ def test_create_subnetpool_default(self):
+ """Create default subnetpool.
+
+ RBAC test for the neutron create_subnetpool:is_default policy
+ """
+ # Most likely we already have default subnetpools for ipv4 and ipv6,
+ # so we temporary mark them as is_default=False, to let this test pass.
+ def_pools = self.subnetpools_client.list_subnetpools(is_default=True)
+ for default_pool in def_pools["subnetpools"]:
+ self.subnetpools_client.update_subnetpool(default_pool["id"],
+ is_default=False)
+
+ self.addCleanup(self.subnetpools_client.update_subnetpool,
+ default_pool["id"], is_default=True)
+
+ with self.rbac_utils.override_role(self):
+ # It apparently only enforces the policy for is_default=True.
+ # It does nothing for is_default=False
+ self._create_subnetpool(is_default=True)
+
+ @rbac_rule_validation.action(service="neutron",
+ rules=["create_subnetpool",
+ "create_subnetpool:shared"],
+ expected_error_codes=[403, 403])
@decorators.idempotent_id('cf730989-0d47-40bc-b39a-99e7de484723')
def test_create_subnetpool_shared(self):
"""Create subnetpool shared.
diff --git a/patrole_tempest_plugin/tests/api/volume/rbac_base.py b/patrole_tempest_plugin/tests/api/volume/rbac_base.py
index 8fa3b67..14b3151 100644
--- a/patrole_tempest_plugin/tests/api/volume/rbac_base.py
+++ b/patrole_tempest_plugin/tests/api/volume/rbac_base.py
@@ -29,11 +29,6 @@
_api_version = 3
@classmethod
- def skip_checks(cls):
- super(BaseVolumeRbacTest, cls).skip_checks()
- cls.skip_rbac_checks()
-
- @classmethod
def setup_clients(cls):
super(BaseVolumeRbacTest, cls).setup_clients()
cls.setup_rbac_utils()
diff --git a/patrole_tempest_plugin/tests/api/volume/test_encryption_types_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_encryption_types_rbac.py
index f10e41b..2ee80eb 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_encryption_types_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_encryption_types_rbac.py
@@ -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 @@
@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 @@
@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 @@
@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 @@
@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 @@
@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):
diff --git a/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py b/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py
index fe36f2c..1772047 100644
--- a/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py
+++ b/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py
@@ -14,6 +14,7 @@
from __future__ import absolute_import
+import functools
import mock
from oslo_config import cfg
@@ -80,7 +81,6 @@
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 @@
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 @@
@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 @@
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 @@
@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 @@
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 @@
"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):
diff --git a/patrole_tempest_plugin/tests/unit/test_rbac_utils.py b/patrole_tempest_plugin/tests/unit/test_rbac_utils.py
index c5264aa..5132079 100644
--- a/patrole_tempest_plugin/tests/unit/test_rbac_utils.py
+++ b/patrole_tempest_plugin/tests/unit/test_rbac_utils.py
@@ -208,11 +208,6 @@
class FakeRbacTest(rbac_utils.RbacUtilsMixin, test.BaseTestCase):
@classmethod
- def skip_checks(cls):
- super(FakeRbacTest, cls).skip_checks()
- cls.skip_rbac_checks()
-
- @classmethod
def setup_clients(cls):
super(FakeRbacTest, cls).setup_clients()
cls.setup_rbac_utils()
@@ -237,21 +232,3 @@
self.assertTrue(hasattr(child_test, 'rbac_utils'))
self.assertIsInstance(child_test.rbac_utils, rbac_utils.RbacUtils)
-
- def test_skip_rbac_checks(self):
- """Validate that the child class is skipped if `[patrole] enable_rbac`
- is False and that the child class's name is in the skip message.
- """
- self.useFixture(patrole_fixtures.ConfPatcher(enable_rbac=False,
- group='patrole'))
-
- class ChildRbacTest(self.parent_class):
- pass
-
- child_test = ChildRbacTest()
-
- with testtools.ExpectedException(
- testtools.TestCase.skipException,
- value_re=('Patrole testing not enabled so skipping %s.'
- % ChildRbacTest.__name__)):
- child_test.setUpClass()
diff --git a/releasenotes/notes/remove-deprecated-enable-rbac-config-option-a5e46ce1053b7dea.yaml b/releasenotes/notes/remove-deprecated-enable-rbac-config-option-a5e46ce1053b7dea.yaml
new file mode 100644
index 0000000..53b1710
--- /dev/null
+++ b/releasenotes/notes/remove-deprecated-enable-rbac-config-option-a5e46ce1053b7dea.yaml
@@ -0,0 +1,5 @@
+---
+upgrade:
+ - |
+ Remove deprecated ``[patrole].enable_rbac`` configuration option. To skip
+ Patrole tests going forward, use an appropriate regex.
diff --git a/releasenotes/notes/volume-type-encryption-policy-granularity-141ac283b9c0778e.yaml b/releasenotes/notes/volume-type-encryption-policy-granularity-141ac283b9c0778e.yaml
new file mode 100644
index 0000000..4aeb107
--- /dev/null
+++ b/releasenotes/notes/volume-type-encryption-policy-granularity-141ac283b9c0778e.yaml
@@ -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).