Merge "Remove duplicate subnet tests fromt test_networks_rbac module"
diff --git a/doc/source/framework/policy_authority.rst b/doc/source/framework/policy_authority.rst
index 7cd4421..822c7b6 100644
--- a/doc/source/framework/policy_authority.rst
+++ b/doc/source/framework/policy_authority.rst
@@ -9,7 +9,8 @@
This module is only called for calculating the "Expected" result if
``[patrole] test_custom_requirements`` is ``False``.
-Using the Policy Authority Module, policy verification is performed by:
+Using the :class:`~patrole_tempest_plugin.policy_authority.PolicyAuthority`
+class, policy verification is performed by:
#. Pooling together the default `in-code` policy rules.
#. Overriding the defaults with custom policy rules located in a policy.json,
@@ -22,9 +23,40 @@
#. Performing a call with all necessary data to ``oslo.policy`` and returning
the expected result back to ``rbac_rule_validation`` decorator.
+When to use
+-----------
+
+This :class:`~patrole_tempest_plugin.rbac_authority.RbacAuthority` class
+can be used to validate the default OpenStack policy configuration. It
+is recommended that this approach be used for RBAC validation for clouds that
+use little to no policy customizations or overrides.
+
+This validation approach should be used when:
+
+* Validating the out-of-the-box policy-in-code OpenStack policy configuration.
+
+ It is important that the default OpenStack policy configuration be validated
+ before deploying OpenStack into production. Bugs exist in software and the
+ earlier they can be caught and prevented (via CI/CD, for example), the
+ better. Patrole continues to be used to identify default policy bugs
+ across OpenStack services.
+
+* Validating policy reliably and accurately.
+
+ Relying on ``oslo.policy`` to compute the expected test results provides
+ accurate tests, without the hassle of having to reinvent the wheel. Since
+ OpenStack APIs use ``oslo.policy`` for policy enforcement, it makes sense
+ to compute expected results by using the same library, ensuring test
+ reliability.
+
+* Continuously validating policy changes to OpenStack projects under
+ development by gating them against Patrole CI/CD jobs run by `Zuul`_.
+
+.. _Zuul: https://docs.openstack.org/infra/zuul/
+
Implementation
--------------
.. automodule:: patrole_tempest_plugin.policy_authority
:members:
- :special-members:
+ :undoc-members:
diff --git a/doc/source/framework/rbac_authority.rst b/doc/source/framework/rbac_authority.rst
new file mode 100644
index 0000000..84c372b
--- /dev/null
+++ b/doc/source/framework/rbac_authority.rst
@@ -0,0 +1,37 @@
+.. rbac-authority:
+
+RBAC Authority Module
+=====================
+
+Overview
+--------
+
+This module implements an abstract class that is implemented by the classes
+below. Each implementation is used by the :ref:`rbac-validation` framework
+to determine each expected test result.
+
+:ref:`policy-authority`
+-----------------------
+
+The *default* :class:`~patrole_tempest_plugin.rbac_authority.RbacAuthority`
+implementation class which is used for policy validation. Uses ``oslo.policy``
+to determine the expected test result.
+
+All Patrole `Zuul`_ gates use this
+:class:`~patrole_tempest_plugin.rbac_authority.RbacAuthority` class by default.
+
+.. _Zuul: https://docs.openstack.org/infra/zuul/
+
+:ref:`requirements-authority`
+-----------------------------
+
+Optional :class:`~patrole_tempest_plugin.rbac_authority.RbacAuthority`
+implementation class which is used for policy validation. It uses a high-level
+requirements-driven approach to validating RBAC in Patrole.
+
+Implementation
+--------------
+
+.. automodule:: patrole_tempest_plugin.rbac_authority
+ :members:
+ :undoc-members:
diff --git a/doc/source/framework/requirements_authority.rst b/doc/source/framework/requirements_authority.rst
new file mode 100644
index 0000000..6c4fcc0
--- /dev/null
+++ b/doc/source/framework/requirements_authority.rst
@@ -0,0 +1,105 @@
+.. _requirements-authority:
+
+Requirements Authority Module
+=============================
+
+Overview
+--------
+
+Requirements-driven approach to declaring the expected RBAC test results
+referenced by Patrole. Uses a high-level YAML syntax to crystallize policy
+requirements concisely and unambiguously.
+
+.. note::
+
+ The :ref:`custom-requirements-file` is required to use this validation
+ approach and, currently, must be manually generated.
+
+This validation approach can be toggled on by setting the
+``[patrole].test_custom_requirements`` configuration option to ``True``;
+see :ref:`patrole-configuration` for more information.
+
+When to use
+-----------
+
+This :class:`~patrole_tempest_plugin.rbac_authority.RbacAuthority` class
+can be used to achieve a requirements-driven approach to validating an
+OpenStack cloud's RBAC implementation. Using this approach, Patrole computes
+expected test results by performing lookups against a
+:ref:`custom-requirements-file` which precisely defines the cloud's RBAC
+requirements.
+
+Using a high-level declarative language, the requirements are captured
+unambiguously in the :ref:`custom-requirements-file`, allowing operators to
+validate their requirements against their OpenStack cloud.
+
+This validation approach should be used when:
+
+* The cloud has heavily customized policy files that require careful validation
+ against one's requirements.
+
+ Heavily customized policy files can contain relatively nuanced/technical
+ syntax that impinges upon the goal of using a clear and concise syntax
+ present in the :ref:`custom-requirements-file` to drive RBAC validation.
+
+* The cloud has non-OpenStack services that require RBAC validation but which
+ don't leverage the ``oslo.policy`` framework.
+
+ Services like `Contrail`_ that are present in an OpenStack-based cloud that
+ interface with OpenStack services like Neutron also require RBAC validation.
+ The requirements-driven approach to RBAC validation is framework-agnostic
+ and so can work with any policy engine.
+
+* Expected results are captured as clear-cut, unambiguous requirements.
+
+ Validating a cloud's RBAC against high-level, clear-cut requirements is
+ a valid use case. Relying on ``oslo.policy`` validating customized policy
+ files is not sufficient to satisfy this use case.
+
+As mentioned above, the trade-off with this approach is having to manually
+generate the :ref:`custom-requirements-file`. There is currently no
+tooling to automatically do this.
+
+.. _Contrail: https://github.com/Juniper/contrail-controller/wiki/RBAC
+
+.. _custom-requirements-file:
+
+Custom Requirements File
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+File path of the YAML file that defines your RBAC requirements. This
+file must be located on the same host that Patrole runs on. The YAML
+file should be written as follows:
+
+.. code-block:: yaml
+
+ <service_foo>:
+ <api_action_a>:
+ - <allowed_role_1>
+ - <allowed_role_2>
+ - <allowed_role_3>
+ <api_action_b>:
+ - <allowed_role_2>
+ - <allowed_role_4>
+ <service_bar>:
+ <api_action_c>:
+ - <allowed_role_3>
+
+Where:
+
+service = the service that is being tested (Cinder, Nova, etc.).
+
+api_action = the policy action that is being tested. Examples:
+
+* volume:create
+* os_compute_api:servers:start
+* add_image
+
+allowed_role = the ``oslo.policy`` role that is allowed to perform the API.
+
+Implementation
+--------------
+
+.. automodule:: patrole_tempest_plugin.requirements_authority
+ :members:
+ :undoc-members:
diff --git a/doc/source/index.rst b/doc/source/index.rst
index d964845..8368262 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -47,7 +47,9 @@
framework/overview
framework/rbac_validation
+ framework/rbac_authority
framework/policy_authority
+ framework/requirements_authority
framework/rbac_utils
Indices and tables
diff --git a/etc/patrole.conf.sample b/etc/patrole.conf.sample
index 518d38a..8e7931b 100644
--- a/etc/patrole.conf.sample
+++ b/etc/patrole.conf.sample
@@ -29,9 +29,10 @@
#
# This option determines whether Patrole should run against a
# ``custom_requirements_file`` which defines RBAC requirements. The
-# purpose of setting this flag to True is to verify that RBAC policy
+# purpose of setting this flag to ``True`` is to verify that RBAC
+# policy
# is in accordance to requirements. The idea is that the
-# ``custom_requirements_file`` perfectly defines what the RBAC
+# ``custom_requirements_file`` precisely defines what the RBAC
# requirements are.
#
# Here are the possible outcomes when running the Patrole tests
@@ -57,28 +58,32 @@
# file must be located on the same host that Patrole runs on. The YAML
# file should be written as follows:
#
-# ```
-# <service_foo>:
-# <api_action_x>:
-# - <allowed_role_a>
-# - <allowed_role_b>
-# - <allowed_role_c>
-# <api_action_y>:
-# - <allowed_role_d>
-# - <allowed_role_e>
-# <service_bar>:
-# <api_action_z>:
-# - <allowed_role_b>
-# ```
+# .. code-block:: yaml
+#
+# <service_foo>:
+# <api_action_a>:
+# - <allowed_role_1>
+# - <allowed_role_2>
+# - <allowed_role_3>
+# <api_action_b>:
+# - <allowed_role_2>
+# - <allowed_role_4>
+# <service_bar>:
+# <api_action_c>:
+# - <allowed_role_3>
#
# Where:
#
-# service = the service that is being tested (Cinder, Nova, etc.)
+# service = the service that is being tested (Cinder, Nova, etc.).
+#
# api_action = the policy action that is being tested. Examples:
-# - volume:create
-# - os_compute_api:servers:start
-# - add_image
-# allowed_role = the Keystone role that is allowed to perform the API.
+#
+# * volume:create
+# * os_compute_api:servers:start
+# * add_image
+#
+# allowed_role = the ``oslo.policy`` role that is allowed to perform
+# the API.
# (string value)
#custom_requirements_file = <None>
diff --git a/patrole_tempest_plugin/README.rst b/patrole_tempest_plugin/README.rst
deleted file mode 100644
index d678422..0000000
--- a/patrole_tempest_plugin/README.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-==============================
-Tempest Integration of Patrole
-==============================
-
-This directory contains Tempest tests to cover the Patrole project.
diff --git a/patrole_tempest_plugin/config.py b/patrole_tempest_plugin/config.py
index f379859..ee7a6c5 100644
--- a/patrole_tempest_plugin/config.py
+++ b/patrole_tempest_plugin/config.py
@@ -42,9 +42,9 @@
help="""
This option determines whether Patrole should run against a
``custom_requirements_file`` which defines RBAC requirements. The
-purpose of setting this flag to True is to verify that RBAC policy
+purpose of setting this flag to ``True`` is to verify that RBAC policy
is in accordance to requirements. The idea is that the
-``custom_requirements_file`` perfectly defines what the RBAC requirements are.
+``custom_requirements_file`` precisely defines what the RBAC requirements are.
Here are the possible outcomes when running the Patrole tests against
a ``custom_requirements_file``:
@@ -67,28 +67,31 @@
file must be located on the same host that Patrole runs on. The YAML
file should be written as follows:
-```
-<service_foo>:
- <api_action_x>:
- - <allowed_role_a>
- - <allowed_role_b>
- - <allowed_role_c>
- <api_action_y>:
- - <allowed_role_d>
- - <allowed_role_e>
-<service_bar>:
- <api_action_z>:
- - <allowed_role_b>
-```
+.. code-block:: yaml
+
+ <service_foo>:
+ <api_action_a>:
+ - <allowed_role_1>
+ - <allowed_role_2>
+ - <allowed_role_3>
+ <api_action_b>:
+ - <allowed_role_2>
+ - <allowed_role_4>
+ <service_bar>:
+ <api_action_c>:
+ - <allowed_role_3>
Where:
-service = the service that is being tested (Cinder, Nova, etc.)
+service = the service that is being tested (Cinder, Nova, etc.).
+
api_action = the policy action that is being tested. Examples:
- - volume:create
- - os_compute_api:servers:start
- - add_image
-allowed_role = the Keystone role that is allowed to perform the API.
+
+* volume:create
+* os_compute_api:servers:start
+* add_image
+
+allowed_role = the ``oslo.policy`` role that is allowed to perform the API.
""")
]
diff --git a/patrole_tempest_plugin/policy_authority.py b/patrole_tempest_plugin/policy_authority.py
index 9499bf6..b813f88 100644
--- a/patrole_tempest_plugin/policy_authority.py
+++ b/patrole_tempest_plugin/policy_authority.py
@@ -156,9 +156,9 @@
def allowed(self, rule_name, role):
"""Checks if a given rule in a policy is allowed with given role.
- :param string rule_name: Rule to be checked using ``oslo.policy``.
- :param bool is_admin: Whether admin context is used.
- :raises RbacParsingException: If `rule_name`` does not exist in the
+ :param string rule_name: Policy name to pass to``oslo.policy``.
+ :param string role: Role to validate for authorization.
+ :raises RbacParsingException: If ``rule_name`` does not exist in the
cloud (in policy file or among registered in-code policy defaults).
"""
is_admin_context = self._is_admin_context(role)
diff --git a/patrole_tempest_plugin/requirements_authority.py b/patrole_tempest_plugin/requirements_authority.py
index 683a7eb..75df9f4 100644
--- a/patrole_tempest_plugin/requirements_authority.py
+++ b/patrole_tempest_plugin/requirements_authority.py
@@ -16,14 +16,17 @@
from oslo_log import log as logging
+from tempest import config
from tempest.lib import exceptions
from patrole_tempest_plugin.rbac_authority import RbacAuthority
+CONF = config.CONF
LOG = logging.getLogger(__name__)
class RequirementsParser(object):
+ """A class that parses a custom requirements file."""
_inner = None
class Inner(object):
@@ -40,6 +43,27 @@
@staticmethod
def parse(component):
+ """Parses a requirements file with the following format:
+
+ .. code-block:: yaml
+
+ <service_foo>:
+ <api_action_a>:
+ - <allowed_role_1>
+ - <allowed_role_2>
+ - <allowed_role_3>
+ <api_action_b>:
+ - <allowed_role_2>
+ - <allowed_role_4>
+ <service_bar>:
+ <api_action_c>:
+ - <allowed_role_3>
+
+ :param str component: Name of the OpenStack service to be validated.
+ :returns: The dictionary that maps each policy action to the list
+ of allowed roles, for the given ``component``.
+ :rtype: dict
+ """
try:
for section in RequirementsParser.Inner._rbac_map:
if component in section:
@@ -51,13 +75,39 @@
class RequirementsAuthority(RbacAuthority):
+ """A class that uses a custom requirements file to validate RBAC."""
+
def __init__(self, filepath=None, component=None):
- if filepath is not None and component is not None:
+ """This class can be used to achieve a requirements-driven approach to
+ validating an OpenStack cloud's RBAC implementation. Using this
+ approach, Patrole computes expected test results by performing lookups
+ against a custom requirements file which precisely defines the cloud's
+ RBAC requirements.
+
+ :param str filepath: Path where the custom requirements file lives.
+ Defaults to ``[patrole].custom_requirements_file``.
+ :param str component: Name of the OpenStack service to be validated.
+ """
+ filepath = filepath or CONF.patrole.custom_requirements_file
+
+ if component is not None:
self.roles_dict = RequirementsParser(filepath).parse(component)
else:
self.roles_dict = None
def allowed(self, rule_name, role):
+ """Checks if a given rule in a policy is allowed with given role.
+
+ :param string rule_name: Rule to be checked using provided requirements
+ file specified by ``[patrole].custom_requirements_file``. Must be
+ a key present in this file, under the appropriate component.
+ :param string role: Role to validate against custom requirements file.
+ :returns: True if ``role`` is allowed to perform ``rule_name``, else
+ False.
+ :rtype: bool
+ :raises KeyError: If ``rule_name`` does not exist among the keyed
+ policy names in the custom requirements file.
+ """
if self.roles_dict is None:
raise exceptions.InvalidConfiguration(
"Roles dictionary parsed from requirements YAML file is "
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 adb5a6c..1fe52e9 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
@@ -33,6 +33,14 @@
class ServerActionsRbacTest(rbac_base.BaseV2ComputeRbacTest):
+ # admin credentials used for waiters which invokes a show API call
+ credentials = ['primary', 'admin']
+
+ @classmethod
+ def setup_clients(cls):
+ super(ServerActionsRbacTest, cls).setup_clients()
+ cls.admin_servers_client = cls.os_admin.servers_client
+
@classmethod
def resource_setup(cls):
super(ServerActionsRbacTest, cls).resource_setup()
@@ -58,17 +66,24 @@
def _stop_server(self):
self.servers_client.stop_server(self.server_id)
waiters.wait_for_server_status(
- self.servers_client, self.server_id, 'SHUTOFF')
+ self.admin_servers_client, self.server_id, 'SHUTOFF')
def _resize_server(self, flavor):
+ status = self.admin_servers_client. \
+ show_server(self.server_id)['server']['status']
+ if status == 'RESIZED':
+ return
self.servers_client.resize_server(self.server_id, flavor)
waiters.wait_for_server_status(
- self.servers_client, self.server_id, 'VERIFY_RESIZE')
+ self.admin_servers_client, self.server_id, 'VERIFY_RESIZE')
def _confirm_resize_server(self):
- self.servers_client.confirm_resize_server(self.server_id)
+ status = self.admin_servers_client. \
+ show_server(self.server_id)['server']['status']
+ if status == 'VERIFY_RESIZE':
+ self.servers_client.confirm_resize_server(self.server_id)
waiters.wait_for_server_status(
- self.servers_client, self.server_id, 'ACTIVE')
+ self.admin_servers_client, self.server_id, 'ACTIVE')
def _shelve_server(self):
self.servers_client.shelve_server(self.server_id)
@@ -77,12 +92,13 @@
self.server_id)
offload_time = CONF.compute.shelved_offload_time
if offload_time >= 0:
- waiters.wait_for_server_status(self.servers_client,
+ waiters.wait_for_server_status(self.admin_servers_client,
self.server_id,
'SHELVED_OFFLOADED',
extra_timeout=offload_time)
else:
- waiters.wait_for_server_status(self.servers_client, self.server_id,
+ waiters.wait_for_server_status(self.admin_servers_client,
+ self.server_id,
'SHELVED')
def _pause_server(self):
@@ -91,7 +107,7 @@
self.servers_client.unpause_server,
self.server_id)
waiters.wait_for_server_status(
- self.servers_client, self.server_id, 'PAUSED')
+ self.admin_servers_client, self.server_id, 'PAUSED')
def _cleanup_server_actions(self, function, server_id, **kwargs):
server = self.servers_client.show_server(server_id)['server']
@@ -179,6 +195,7 @@
self._resize_server(self.flavor_ref_alt)
self.addCleanup(self._confirm_resize_server)
self.addCleanup(self._resize_server, self.flavor_ref)
+ self.addCleanup(self._confirm_resize_server)
with self.rbac_utils.override_role(self):
self._confirm_resize_server()
@@ -276,6 +293,7 @@
self.compute_images_client.delete_image, image['id'])
@decorators.idempotent_id('9fdd4630-731c-4f7c-bce5-69fa3792c52a')
+ @decorators.attr(type='slow')
@testtools.skipUnless(CONF.compute_feature_enabled.snapshot,
'Snapshotting not available, backup not possible.')
@utils.services('image')
diff --git a/patrole_tempest_plugin/tests/api/compute/test_server_misc_policy_actions_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_server_misc_policy_actions_rbac.py
index 58a829d..13faca1 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_server_misc_policy_actions_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_server_misc_policy_actions_rbac.py
@@ -570,8 +570,7 @@
@classmethod
def resource_setup(cls):
def _cleanup_ports(network_id):
- ports = cls.ports_client.\
- list_ports(network_id=network_id)['ports']
+ ports = cls.ports_client.list_ports(network_id=network_id)['ports']
for port in ports:
test_utils.call_and_ignore_notfound_exc(
cls.ports_client.delete_port,
@@ -666,6 +665,9 @@
self.interfaces_client, self.server['id'],
interface['port_id'], 'ACTIVE')
self.addCleanup(
+ waiters.wait_for_interface_detach, self.interfaces_client,
+ self.server['id'], interface['port_id'])
+ self.addCleanup(
test_utils.call_and_ignore_notfound_exc,
self.interfaces_client.delete_interface,
self.server['id'], interface['port_id'])
@@ -684,6 +686,8 @@
with self.rbac_utils.override_role(self):
self.interfaces_client.delete_interface(self.server['id'],
interface['port_id'])
+ waiters.wait_for_interface_detach(
+ self.interfaces_client, self.server['id'], interface['port_id'])
@decorators.idempotent_id('6886d360-0d86-4760-b1a3-882d81fbebcc')
@utils.requires_ext(extension='os-ips', service='compute')
@@ -723,47 +727,16 @@
if interfaces:
network_id = interfaces[0]['net_id']
else:
- network_id = self.interfaces_client.create_interface(
- self.server['id'])['interfaceAttachment']['net_id']
+ interface = self.interfaces_client.create_interface(
+ self.server['id'])['interfaceAttachment']
+ network_id = interface['net_id']
+ self.addCleanup(
+ waiters.wait_for_interface_detach, self.interfaces_client,
+ self.server['id'], interface['port_id'])
+ self.addCleanup(
+ self.interfaces_client.delete_interface,
+ self.server['id'], interface['port_id'])
with self.rbac_utils.override_role(self):
self.servers_client.add_fixed_ip(self.server['id'],
networkId=network_id)
-
-
-class VirtualInterfacesRbacTest(rbac_base.BaseV2ComputeRbacTest):
- # The compute os-virtual-interfaces API is deprecated from the Microversion
- # 2.44 onward. For more information, see:
- # https://developer.openstack.org/api-ref/compute/#servers-virtual-interfaces-servers-os-virtual-interfaces-deprecated
- max_microversion = '2.43'
-
- @classmethod
- def setup_credentials(cls):
- # This test needs a network and a subnet
- cls.set_network_resources(network=True, subnet=True)
- super(VirtualInterfacesRbacTest, cls).setup_credentials()
-
- @classmethod
- def resource_setup(cls):
- super(VirtualInterfacesRbacTest, cls).resource_setup()
- cls.server = cls.create_test_server(wait_until='ACTIVE')
-
- @rbac_rule_validation.action(
- service="nova",
- rule="os_compute_api:os-virtual-interfaces")
- @decorators.idempotent_id('fc719ae3-0f73-4689-8378-1b841f0f2818')
- def test_list_virtual_interfaces(self):
- """Test list virtual interfaces, part of os-virtual-interfaces.
-
- If Neutron is available, then call the API and expect it to fail
- with a 400 BadRequest (policy enforcement is done before that happens).
- """
- with self.rbac_utils.override_role(self):
- if CONF.service_available.neutron:
- msg = ("Listing virtual interfaces is not supported by this "
- "cloud.")
- with self.assertRaisesRegex(lib_exc.BadRequest, msg):
- self.servers_client.list_virtual_interfaces(
- self.server['id'])
- else:
- self.servers_client.list_virtual_interfaces(self.server['id'])
diff --git a/patrole_tempest_plugin/tests/api/compute/test_server_volume_attachments_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_server_volume_attachments_rbac.py
index b803fe3..a510d1e 100644
--- a/patrole_tempest_plugin/tests/api/compute/test_server_volume_attachments_rbac.py
+++ b/patrole_tempest_plugin/tests/api/compute/test_server_volume_attachments_rbac.py
@@ -25,6 +25,10 @@
CONF = config.CONF
+# FIXME(felipemonteiro): `@decorators.attr(type='slow')` are added to tests
+# below to in effect cause the tests to be non-voting in Zuul due to a high
+# rate of spurious failures related to volume attachments. This will be
+# revisited at a later date.
class ServerVolumeAttachmentRbacTest(rbac_base.BaseV2ComputeRbacTest):
@classmethod
@@ -53,6 +57,7 @@
with self.rbac_utils.override_role(self):
self.servers_client.list_volume_attachments(self.server['id'])
+ @decorators.attr(type='slow')
@rbac_rule_validation.action(
service="nova",
rule="os_compute_api:os-volumes-attachments:create")
@@ -61,6 +66,7 @@
with self.rbac_utils.override_role(self):
self.attach_volume(self.server, self.volume)
+ @decorators.attr(type='slow')
@rbac_rule_validation.action(
service="nova",
rule="os_compute_api:os-volumes-attachments:show")
diff --git a/patrole_tempest_plugin/tests/api/compute/test_virtual_interfaces_rbac.py b/patrole_tempest_plugin/tests/api/compute/test_virtual_interfaces_rbac.py
new file mode 100644
index 0000000..ae77a34
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/compute/test_virtual_interfaces_rbac.py
@@ -0,0 +1,64 @@
+# 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 patrole_tempest_plugin import rbac_rule_validation
+from patrole_tempest_plugin.tests.api.compute import rbac_base
+
+CONF = config.CONF
+
+
+# TODO(rb560u): Remove this test class once the nova queens branch goes into
+# extended maintenance mode.
+class VirtualInterfacesRbacTest(rbac_base.BaseV2ComputeRbacTest):
+ # The compute os-virtual-interfaces API is deprecated from the Microversion
+ # 2.44 onward. For more information, see:
+ # https://developer.openstack.org/api-ref/compute/#servers-virtual-interfaces-servers-os-virtual-interfaces-deprecated
+ depends_on_nova_network = True
+ max_microversion = '2.43'
+
+ @classmethod
+ def setup_credentials(cls):
+ # This test needs a network and a subnet
+ cls.set_network_resources(network=True, subnet=True)
+ super(VirtualInterfacesRbacTest, cls).setup_credentials()
+
+ @classmethod
+ def resource_setup(cls):
+ super(VirtualInterfacesRbacTest, cls).resource_setup()
+ cls.server = cls.create_test_server(wait_until='ACTIVE')
+
+ @rbac_rule_validation.action(
+ service="nova",
+ rule="os_compute_api:os-virtual-interfaces")
+ @decorators.idempotent_id('fc719ae3-0f73-4689-8378-1b841f0f2818')
+ def test_list_virtual_interfaces(self):
+ """Test list virtual interfaces, part of os-virtual-interfaces.
+
+ If Neutron is available, then call the API and expect it to fail
+ with a 400 BadRequest (policy enforcement is done before that happens).
+ """
+ with self.rbac_utils.override_role(self):
+ if CONF.service_available.neutron:
+ msg = ("Listing virtual interfaces is not supported by this "
+ "cloud.")
+ with self.assertRaisesRegex(lib_exc.BadRequest, msg):
+ self.servers_client.list_virtual_interfaces(
+ self.server['id'])
+ else:
+ self.servers_client.list_virtual_interfaces(self.server['id'])
diff --git a/patrole_tempest_plugin/tests/api/identity/rbac_base.py b/patrole_tempest_plugin/tests/api/identity/rbac_base.py
index 90fa6aa..91b3d1e 100644
--- a/patrole_tempest_plugin/tests/api/identity/rbac_base.py
+++ b/patrole_tempest_plugin/tests/api/identity/rbac_base.py
@@ -118,6 +118,8 @@
@classmethod
def setup_clients(cls):
super(BaseIdentityV3RbacTest, cls).setup_clients()
+ cls.application_credentials_client = \
+ cls.os_primary.application_credentials_client
cls.creds_client = cls.os_primary.credentials_client
cls.consumers_client = cls.os_primary.oauth_consumers_client
cls.domains_client = cls.os_primary.domains_client
diff --git a/patrole_tempest_plugin/tests/api/identity/v3/test_application_credentials_rbac.py b/patrole_tempest_plugin/tests/api/identity/v3/test_application_credentials_rbac.py
new file mode 100644
index 0000000..c7a6033
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/identity/v3/test_application_credentials_rbac.py
@@ -0,0 +1,85 @@
+# 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 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.identity import rbac_base
+
+
+CONF = config.CONF
+
+
+class ApplicationCredentialsV3RbacTest(rbac_base.BaseIdentityV3RbacTest):
+
+ @classmethod
+ def skip_checks(cls):
+ super(ApplicationCredentialsV3RbacTest, cls).skip_checks()
+ if not CONF.identity_feature_enabled.application_credentials:
+ raise cls.skipException("Application credentials are not available"
+ " in this environment")
+
+ @classmethod
+ def resource_setup(cls):
+ super(ApplicationCredentialsV3RbacTest, cls).resource_setup()
+ cls.user_id = cls.os_primary.credentials.user_id
+
+ def _create_application_credential(self, name=None, **kwargs):
+ name = name or data_utils.rand_name('application_credential')
+ application_credential = (
+ self.application_credentials_client.create_application_credential(
+ self.user_id, name=name, **kwargs))['application_credential']
+ self.addCleanup(
+ test_utils.call_and_ignore_notfound_exc,
+ self.application_credentials_client.delete_application_credential,
+ self.user_id,
+ application_credential['id'])
+ return application_credential
+
+ @decorators.idempotent_id('b53bee14-e9df-4929-b257-6def76c12e4d')
+ @rbac_rule_validation.action(service="keystone",
+ rule="identity:create_application_credential")
+ def test_create_application_credential(self):
+ with self.rbac_utils.override_role(self):
+ self._create_application_credential()
+
+ @decorators.idempotent_id('58b3c3a0-5ad0-44f7-8da7-0736f71f7168')
+ @rbac_rule_validation.action(service="keystone",
+ rule="identity:list_application_credentials")
+ def test_list_application_credentials(self):
+ with self.rbac_utils.override_role(self):
+ self.application_credentials_client.list_application_credentials(
+ user_id=self.user_id)
+
+ @decorators.idempotent_id('d7b13968-a8a6-47fd-8e1d-7cc7f565c7f8')
+ @rbac_rule_validation.action(service="keystone",
+ rule="identity:get_application_credential")
+ def test_show_application_credential(self):
+ app_cred = self._create_application_credential()
+ with self.rbac_utils.override_role(self):
+ self.application_credentials_client.show_application_credential(
+ user_id=self.user_id, application_credential_id=app_cred['id'])
+
+ @decorators.idempotent_id('521b7c0f-1dd5-47a6-ae95-95c0323d7735')
+ @rbac_rule_validation.action(service="keystone",
+ rule="identity:delete_application_credential")
+ def test_delete_application_credential(self):
+ app_cred = self._create_application_credential()
+ with self.rbac_utils.override_role(self):
+ self.application_credentials_client.delete_application_credential(
+ user_id=self.user_id, application_credential_id=app_cred['id'])
diff --git a/patrole_tempest_plugin/tests/api/volume/test_volume_actions_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_volume_actions_rbac.py
index 46f7a3e..dcc2bd5 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_volume_actions_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_volume_actions_rbac.py
@@ -73,6 +73,7 @@
'"volume_extension:volume_actions:attach" must be available in the '
'cloud.')
@utils.services('compute')
+ @decorators.attr(type='slow')
@rbac_rule_validation.action(
service="cinder",
rule="volume_extension:volume_actions:attach")