Merge "Keystone v3 tests for endpoint filters for projects"
diff --git a/HACKING.rst b/HACKING.rst
index 722759f..bb337b0 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -1,4 +1,6 @@
-patrole Style Commandments
-===============================================
+Patrole Style Commandments
+==========================
-Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/
+Read the OpenStack Style Commandments: `<http://docs.openstack.org/developer/hacking/>`__
+
+Also review Tempest's Style Commandments: `<https://docs.openstack.org/developer/tempest/HACKING.html>`__
diff --git a/README.rst b/README.rst
index d9a6507..c527c3b 100644
--- a/README.rst
+++ b/README.rst
@@ -1,10 +1,11 @@
-=======
-patrole
-=======
+========
+Overview
+========
-Patrole is a tool for verifying that Role-Based Access Control is being enforced.
+Patrole is a tool for verifying that Role-Based Access Control is being
+correctly enforced.
-Patrole allows users to run API tests using specified RBAC roles. This allows
+Patrole allows users to run API tests using specified RBAC roles. This allows
deployments to verify that only intended roles have access to those APIs.
This is critical to ensure security, especially in large deployments with
custom roles.
@@ -15,18 +16,144 @@
* Bugs: http://bugs.launchpad.net/patrole
Features
---------
-
-Patrole offers RBAC testing for various OpenStack RBAC policies. It includes
+========
+Patrole offers RBAC testing for various OpenStack RBAC policies. It includes
a decorator that wraps around tests which verifies that when the test calls the
-corresponding api endpoint, access is only granted for correct roles.
+corresponding API endpoint, access is only granted for correct roles.
+
+Currently, Patrole supports policies contained in code and in policy.json files.
+If both exist, the policy actions in the policy.json are prioritized.
+
+.. _test-flows:
+
+Test Flows
+----------
There are several possible test flows.
-If the rbac_test_role is allowed to access the endpoint
- - The test passes if no 403 forbidden or RbacActionFailed exception is raised.
+If the ``rbac_test_role`` is allowed to access the endpoint:
-If the rbac_test_role is not allowed to access the endpoint
- - If the endpoint returns a 403 forbidden exception the test will pass
- - If the endpoint returns something other than a 403 forbidden to indicate
- that the role is not allowed, the test will raise an RbacActionFailed exception.
+* The test passes if no 403 ``Forbidden`` or ``RbacActionFailed`` exception is raised.
+
+If the ``rbac_test_role`` is not allowed to access the endpoint:
+
+* If the endpoint returns a 403 `Forbidden` exception the test will pass.
+* If the endpoint returns successfully, then the test will fail with an
+ ``RbacOverPermission`` exception.
+* If the endpoint returns something other than a 403 ``Forbidden`` to indicate
+ that the role is not allowed, the test will raise an ``RbacActionFailed`` exception.
+
+.. note::
+
+ Certain services like Neutron *intentionally* raise a 404 instead of a 403
+ for security concerns. Patrole accomodates this behavior by anticipating
+ a 404 instead of a 403, if specified through a special argument. For more
+ information about Neutron's policy enforcement, see:
+ `<https://docs.openstack.org/developer/neutron/devref/policy.html#request-authorization>`__.
+
+How It Works
+============
+Patrole leverages oslo_policy (OpenStack's policy enforcement engine) to
+determine whether a given role is allowed to perform a policy action given a
+specific rule and OpenStack service. This is done before test execution inside
+the ``rbac_rule_validation.action`` decorator. Then, inside the test, the API
+that does policy enforcement for the same rule is called. The outcome is
+compared against the result from oslo_policy and a pass or fail is determined
+as outlined above: :ref:`test-flows`.
+
+.. note::
+
+ Currently, Patrole does not support checking multiple rules against a single
+ API call. Even though some APIs enforce multiple rules (some indirectly),
+ it is increasingly difficult to maintain the tests if multiple policy
+ actions are expected to be called.
+
+Test Execution Workflow
+-----------------------
+
+The workflow is as follows:
+
+#. Each test uses the ``rbac_rule_validation.action`` decorator, like below: ::
+
+ @rbac_rule_validation.action(
+ service="nova",
+ rule="os_compute_api:servers:stop")
+ @decorators.idempotent_id('ab4a17d2-166f-4a6d-9944-f17baa576cf2')
+ def test_stop_server(self):
+ # Set the primary credential's role to "rbac_test_role".
+ self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+ # Call the API that enforces the policy action specified by "rule".
+ self._test_stop_server()
+
+ The ``service`` attribute accepts an OpenStack service and the ``rule`` attribute
+ accepts a valid OpenStack policy action, like "os_compute_api:servers:stop".
+
+#. The ``rbac_rule_validation.action`` decorator passes these attributes,
+ along with user_id and project_id information derived from the primary
+ Tempest credential (``self.os.credentials.user_id`` and ``self.os.credentials.project_id``),
+ to the ``rbac_policy_parser``.
+
+#. The logic in ``rbac_policy_parser`` then passes all this information along
+ and the role in ``CONF.rbac.rbac_test_role`` to oslo_policy to determine whether
+ the ``rbac_test_role`` is authorized to perform the policy action for the given
+ service.
+
+#. After all of the logic above has executed inside the rbac decorator, the
+ test is executed. The test then sets up test-level resources, if necessary,
+ with **admin** credentials implicitly. This is accomplished through
+ ``rbac_utils.switch_role(toggle_rbac_role=False)``: ::
+
+ @classmethod
+ def setup_clients(cls):
+ super(BaseV2ComputeRbacTest, cls).setup_clients()
+ cls.auth_provider = cls.os.auth_provider
+ cls.rbac_utils = rbac_utils()
+ cls.rbac_utils.switch_role(cls, toggle_rbac_role=False)
+
+ This code has *already* executed when the test class is instantiated, because
+ it is located in the base rbac test class. Whenever ``cls.rbac_utils.switch_role``
+ is called, one of two behaviors are possible:
+
+ #. The primary credential's role is changed to admin if ``toggle_rbac_role=False``
+ #. The primary credential's role is changed to ``rbac_test_role`` if
+ ``toggle_rbac_role=True``
+
+ Thus, at the *beginning* of every test and during ``resource_setup`` and
+ ``resource_cleanup``, the primary credential has the admin role.
+
+#. After preliminary test-level setup is performed, like creating a server, a
+ second call to ``self.rbac_utils.switch_role`` is done: ::
+
+ self.rbac_utils.switch_role(cls, toggle_rbac_role=True)
+
+ Now the primary credential has the role specified by ``rbac_test_role``.
+
+#. The API endpoint in which policy enforcement of "os_compute_api:servers:stop"
+ is performed can now be called.
+
+ .. note:
+
+ To determine whether a policy action is enforced, refer to the relevant
+ controller code to make sure that the policy action is indeed enforced.
+
+#. Now that a call is made to "stop_server" with the primary credentials having
+ the role specified by ``rbac_test_role``, either the nova contoller will allow
+ or disallow the action to be performed. Since the "stop_server" policy action in
+ nova is defined as "base.RULE_ADMIN_OR_OWNER", the API will most likely
+ return a successful status code. For more information about this policy action,
+ see `<https://github.com/openstack/nova/blob/master/nova/policies/servers.py>`__.
+
+#. As mentioned above, the result from the API call and the result from oslo_policy
+ are compared for consistency.
+
+#. Finally, after the test has executed, but before ``tearDown`` or ``resource_cleanup``
+ is called, ``self.rbac_utils.switch_role(cls, toggle_rbac_role=False)`` is
+ called, so that the primary credential yet again has admin permissions for
+ test clean up. This call is always performed in the "finally" block inside
+ the ``rbac_rule_validation`` decorator.
+
+.. warning::
+
+ Failure to call ``self.rbac_utils.switch_role(cls, toggle_rbac_role=True)``
+ inside a test with the ``rbac_rule_validation`` decorator applied results
+ in a ``RbacResourceSetupFailed`` being raised, causing the test to fail.
diff --git a/contrib/post_test_hook.sh b/contrib/post_test_hook.sh
index 085b5b1..db48fc2 100644
--- a/contrib/post_test_hook.sh
+++ b/contrib/post_test_hook.sh
@@ -28,16 +28,12 @@
TEMPEST_COMMAND="sudo -H -u tempest tox"
DEVSTACK_GATE_TEMPEST_REGEX="(?!.*\[.*\bslow\b.*\])(^patrole_tempest_plugin\.tests\.api)"
+DEVSTACK_GATE_TEMPEST_HEAT_REGEX="(?!.*\[.*\bslow\b.*\])(^patrole_tempest_plugin\.tests\.api\.orchestration)"
DEVSTACK_MULTINODE_GATE_TEMPEST_REGEX="(?=.*\[.*\bslow\b.*\])(^patrole_tempest_plugin\.tests\.api)"
# Import devstack function 'iniset'.
source $BASE/new/devstack/functions
-# Use uuid tokens for faster test runs
-KEYSTONE_CONF=/etc/keystone/keystone.conf
-iniset $KEYSTONE_CONF token provider uuid
-sudo service apache2 restart
-
# First argument is expected to contain value equal either to 'admin' or
# 'member' (both lower-case).
RBAC_ROLE=$1
@@ -50,30 +46,47 @@
# environment is "multinode" or not (empty string).
TYPE=$2
-# Set enable_rbac=True under [rbac] section in tempest.conf
-iniset $TEMPEST_CONFIG rbac enable_rbac True
-# Set rbac_test_role=$RBAC_ROLE under [rbac] section in tempest.conf
-iniset $TEMPEST_CONFIG rbac rbac_test_role $RBAC_ROLE
-# Set strict_policy_check=False under [rbac] section in tempest.conf
-iniset $TEMPEST_CONFIG rbac strict_policy_check False
-# Set additional, necessary CONF values
-iniset $TEMPEST_CONFIG auth use_dynamic_credentials True
-iniset $TEMPEST_CONFIG auth tempest_roles Member
-iniset $TEMPEST_CONFIG identity auth_version v3
+function set_uuid_tokens() {
+ # Use uuid tokens for faster test runs
+ KEYSTONE_CONF=/etc/keystone/keystone.conf
+ iniset $KEYSTONE_CONF token provider uuid
+ sudo service apache2 restart
+}
-# Give permissions back to Tempest.
-sudo chown -R tempest:stack $BASE/new/tempest
-sudo chown -R tempest:stack $BASE/data/tempest
+function setup_config() {
+ # Set enable_rbac=True under [rbac] section in tempest.conf
+ iniset $TEMPEST_CONFIG rbac enable_rbac True
+ # Set rbac_test_role=$RBAC_ROLE under [rbac] section in tempest.conf
+ iniset $TEMPEST_CONFIG rbac rbac_test_role $RBAC_ROLE
+ # Set strict_policy_check=False under [rbac] section in tempest.conf
+ iniset $TEMPEST_CONFIG rbac strict_policy_check False
+ # Set additional, necessary CONF values
+ iniset $TEMPEST_CONFIG auth use_dynamic_credentials True
+ iniset $TEMPEST_CONFIG auth tempest_roles Member
+ iniset $TEMPEST_CONFIG identity auth_version v3
+}
-set -o errexit
+function run_tests() {
+ # Give permissions back to Tempest.
+ sudo chown -R tempest:stack $BASE/new/tempest
+ sudo chown -R tempest:stack $BASE/data/tempest
-# cd into Tempest directory before executing tox.
-cd $BASE/new/tempest
+ set -o errexit
-if [[ "$TYPE" == "multinode" ]]; then
- $TEMPEST_COMMAND -eall-plugin -- $DEVSTACK_MULTINODE_GATE_TEMPEST_REGEX --concurrency=$TEMPEST_CONCURRENCY
-else
- $TEMPEST_COMMAND -eall-plugin -- $DEVSTACK_GATE_TEMPEST_REGEX --concurrency=$TEMPEST_CONCURRENCY
-fi
+ # cd into Tempest directory before executing tox.
+ cd $BASE/new/tempest
-sudo -H -u tempest .tox/all-plugin/bin/tempest list-plugins
+ if [[ "$TYPE" == "multinode" ]]; then
+ $TEMPEST_COMMAND -eall-plugin -- $DEVSTACK_MULTINODE_GATE_TEMPEST_REGEX --concurrency=$TEMPEST_CONCURRENCY
+ elif [[ "$TYPE" == "heat" ]]; then
+ $TEMPEST_COMMAND -eall-plugin -- $DEVSTACK_GATE_TEMPEST_HEAT_REGEX --concurrency=$TEMPEST_CONCURRENCY
+ else
+ $TEMPEST_COMMAND -eall-plugin -- $DEVSTACK_GATE_TEMPEST_REGEX --concurrency=$TEMPEST_CONCURRENCY
+ fi
+
+ sudo -H -u tempest .tox/all-plugin/bin/tempest list-plugins
+}
+
+set_uuid_tokens
+setup_config
+run_tests
diff --git a/doc/source/index.rst b/doc/source/index.rst
index fbe592c..35e8439 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -3,7 +3,7 @@
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
-Welcome to patrole's documentation!
+Patrole - an OpenStack Tempest Plugin for RBAC Testing
========================================================
Contents:
@@ -22,4 +22,3 @@
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
-
diff --git a/doc/source/installation.rst b/doc/source/installation.rst
index 76064f1..b532d63 100644
--- a/doc/source/installation.rst
+++ b/doc/source/installation.rst
@@ -3,24 +3,24 @@
============
Installation Information
-########################
+========================
At the command line::
- $ pip install patrole
+ $ sudo pip install patrole
Or, if you have virtualenvwrapper installed::
$ mkvirtualenv patrole
- $ pip install patrole
+ $ sudo pip install patrole
Or to install from the source::
$ navigate to patrole directory
- $ pip install -e .
+ $ sudo pip install -e .
Configuration Information
-#########################
+=========================
tempest.conf
++++++++++++
@@ -28,25 +28,25 @@
To run the RBAC tempest api test, you have to make the following changes to
the tempest.conf file.
-#. [auth] section updates ::
+#. ``auth`` section updates ::
- # Allows test cases to create/destroy projects and users. This option
- # requires that OpenStack Identity API admin credentials are known. If
- # false, isolated test cases and parallel execution, can still be
- # achieved configuring a list of test accounts (boolean value)
- use_dynamic_credentials = True
+ # Allows test cases to create/destroy projects and users. This option
+ # requires that OpenStack Identity API admin credentials are known. If
+ # false, isolated test cases and parallel execution, can still be
+ # achieved configuring a list of test accounts (boolean value)
+ use_dynamic_credentials = True
-#. [rbac] section updates ::
+#. ``rbac`` section updates ::
- # The role that you want the RBAC tests to use for RBAC testing
- # This needs to be edited to run the test as a different role.
- rbac_test_role = _member_
+ # The role that you want the RBAC tests to use for RBAC testing
+ # This needs to be edited to run the test as a different role.
+ rbac_test_role = _member_
- # Enables RBAC Tempest tests if set to True. Otherwise, they are
- # skipped.
- enable_rbac = True
+ # Enables RBAC Tempest tests if set to True. Otherwise, they are
+ # skipped.
+ enable_rbac = True
- # If set to true, tests throw a RbacParsingException for policies
- # not found in the policy.json. Otherwise, they throw a
- # skipException.
- strict_policy_check = False
+ # If set to true, tests throw a RbacParsingException for policies
+ # not found in the policy.json. Otherwise, they throw a
+ # skipException.
+ strict_policy_check = False
diff --git a/doc/source/usage.rst b/doc/source/usage.rst
index 5d6c0b9..c2fc6d3 100644
--- a/doc/source/usage.rst
+++ b/doc/source/usage.rst
@@ -1,10 +1,36 @@
+..
+
========
Usage
========
-To use run patrole tests in Tempest::
+Running Patrole Tests in Tempest
+================================
- If patrole is installed correctly tests are run like any others.
+If Patrole is installed correctly, then the API tests can be executed
+from inside the tempest root directory as follows: ::
- To change the role that the patrole tests are being run as edit rbac_role
- in the rbac section of tempest.conf.
+ tox -eall-plugin -- patrole_tempest_plugin.tests.api
+
+To execute patrole tests for a specific module, run: ::
+
+ tox -eall-plugin -- patrole_tempest_plugin.tests.api.compute
+
+To change the role that the patrole tests are being run as, edit
+``rbac_test_role`` in the ``rbac`` section of tempest.conf: ::
+
+ [rbac]
+ rbac_test_role = Member
+ ...
+
+.. note::
+
+ The ``rbac_test_role`` is service-specific. Member, for example,
+ is an arbitrary role, but by convention is used to designate the default
+ non-admin role in the system. Most patrole tests should be run with
+ **admin** and **Member** roles. However, some services, like Heat, take
+ advantage of a role called **heat_stack_user**, as it appears frequently
+ in Heat's policy.json.
+
+For more information about the Member role,
+please see: `<https://ask.openstack.org/en/question/4759/member-vs-_member_/>`__.
diff --git a/patrole_tempest_plugin/README.rst b/patrole_tempest_plugin/README.rst
index b016b88..d678422 100644
--- a/patrole_tempest_plugin/README.rst
+++ b/patrole_tempest_plugin/README.rst
@@ -1,6 +1,5 @@
-===============================================
-Tempest Integration of patrole
-===============================================
+==============================
+Tempest Integration of Patrole
+==============================
-This directory contains Tempest tests to cover the patrole project.
-
+This directory contains Tempest tests to cover the Patrole project.
diff --git a/patrole_tempest_plugin/rbac_auth.py b/patrole_tempest_plugin/rbac_auth.py
index 7281969..ae497f3 100644
--- a/patrole_tempest_plugin/rbac_auth.py
+++ b/patrole_tempest_plugin/rbac_auth.py
@@ -27,9 +27,9 @@
class RbacAuthority(object):
- def __init__(self, tenant_id, user_id, service=None):
+ def __init__(self, project_id, user_id, service, extra_target_data):
self.policy_parser = rbac_policy_parser.RbacPolicyParser(
- tenant_id, user_id, service)
+ project_id, user_id, service, extra_target_data=extra_target_data)
def get_permission(self, rule_name, role):
try:
diff --git a/patrole_tempest_plugin/rbac_policy_parser.py b/patrole_tempest_plugin/rbac_policy_parser.py
index 38bed7c..2686736 100644
--- a/patrole_tempest_plugin/rbac_policy_parser.py
+++ b/patrole_tempest_plugin/rbac_policy_parser.py
@@ -40,7 +40,8 @@
each role, whether a given rule is allowed using oslo policy.
"""
- def __init__(self, tenant_id, user_id, service=None, path=None):
+ def __init__(self, project_id, user_id, service=None, path=None,
+ extra_target_data={}):
"""Initialization of Rbac Policy Parser.
Parses a policy file to create a dictionary, mapping policy actions to
@@ -57,7 +58,7 @@
the custom policy file over the default policy implementation is
prioritized.
- :param tenant_id: type uuid
+ :param project_id: type uuid
:param user_id: type uuid
:param service: type string
:param path: type string
@@ -78,8 +79,9 @@
self.path = path or os.path.join('/etc', service, 'policy.json')
self.rules = policy.Rules.load(self._get_policy_data(service),
'default')
- self.tenant_id = tenant_id
+ self.project_id = project_id
self.user_id = user_id
+ self.extra_target_data = extra_target_data
def allowed(self, rule_name, role):
is_admin_context = self._is_admin_context(role)
@@ -101,7 +103,7 @@
try:
file_policy_data = json.loads(file_policy_data)
except ValueError:
- pass
+ file_policy_data = None
# Check whether policy actions are defined in code. Nova and Keystone,
# for example, define their default policy actions in code.
@@ -165,8 +167,8 @@
"name": role
}
],
- "project_id": self.tenant_id,
- "tenant_id": self.tenant_id,
+ "project_id": self.project_id,
+ "tenant_id": self.project_id,
"user_id": self.user_id
}
}
@@ -200,6 +202,8 @@
"tenant_id": access_data['project_id'],
"network:tenant_id": access_data['project_id'],
"user_id": access_data['user_id']}
+ if self.extra_target_data:
+ target.update(self.extra_target_data)
result = self._try_rule(apply_rule, target, access_data, o)
return result
diff --git a/patrole_tempest_plugin/rbac_rule_validation.py b/patrole_tempest_plugin/rbac_rule_validation.py
index ec63119..6a5ed5e 100644
--- a/patrole_tempest_plugin/rbac_rule_validation.py
+++ b/patrole_tempest_plugin/rbac_rule_validation.py
@@ -29,7 +29,8 @@
LOG = logging.getLogger(__name__)
-def action(service, rule, admin_only=False, expected_error_code=403):
+def action(service, rule, admin_only=False, expected_error_code=403,
+ extra_target_data={}):
"""A decorator which does a policy check and matches it against test run.
A decorator which allows for positive and negative RBAC testing. Given
@@ -66,10 +67,10 @@
caller_ref = None
if args and isinstance(args[0], test.BaseTestCase):
caller_ref = args[0]
- tenant_id = caller_ref.auth_provider.credentials.tenant_id
+ project_id = caller_ref.auth_provider.credentials.project_id
user_id = caller_ref.auth_provider.credentials.user_id
except AttributeError as e:
- msg = ("{0}: tenant_id/user_id not found in "
+ msg = ("{0}: project_id/user_id not found in "
"cls.auth_provider.credentials".format(e))
LOG.error(msg)
raise rbac_exceptions.RbacResourceSetupFailed(msg)
@@ -80,10 +81,11 @@
"check for policy action {0}.".format(rule))
allowed = CONF.rbac.rbac_test_role == 'admin'
else:
- authority = rbac_auth.RbacAuthority(tenant_id, user_id,
- service)
- allowed = authority.get_permission(rule,
- CONF.rbac.rbac_test_role)
+ authority = rbac_auth.RbacAuthority(
+ project_id, user_id, service,
+ _format_extra_target_data(caller_ref, extra_target_data))
+ allowed = authority.get_permission(
+ rule, CONF.rbac.rbac_test_role)
expected_exception, irregular_msg = _get_exception_type(
expected_error_code)
@@ -146,3 +148,34 @@
raise rbac_exceptions.RbacInvalidErrorCode()
return expected_exception, irregular_msg
+
+
+def _format_extra_target_data(test_obj, extra_target_data):
+ """Formats the "extra_target_data" dictionary with correct test data.
+
+ Before being formatted, "extra_target_data" is a dictionary that maps a
+ policy string like "trust.trustor_user_id" to a nested list of BaseTestCase
+ attributes. For example, the attribute list in:
+
+ "trust.trustor_user_id": "os.auth_provider.credentials.user_id"
+
+ is parsed by iteratively calling `getattr` until the value of "user_id"
+ is resolved. The resulting dictionary returns:
+
+ "trust.trustor_user_id": "the user_id of the `primary` credential"
+
+ :param test_obj: type BaseTestCase (tempest base test class)
+ :param extra_target_data: dictionary with unresolved string literals that
+ reference nested BaseTestCase attributes
+ :returns: dictionary with resolved BaseTestCase attributes
+ """
+ attr_value = test_obj
+ formatted_target_data = {}
+
+ for user_attribute, attr_string in extra_target_data.items():
+ attrs = attr_string.split('.')
+ for attr in attrs:
+ attr_value = getattr(attr_value, attr)
+ formatted_target_data[user_attribute] = attr_value
+
+ return formatted_target_data
diff --git a/patrole_tempest_plugin/tests/api/compute/test_server_actions_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_server_actions_rbac.py
index a47db68..81266af 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_server_actions_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_server_actions_rbac.py
@@ -351,3 +351,32 @@
LOG.info("host_status attribute not returned when role doesn't "
"have permission to access it.")
raise rbac_exceptions.RbacActionFailed
+
+
+class ServerActionsV214RbacTest(rbac_base.BaseV2ComputeRbacTest):
+
+ min_microversion = '2.14'
+ max_microversion = 'latest'
+
+ @classmethod
+ def setup_clients(cls):
+ super(ServerActionsV214RbacTest, cls).setup_clients()
+ cls.client = cls.servers_client
+
+ @classmethod
+ def resource_setup(cls):
+ cls.set_validation_resources()
+ super(ServerActionsV214RbacTest, cls).resource_setup()
+ cls.server_id = cls.create_test_server(wait_until='ACTIVE')['id']
+
+ @rbac_rule_validation.action(
+ service="nova",
+ rule="os_compute_api:os-evacuate")
+ @decorators.idempotent_id('78ecef3c-faff-412a-83be-47651963eb21')
+ def test_evacuate_server(self):
+ self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+ self.assertRaisesRegex(lib_exc.NotFound,
+ "Compute host fake-host not found.",
+ self.client.evacuate_server,
+ self.server_id,
+ host='fake-host')
diff --git a/patrole_tempest_plugin/tests/api/compute/test_server_virtual_interfaces_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_server_virtual_interfaces_rbac.py
index 3e74a01..a0a000c 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_server_virtual_interfaces_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_server_virtual_interfaces_rbac.py
@@ -14,11 +14,9 @@
# under the License.
from tempest import config
-
from tempest.lib import decorators
from tempest.lib import exceptions
-from patrole_tempest_plugin import rbac_exceptions
from patrole_tempest_plugin import rbac_rule_validation
from patrole_tempest_plugin.tests.api.compute import rbac_base as base
@@ -39,13 +37,10 @@
def test_list_virtual_interfaces(self):
server = self.create_test_server(wait_until='ACTIVE')
self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- try:
- self.client.list_virtual_interfaces(server['id'])
- except exceptions.ServerFault as e:
- raise rbac_exceptions.RbacActionFailed(e)
- except exceptions.BadRequest as e:
+
+ if CONF.service_available.neutron:
msg = "Listing virtual interfaces is not supported by this cloud."
- if msg == str(e.resp_body['message']):
- raise self.skipException(msg)
- else:
- raise e
+ with self.assertRaisesRegex(exceptions.BadRequest, msg):
+ self.client.list_virtual_interfaces(server['id'])
+ else:
+ self.client.list_virtual_interfaces(server['id'])
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/rbac_base.py b/patrole_tempest_plugin/tests/api/identity/v3/rbac_base.py
index a1cdf4c..31533a3 100644
--- a/patrole_tempest_plugin/tests/api/identity/v3/rbac_base.py
+++ b/patrole_tempest_plugin/tests/api/identity/v3/rbac_base.py
@@ -53,6 +53,7 @@
cls.role_assignments_client = cls.os.role_assignments_client
cls.roles_client = cls.os.roles_v3_client
cls.services_client = cls.os.identity_services_v3_client
+ cls.trusts_client = cls.os.trusts_client
cls.users_client = cls.os.users_v3_client
@classmethod
@@ -67,6 +68,7 @@
cls.regions = []
cls.roles = []
cls.services = []
+ cls.trusts = []
cls.users = []
@classmethod
@@ -111,6 +113,10 @@
test_utils.call_and_ignore_notfound_exc(
cls.services_client.delete_service, service['id'])
+ for trust in cls.trusts:
+ test_utils.call_and_ignore_notfound_exc(
+ cls.trusts_client.delete_trust, trust['id'])
+
for user in cls.users:
test_utils.call_and_ignore_notfound_exc(
cls.users_client.delete_user, user['id'])
@@ -229,6 +235,16 @@
return service
@classmethod
+ def setup_test_trust(cls, trustee_user_id, trustor_user_id, **kwargs):
+ """Setup a test trust."""
+ trust = cls.trusts_client.create_trust(
+ trustee_user_id=trustee_user_id, trustor_user_id=trustor_user_id,
+ impersonation=False, **kwargs)['trust']
+ cls.trusts.append(trust)
+
+ return trust
+
+ @classmethod
def setup_test_user(cls, password=None, **kwargs):
"""Set up a test user."""
username = data_utils.rand_name('test_user')
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/test_trusts_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_trusts_rbac.py
new file mode 100644
index 0000000..622b330
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_trusts_rbac.py
@@ -0,0 +1,130 @@
+# Copyright 2017 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 import config
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+from tempest import test
+
+from patrole_tempest_plugin import rbac_rule_validation
+from patrole_tempest_plugin.tests.api.identity.v3 import rbac_base
+
+CONF = config.CONF
+
+
+class IdentityTrustV3RbacTest(rbac_base.BaseIdentityV3RbacTest):
+
+ credentials = ['primary', 'admin', 'alt']
+
+ @classmethod
+ def skip_checks(cls):
+ super(IdentityTrustV3RbacTest, cls).skip_checks()
+ if not CONF.identity_feature_enabled.trust:
+ raise cls.skipException(
+ "%s skipped as trust feature isn't enabled" % cls.__name__)
+
+ @classmethod
+ def resource_setup(cls):
+ super(IdentityTrustV3RbacTest, cls).resource_setup()
+ # Use the primary user's credentials for the "trustor_user_id", since
+ # user_id:%(trust.trustor_user_id)s will thereby evaluate to
+ # "primary user's user_id:primary user's user_id" which evaluates to
+ # true.
+ cls.trustor_user_id = cls.auth_provider.credentials.user_id
+ cls.trustor_project_id = cls.auth_provider.credentials.project_id
+ cls.trustee_user_id = cls.setup_test_user()['id']
+
+ # The "unauthorized_user_id" does not have permissions to create a
+ # trust because the user_id in "user_id:%(trust.trustor_user_id)s" (the
+ # policy rule for creating a trust) corresponds to the primary user_id
+ # not the alt user_id.
+ cls.unauthorized_user_id = cls.os_alt.auth_provider.credentials.user_id
+
+ # A role is guaranteed to exist (namely the admin role), because
+ # "trustor_user_id" and "trustor_project_id" are the primary tempest
+ # user and project, respectively.
+ cls.delegated_role_id = cls.roles_client.list_user_roles_on_project(
+ cls.trustor_project_id, cls.trustor_user_id)['roles'][0]['id']
+
+ cls.trust = cls.setup_test_trust(trustor_user_id=cls.trustor_user_id,
+ trustee_user_id=cls.trustee_user_id,
+ project_id=cls.trustor_project_id,
+ roles=[{'id': cls.delegated_role_id}])
+
+ @decorators.idempotent_id('7ab595a7-9b71-45fe-91d8-2793b0292f72')
+ @rbac_rule_validation.action(
+ service="keystone",
+ rule="identity:create_trust",
+ extra_target_data={
+ "trust.trustor_user_id": "os.auth_provider.credentials.user_id"
+ })
+ def test_create_trust(self):
+ self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+ self.setup_test_trust(trustor_user_id=self.trustor_user_id,
+ trustee_user_id=self.trustee_user_id)
+
+ @decorators.idempotent_id('bd72d22a-6e11-4840-bd93-17b382e7f0e0')
+ @test.attr(type=['negative'])
+ @rbac_rule_validation.action(
+ service="keystone",
+ rule="identity:create_trust",
+ extra_target_data={
+ "trust.trustor_user_id": "os_alt.auth_provider.credentials.user_id"
+ })
+ def test_create_trust_negative(self):
+ # Explicit negative test for identity:create_trust policy action.
+ # Assert expected exception is Forbidden and then reraise it.
+ self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+ e = self.assertRaises(lib_exc.Forbidden, self.setup_test_trust,
+ trustor_user_id=self.unauthorized_user_id,
+ trustee_user_id=self.trustee_user_id)
+ raise e
+
+ @decorators.idempotent_id('d9a6fd06-08f6-462c-a86c-ce009adf1230')
+ @rbac_rule_validation.action(
+ service="keystone",
+ rule="identity:delete_trust")
+ def test_delete_trust(self):
+ trust = self.setup_test_trust(trustor_user_id=self.trustor_user_id,
+ trustee_user_id=self.trustee_user_id)
+
+ self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+ self.trusts_client.delete_trust(trust['id'])
+
+ @decorators.idempotent_id('f2e32896-bf66-4f4e-89cf-e7fba0ef1f38')
+ @rbac_rule_validation.action(
+ service="keystone",
+ rule="identity:list_trusts")
+ def test_list_trusts(self):
+ self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+ self.trusts_client.list_trusts(
+ trustor_user_id=self.trustor_user_id)['trusts']
+
+ @decorators.idempotent_id('3c9ff92f-a73e-4f9b-8865-e017f38c70f5')
+ @rbac_rule_validation.action(
+ service="keystone",
+ rule="identity:list_roles_for_trust")
+ def test_list_roles_for_trust(self):
+ self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+ self.trusts_client.list_trust_roles(self.trust['id'])['roles']
+
+ @decorators.idempotent_id('3bb4f97b-cecd-4c7d-ad10-b88ee6c5d573')
+ @rbac_rule_validation.action(
+ service="keystone",
+ rule="identity:get_role_for_trust")
+ def test_show_trust_role(self):
+ self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+ self.trusts_client.show_trust_role(
+ self.trust['id'], self.delegated_role_id)['role']
diff --git a/patrole_tempest_plugin/tests/api/image/v2/test_images_rbac.py b/patrole_tempest_plugin/tests/api/image/v2/test_images_rbac.py
index fa492bb..e7fe87f 100644
--- a/patrole_tempest_plugin/tests/api/image/v2/test_images_rbac.py
+++ b/patrole_tempest_plugin/tests/api/image/v2/test_images_rbac.py
@@ -29,6 +29,18 @@
super(BasicOperationsImagesRbacTest, cls).setup_clients()
cls.client = cls.os.image_client_v2
+ def _create_image(self, **kwargs):
+ image_name = data_utils.rand_name('image')
+ image = self.create_image(name=image_name,
+ container_format='bare',
+ disk_format='raw',
+ **kwargs)
+ return image
+
+ def _upload_image(self, image_id):
+ image_file = moves.cStringIO(data_utils.random_bytes())
+ return self.client.store_image_file(image_id, image_file)
+
@rbac_rule_validation.action(service="glance",
rule="add_image")
@decorators.idempotent_id('0f148510-63bf-11e6-b348-080027d0d606')
@@ -38,14 +50,8 @@
RBAC test for the glance create_image endpoint
"""
- uuid = '00000000-1111-2222-3333-444455556666'
- image_name = data_utils.rand_name('image')
self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- self.create_image(name=image_name,
- container_format='bare',
- disk_format='raw',
- visibility='private',
- ramdisk_id=uuid)
+ self._create_image()
@rbac_rule_validation.action(service="glance",
rule="upload_image")
@@ -56,18 +62,25 @@
RBAC test for the glance upload_image endpoint
"""
- uuid = '00000000-1111-2222-3333-444455556666'
- image_name = data_utils.rand_name('image')
- body = self.create_image(name=image_name,
- container_format='bare',
- disk_format='raw',
- visibility='private',
- ramdisk_id=uuid)
+ image = self._create_image()
self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- # Try uploading an image file
- image_file = moves.cStringIO(data_utils.random_bytes())
- self.client.store_image_file(body['id'], image_file)
+ self._upload_image(image['id'])
+
+ @decorators.idempotent_id('f0c268f3-cb51-49aa-9bd5-d30cf647322f')
+ @rbac_rule_validation.action(service="glance",
+ rule="download_image")
+ def test_download_image(self):
+
+ """Download Image Test
+
+ RBAC test for the glance download_image endpoint
+ """
+ image = self._create_image()
+ self._upload_image(image['id'])
+
+ self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+ self.client.show_image_file(image['id'])
@rbac_rule_validation.action(service="glance",
rule="delete_image")
@@ -78,16 +91,11 @@
RBAC test for the glance delete_image endpoint
"""
- image_name = data_utils.rand_name('image')
- body = self.create_image(name=image_name,
- container_format='bare',
- disk_format='raw',
- visibility='public')
- image_id = body.get('id')
- # Toggle role and delete created image
+ image = self._create_image()
+
self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- self.client.delete_image(image_id)
- self.client.wait_for_resource_deletion(image_id)
+ self.client.delete_image(image['id'])
+ self.client.wait_for_resource_deletion(image['id'])
@rbac_rule_validation.action(service="glance",
rule="get_image")
@@ -98,16 +106,10 @@
RBAC test for the glance create_image endpoint
"""
+ image = self._create_image()
- image_name = data_utils.rand_name('image')
- body = self.create_image(name=image_name,
- container_format='bare',
- disk_format='raw',
- visibility='private')
- image_id = body.get('id')
- # Toggle role and get created image
self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- self.client.show_image(image_id)
+ self.client.show_image(image['id'])
@rbac_rule_validation.action(service="glance",
rule="get_images")
@@ -118,10 +120,8 @@
RBAC test for the glance list_images endpoint
"""
-
- # Toggle role and get created image
self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- self.client.list_images()
+ self.client.list_images()['images']
@rbac_rule_validation.action(service="glance",
rule="modify_image")
@@ -132,22 +132,42 @@
RBAC test for the glance update_image endpoint
"""
- image_name = data_utils.rand_name('image')
- body = self.create_image(name=image_name,
- container_format='bare',
- disk_format='raw',
- visibility='private')
- image_id = body.get('id')
+ image = self._create_image()
- # Now try uploading an image file
- image_file = moves.cStringIO(data_utils.random_bytes())
- self.client.store_image_file(image_id, image_file)
-
- # Toggle role and update created image
self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- new_image_name = data_utils.rand_name('new-image')
- body = self.client.update_image(image_id, [
- dict(replace='/name', value=new_image_name)])
+ updated_image_name = data_utils.rand_name('image')
+ self.client.update_image(image['id'], [
+ dict(replace='/name', value=updated_image_name)])
+
+ @decorators.idempotent_id('244050d9-1b9a-446a-b3c5-f26f3ba8eb75')
+ @rbac_rule_validation.action(service="glance",
+ rule="modify_image")
+ def test_create_image_tag(self):
+
+ """Create image tag
+
+ RBAC test for the glance add_image_tag endpoint
+ """
+ image = self._create_image()
+
+ self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+ self.client.add_image_tag(image['id'], data_utils.rand_name('tag'))
+
+ @decorators.idempotent_id('c4a0bf9c-b78b-48c6-a31f-72c95f943c6e')
+ @rbac_rule_validation.action(service="glance",
+ rule="modify_image")
+ def test_delete_image_tag(self):
+
+ """Delete image tag
+
+ RBAC test for the glance delete_image_tag endpoint
+ """
+ image = self._create_image()
+ tag_name = data_utils.rand_name('tag')
+ self.client.add_image_tag(image['id'], tag_name)
+
+ self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+ self.client.delete_image_tag(image['id'], tag_name)
@rbac_rule_validation.action(service="glance",
rule="publicize_image")
@@ -158,12 +178,8 @@
RBAC test for the glance publicize_image endpoint
"""
- image_name = data_utils.rand_name('image')
self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- self.create_image(name=image_name,
- container_format='bare',
- disk_format='raw',
- visibility='public')
+ self._create_image(visibility='public')
@rbac_rule_validation.action(service="glance",
rule="deactivate")
@@ -174,20 +190,11 @@
RBAC test for the glance deactivate_image endpoint
"""
- uuid = '00000000-1111-2222-3333-444455556666'
- image_name = data_utils.rand_name('image')
- body = self.create_image(name=image_name,
- container_format='bare',
- disk_format='raw',
- visibility='private',
- ramdisk_id=uuid)
- image_id = body.get('id')
- # Now try uploading an image file
- image_file = moves.cStringIO(data_utils.random_bytes())
- self.client.store_image_file(image_id=image_id, data=image_file)
- # Toggling role and deacivate image
+ image = self._create_image()
+ self._upload_image(image['id'])
+
self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- self.client.deactivate_image(image_id)
+ self.client.deactivate_image(image['id'])
@rbac_rule_validation.action(service="glance",
rule="reactivate")
@@ -198,18 +205,8 @@
RBAC test for the glance reactivate_image endpoint
"""
- uuid = '00000000-1111-2222-3333-444455556666'
- image_name = data_utils.rand_name('image')
- body = self.create_image(name=image_name,
- container_format='bare',
- disk_format='raw',
- visibility='private',
- ramdisk_id=uuid)
+ image = self._create_image()
+ self._upload_image(image['id'])
- # Now try uploading an image file
- image_id = body.get('id')
- image_file = moves.cStringIO(data_utils.random_bytes())
- self.client.store_image_file(image_id=image_id, data=image_file)
- # Toggling role and reactivate image
self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- self.client.reactivate_image(image_id)
+ self.client.reactivate_image(image['id'])
diff --git a/patrole_tempest_plugin/tests/api/volume/rbac_base.py b/patrole_tempest_plugin/tests/api/volume/rbac_base.py
index a2d5345..79b9f0d 100644
--- a/patrole_tempest_plugin/tests/api/volume/rbac_base.py
+++ b/patrole_tempest_plugin/tests/api/volume/rbac_base.py
@@ -13,6 +13,8 @@
from tempest.api.volume import base as vol_base
from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
from patrole_tempest_plugin.rbac_utils import rbac_utils
@@ -45,3 +47,32 @@
}
cls.volume_hosts_client, cls.volume_types_client = \
version_checker[cls._api_version]
+
+ @classmethod
+ def resource_setup(cls):
+ super(BaseVolumeRbacTest, cls).resource_setup()
+ cls.volume_types = []
+
+ @classmethod
+ def resource_cleanup(cls):
+ super(BaseVolumeRbacTest, cls).resource_cleanup()
+ cls.clear_volume_types()
+
+ @classmethod
+ def create_volume_type(cls, name=None, **kwargs):
+ """Create a test volume-type"""
+ name = name or data_utils.rand_name(cls.__name__ + '-volume-type')
+ volume_type = cls.volume_types_client.create_volume_type(
+ name=name, **kwargs)['volume_type']
+ cls.volume_types.append(volume_type['id'])
+ return volume_type
+
+ @classmethod
+ def clear_volume_types(cls):
+ for vol_type in cls.volume_types:
+ test_utils.call_and_ignore_notfound_exc(
+ cls.volume_types_client.delete_volume_type, vol_type)
+
+ for vol_type in cls.volume_types:
+ test_utils.call_and_ignore_notfound_exc(
+ cls.volume_types_client.wait_for_resource_deletion, vol_type)
diff --git a/patrole_tempest_plugin/tests/api/volume/test_user_messages_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_user_messages_rbac.py
new file mode 100644
index 0000000..b1e5cba
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/volume/test_user_messages_rbac.py
@@ -0,0 +1,91 @@
+# Copyright 2017 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 import config
+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.volume import rbac_base
+
+CONF = config.CONF
+
+
+class MessagesV3RbacTest(rbac_base.BaseVolumeRbacTest):
+ _api_version = 3
+ min_microversion = '3.3'
+ max_microversion = 'latest'
+
+ @classmethod
+ def setup_clients(cls):
+ super(MessagesV3RbacTest, cls).setup_clients()
+ cls.client = cls.os.volume_v3_messages_client
+
+ def _create_user_message(self):
+ """Trigger a 'no valid host' situation to generate a message."""
+ bad_protocol = data_utils.rand_name('storage_protocol')
+ bad_vendor = data_utils.rand_name('vendor_name')
+ extra_specs = {'storage_protocol': bad_protocol,
+ 'vendor_name': bad_vendor}
+ vol_type_name = data_utils.rand_name(
+ self.__class__.__name__ + '-volume-type')
+ bogus_type = self.create_volume_type(
+ name=vol_type_name, extra_specs=extra_specs)
+ params = {'volume_type': bogus_type['id'],
+ 'size': CONF.volume.volume_size}
+ volume = self.create_volume(wait_until="error", **params)
+ messages = self.messages_client.list_messages()['messages']
+ message_id = None
+ for message in messages:
+ if message['resource_uuid'] == volume['id']:
+ message_id = message['id']
+ break
+ self.assertIsNotNone(message_id, 'No user message generated for '
+ 'volume %s' % volume['id'])
+
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.client.delete_message, message_id)
+
+ return message_id
+
+ @decorators.idempotent_id('bf7f31a1-509b-4a7d-a8a8-ad6ce68229c7')
+ @rbac_rule_validation.action(
+ service="cinder",
+ rule="message:get_all")
+ def test_list_messages(self):
+ self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+ self.client.list_messages()['messages']
+
+ @decorators.idempotent_id('9cc1ad1e-68a2-4407-8b60-ea77909bce08')
+ @rbac_rule_validation.action(
+ service="cinder",
+ rule="message:get")
+ def test_show_message(self):
+ message_id = self._create_user_message()
+
+ self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+ self.client.show_message(message_id)['message']
+
+ @decorators.idempotent_id('65ca7fb7-7f2c-443e-b144-ac86973a97be')
+ @rbac_rule_validation.action(
+ service="cinder",
+ rule="message:delete")
+ def test_delete_message(self):
+ message_id = self._create_user_message()
+
+ self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+ self.client.delete_message(message_id)
+ self.client.wait_for_resource_deletion(message_id)
diff --git a/patrole_tempest_plugin/tests/unit/test_rbac_policy_parser.py b/patrole_tempest_plugin/tests/unit/test_rbac_policy_parser.py
index b0dd179..0906222 100644
--- a/patrole_tempest_plugin/tests/unit/test_rbac_policy_parser.py
+++ b/patrole_tempest_plugin/tests/unit/test_rbac_policy_parser.py
@@ -384,3 +384,89 @@
}
self.assertEqual(expected_policy_data, actual_policy_data)
+
+ @mock.patch.object(rbac_policy_parser, 'credentials', autospec=True)
+ @mock.patch.object(rbac_policy_parser, 'stevedore', autospec=True)
+ def test_get_policy_data_cannot_find_policy(self, mock_stevedore,
+ mock_creds):
+ mock_stevedore.named.NamedExtensionManager.return_value = None
+ mock_creds.AdminManager.return_value.identity_services_v3_client.\
+ list_services.return_value = {
+ 'services': [{'name': 'test_service'}]}
+
+ e = self.assertRaises(rbac_exceptions.RbacParsingException,
+ rbac_policy_parser.RbacPolicyParser,
+ None, None, 'test_service', None)
+
+ expected_error = \
+ 'Policy file for {0} service neither found in code '\
+ 'nor at {1}.'.format('test_service',
+ '/etc/test_service/policy.json')
+
+ self.assertIn(expected_error, str(e))
+
+ @mock.patch.object(rbac_policy_parser, 'os', autospec=True)
+ @mock.patch.object(rbac_policy_parser, 'json', autospec=True)
+ @mock.patch.object(rbac_policy_parser, 'credentials', autospec=True)
+ @mock.patch.object(rbac_policy_parser, 'stevedore', autospec=True)
+ def test_get_policy_data_without_valid_policy(self, mock_stevedore,
+ mock_credentials, mock_json,
+ mock_os):
+ mock_os.path.isfile.return_value = False
+
+ test_policy_action = mock.Mock(check='rule:bar')
+ test_policy_action.configure_mock(name='foo')
+
+ test_policy = mock.Mock(obj=[test_policy_action])
+ test_policy.configure_mock(name='test_service')
+
+ mock_stevedore.named.NamedExtensionManager\
+ .return_value = [test_policy]
+
+ mock_credentials.AdminManager.return_value.identity_services_v3_client.\
+ list_services.return_value = {
+ 'services': [{'name': 'test_service'}]
+ }
+
+ mock_json.dumps.side_effect = ValueError
+
+ e = self.assertRaises(rbac_exceptions.RbacParsingException,
+ rbac_policy_parser.RbacPolicyParser,
+ None, None, 'test_service', None)
+
+ expected_error = "Policy file for {0} service is invalid."\
+ .format("test_service")
+
+ self.assertIn(expected_error, str(e))
+
+ mock_stevedore.named.NamedExtensionManager.assert_called_once_with(
+ 'oslo.policy.policies',
+ names=['test_service'],
+ on_load_failure_callback=None,
+ invoke_on_load=True,
+ warn_on_missing_entrypoint=False)
+
+ @mock.patch.object(rbac_policy_parser, 'json', autospec=True)
+ @mock.patch.object(rbac_policy_parser, 'credentials', autospec=True)
+ @mock.patch.object(rbac_policy_parser, 'stevedore', autospec=True)
+ def test_get_policy_data_from_file_not_json(self, mock_stevedore,
+ mock_credentials,
+ mock_json):
+
+ mock_credentials.AdminManager.return_value.identity_services_v3_client.\
+ list_services.return_value = {
+ 'services': [{'name': 'test_service'}]
+ }
+ mock_stevedore.named.NamedExtensionManager.return_value = None
+ mock_json.loads.side_effect = ValueError
+
+ e = self.assertRaises(rbac_exceptions.RbacParsingException,
+ rbac_policy_parser.RbacPolicyParser,
+ None, None, 'test_service',
+ self.tenant_policy_file)
+
+ expected_error = 'Policy file for {0} service neither found in code '\
+ 'nor at {1}.'.format('test_service',
+ self.tenant_policy_file)
+
+ self.assertIn(expected_error, str(e))
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 78d8e66..174945e 100644
--- a/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py
+++ b/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py
@@ -33,8 +33,8 @@
self.mock_args = mock.Mock(spec=test.BaseTestCase)
self.mock_args.auth_provider = mock.Mock()
self.mock_args.rbac_utils = mock.Mock()
- self.mock_args.auth_provider.credentials.tenant_id = \
- mock.sentinel.tenant_id
+ self.mock_args.auth_provider.credentials.project_id = \
+ mock.sentinel.project_id
self.mock_args.auth_provider.credentials.user_id = \
mock.sentinel.user_id
@@ -292,8 +292,8 @@
str(e))
mock_rbac_policy_parser.RbacPolicyParser.assert_called_once_with(
- mock.sentinel.tenant_id, mock.sentinel.user_id,
- mock.sentinel.service)
+ mock.sentinel.project_id, mock.sentinel.user_id,
+ mock.sentinel.service, extra_target_data={})
@mock.patch.object(rbac_auth, 'RbacAuthority', autospec=True)
def test_get_exception_type_404(self, mock_auth):
diff --git a/tox.ini b/tox.ini
index e123d64..a004c6e 100644
--- a/tox.ini
+++ b/tox.ini
@@ -28,6 +28,10 @@
commands = {posargs}
[testenv:cover]
+commands = rm -rf *.pyc
+ rm -rf cover
+ rm -f .coverage
+ nosetests {posargs}
setenv = VIRTUAL_ENV={envdir}
NOSE_WITH_COVERAGE=1
NOSE_COVER_BRANCHES=1
@@ -36,7 +40,7 @@
NOSE_COVER_HTML_DIR={toxinidir}/cover
NOSE_WHERE=patrole_tempest_plugin/tests/unit
whitelist_externals = nosetests
-commands = nosetests {posargs}
+ rm
[testenv:docs]
commands = python setup.py build_sphinx