Retire patrole

Patrole project is not active anymore and its gate is broken.
We waited for couple of cycle to see if there is any interest
in this project and anyone can maintain it. But we did not get any
new maintainers and current QA team does not have bandwidth/interest
to continue maintaining it.

This project was for RBAc testing which is moving towards unit/functional
tests on service side as well as tempest plugins tests.

In QA 2023.2 PTG, we decided to retire this project

- https://etherpad.opendev.org/p/qa-bobcat-ptg

Change-Id: I7721cf06104e5871ec27cdd87d4608dace60a8b7
This commit is contained in:
Ghanshyam Mann 2023-04-10 22:29:00 -05:00
parent aa7bc0dd9a
commit b540700061
324 changed files with 10 additions and 25345 deletions

View File

@ -1,6 +0,0 @@
[run]
branch = True
source = patrole
[report]
ignore_errors = True

65
.gitignore vendored
View File

@ -1,65 +0,0 @@
*.py[cod]
# C extensions
*.so
# Packages
*.egg*
*.egg-info
dist
build
eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg
lib
lib64
# Installer logs
pip-log.txt
# Unit test / coverage reports
cover/
.coverage*
!.coveragerc
.tox
nosetests.xml
.testrepository
.venv
# Translations
*.mo
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# Complexity
output/*.html
output/*/index.html
# Sphinx
doc/build
doc/source/_static/patrole.conf.sample
doc/source/framework/code/
# pbr generates these
AUTHORS
ChangeLog
# Editors
*~
.*.swp
.*sw?
*.idea
# Files created by releasenotes build
releasenotes/build
# Misc
.stestr
*.log

View File

@ -1,5 +0,0 @@
# Format is:
# <preferred e-mail> <other e-mail 1>
# <preferred e-mail> <other e-mail 2>
Felipe Monteiro <felipe.carneiro.monteiro@gmail.com> <fm577c@att.com>
Felipe Monteiro <felipe.carneiro.monteiro@gmail.com> <felipe.monteiro@att.com>

View File

@ -1,3 +0,0 @@
[DEFAULT]
test_path=./patrole_tempest_plugin/tests/unit
group_regex=([^\.]*\.)*

View File

@ -1,246 +0,0 @@
- job:
name: patrole-base
parent: devstack-tempest
description: |
Patrole base job for admin,member and reader roles. This job executes RBAC tests
for all the "core" services that Tempest covers, excluding Swift.
required-projects:
- name: opendev.org/openstack/tempest
- name: opendev.org/openstack/patrole
timeout: 7800
roles:
- zuul: opendev.org/openstack/devstack
# Define common irrelevant files to use everywhere else
irrelevant-files: &patrole-irrelevant-files
- ^(test-|)requirements.txt$
- ^.*\.rst$
- ^doc/.*
- ^etc/.*$
- ^patrole_tempest_plugin/tests/unit/.*$
- ^patrole_tempest_plugin/hacking/.*$
- ^releasenotes/.*
- ^setup.cfg$
vars:
tempest_plugins:
- patrole
devstack_plugins:
patrole: https://opendev.org/openstack/patrole.git
devstack_services:
tempest: true
neutron: true
neutron-trunk: true
tempest_test_regex: (?!.*\[.*\bslow\b.*\])(^patrole_tempest_plugin\.tests\.api)
# run the tempest all tox environment target with patrole regex
tox_envlist: all
# allows job to use the tempest version installed with devstack instead of pypi
# according to the requirements.txt
tox_extra_args: --sitepackages
- job:
name: patrole-base-multinode
parent: tempest-multinode-full-py3
description: |-
Patrole base job for multinode and "slow" tests where "slow" tests include:
* Tests that take more than ~30 seconds to run.
* Tests that experience spurious failures related to servers, volumes,
backups and similar resources failing to build.
timeout: 7800
branches:
- master
required-projects:
- opendev.org/openstack/devstack-gate
- opendev.org/openstack/tempest
- opendev.org/openstack/patrole
irrelevant-files: *patrole-irrelevant-files
vars:
tempest_plugins:
- patrole
devstack_plugins:
patrole: https://opendev.org/openstack/patrole.git
devstack_services:
tempest: true
neutron: true
tempest_concurrency: 1
tempest_test_regex: (?=.*\[.*\bslow\b.*\])(^patrole_tempest_plugin\.tests\.api)
tox_envlist: all
tox_extra_args: --sitepackages
- job:
name: patrole-admin
parent: patrole-base
description: Patrole job for admin role.
vars:
devstack_localrc:
RBAC_TEST_ROLES: admin
# https://storyboard.openstack.org/#!/story/2009048
tempest_exclude_regex: patrole_tempest_plugin.tests.api.volume.test_volume_actions_rbac.VolumesActionsV3RbacTest.test_force_detach_volume_from_instance
- job:
name: patrole-member
parent: patrole-base
description: Patrole job for member role.
# This currently works from stable/pike onward.
branches: ^(?!stable/ocata).*$
vars:
devstack_localrc:
RBAC_TEST_ROLES: member
# https://storyboard.openstack.org/#!/story/2009216
tempest_exclude_regex: patrole_tempest_plugin.tests.api.volume.test_volume_types_extra_specs_rbac.VolumeTypesExtraSpecsRbacTest.test_show_volume_type_extra_specs
- job:
name: patrole-reader
parent: patrole-base
description: Patrole job for reader role.
# This currently works from stable/stein onward.
branches: ^(?!stable/(ocata|pike|queens|rocky)).*$
vars:
devstack_localrc:
RBAC_TEST_ROLES: reader
# https://storyboard.openstack.org/#!/story/2009216
tempest_exclude_regex: patrole_tempest_plugin.tests.api.volume.test_volume_types_extra_specs_rbac.VolumeTypesExtraSpecsRbacTest.test_show_volume_type_extra_specs
- job:
name: patrole-member-wallaby
parent: patrole-member
override-checkout: stable/wallaby
- job:
name: patrole-member-victoria
parent: patrole-member
override-checkout: stable/victoria
- job:
name: patrole-member-ussuri
parent: patrole-member
nodeset: openstack-single-node-bionic
override-checkout: stable/ussuri
vars:
# https://storyboard.openstack.org/#!/story/2009048
tempest_exclude_regex: patrole_tempest_plugin.tests.api.volume.test_volume_actions_rbac.VolumesActionsV3RbacTest.test_force_detach_volume_from_instance
- job:
name: patrole-multinode-admin
parent: patrole-base-multinode
voting: false
vars:
devstack_localrc:
RBAC_TEST_ROLES: admin
- job:
name: patrole-multinode-member
parent: patrole-base-multinode
voting: false
vars:
devstack_localrc:
RBAC_TEST_ROLES: member
- job:
name: patrole-multinode-reader
parent: patrole-base-multinode
voting: false
vars:
devstack_localrc:
RBAC_TEST_ROLES: reader
- job:
name: patrole-py35-member
parent: patrole-base
description: Patrole py35 job for member role.
vars:
devstack_localrc:
# Use member for py35 because arguably negative testing is more
# important than admin, which is already covered by patrole-admin job.
RBAC_TEST_ROLES: member
USE_PYTHON3: true
devstack_services:
s-account: false
s-container: false
s-object: false
s-proxy: false
# Without Swift, c-bak cannot run (in the gate at least).
c-bak: false
- job:
name: patrole-extension-base
parent: patrole-base
description: |
Patrole plugin job for admin and member roles which runs RBAC tests for
neutron-tempest-plugin APIs (if the plugin is installed).
Covers Neutron extension functionality only. Should not be used for
supporting Neutron plugins like fwaas.
required-projects:
- name: opendev.org/openstack/tempest
- name: opendev.org/openstack/patrole
- name: opendev.org/openstack/neutron-tempest-plugin
vars:
tempest_plugins:
- patrole
- neutron-tempest-plugin
devstack_plugins:
neutron: https://opendev.org/openstack/neutron.git
patrole: https://opendev.org/openstack/patrole.git
neutron-tempest-plugin: https://opendev.org/openstack/neutron-tempest-plugin.git
devstack_services:
tempest: true
neutron: true
neutron-segments: true
neutron-qos: true
tempest_test_regex: (?=.*ExtRbacTest)(^patrole_tempest_plugin\.tests\.api)
- job:
name: patrole-extension-admin
parent: patrole-extension-base
voting: false
vars:
devstack_localrc:
RBAC_TEST_ROLES: admin
- job:
name: patrole-extension-member
parent: patrole-extension-base
voting: false
vars:
devstack_localrc:
RBAC_TEST_ROLES: member
- job:
name: patrole-extension-reader
parent: patrole-extension-base
voting: false
vars:
devstack_localrc:
RBAC_TEST_ROLES: reader
- project:
templates:
- openstack-cover-jobs
- openstack-python3-yoga-jobs
- check-requirements
- publish-openstack-docs-pti
- release-notes-jobs-python3
check:
jobs:
- patrole-admin
- patrole-member
- patrole-reader
- patrole-member-wallaby
- patrole-member-victoria
- patrole-member-ussuri
- patrole-multinode-admin
- patrole-multinode-member
- patrole-multinode-reader
- patrole-extension-admin
- patrole-extension-member
- patrole-extension-reader
gate:
jobs:
- patrole-admin
- patrole-member
- patrole-reader
periodic-stable:
jobs:
- patrole-member-wallaby
- patrole-member-victoria
- patrole-member-ussuri

View File

@ -1,19 +0,0 @@
The source repository for this project can be found at:
https://opendev.org/openstack/patrole
Pull requests submitted through GitHub are not monitored.
To start contributing to OpenStack, follow the steps in the contribution guide
to set up and use Gerrit:
https://docs.openstack.org/contributors/code-and-documentation/quick-start.html
Bugs should be filed on Storyboard:
https://storyboard.openstack.org/#!/project/1040
For more specific information about contributing to this repository, see the
Patrole contributor guide:
https://docs.openstack.org/patrole/latest/contributor/contributing.html

View File

@ -1,170 +0,0 @@
Patrole Coding Guide
====================
- Step 1: Read the OpenStack Style Commandments: `<https://docs.openstack.org/hacking/latest/>`__
- Step 2: Review Tempest's Style Commandments: `<https://docs.openstack.org/tempest/latest/HACKING.html>`__
- Step 3: Read on
Patrole Specific Commandments
------------------------------
Patrole borrows the following commandments from Tempest; refer to
`Tempest's Commandments <https://docs.openstack.org/tempest/latest/HACKING.html>`__
for more information:
.. note::
The original Tempest Commandments do not include Patrole-specific paths.
Patrole-specific paths replace the Tempest-specific paths within Patrole's
hacking checks.
- [T102] Cannot import OpenStack python clients in
``patrole_tempest_plugin/tests/api``
- [T105] Tests cannot use setUpClass/tearDownClass
- [T107] Check that a service tag isn't in the module path
- [T108] Check no hyphen at the end of rand_name() argument
- [T109] Cannot use testtools.skip decorator; instead use
``decorators.skip_because`` from ``tempest.lib``
- [T113] Check that tests use ``data_utils.rand_uuid()`` instead of
``uuid.uuid4()``
- [N322] Method's default argument shouldn't be mutable
The following are Patrole's specific Commandments:
- [P100] The ``rbac_rule_validation.action`` decorator must be applied to
all RBAC tests
- [P101] RBAC test filenames must end with "_rbac.py"; for example,
test_servers_rbac.py, not test_servers.py
- [P102] RBAC test class names must end in 'RbacTest'
- [P103] ``self.client`` must not be used as a client alias; this allows for
code that is more maintainable and easier to read
- [P104] RBAC `extension test class`_ names must end in 'ExtRbacTest'
.. _extension test class: https://git.openstack.org/cgit/openstack/patrole/plain/patrole_tempest_plugin/tests/api/network/README.rst
Supported OpenStack Components
------------------------------
Patrole only offers **in-tree** integration testing coverage for the following
components:
* Cinder
* Glance
* Keystone
* Neutron
* Nova
Patrole currently has no stable library, so reliance upon Patrole's framework
for external RBAC testing should be done with caution. Nonetheless, even when
Patrole has a stable library, it will only offer in-tree RBAC testing for
the components listed above.
Role Overriding
---------------
Correct role overriding is vital to correct RBAC testing within Patrole. If a
test does not call ``self.override_role()`` within the RBAC test, followed
by the API endpoint that enforces the expected policy action, then the test is
**not** a valid Patrole test: The API endpoint under test will be performed
with admin role, which is always wrong unless ``CONF.patrole.rbac_test_role``
is also admin.
.. todo::
Patrole does not have a hacking check for role overriding, but one may be
added in the future.
Branchless Patrole Considerations
---------------------------------
Like Tempest, Patrole is branchless. This is to better ensure API and RBAC
consistency between releases because API and RBAC behavior should not change
between releases. This means that the stable branches are also gated by the
Patrole master branch, which also means that proposed commits to Patrole must
work against both the master and all the currently supported stable branches
of the projects. As such there are a few special considerations that have to
be accounted for when pushing new changes to Patrole.
1. New Tests for new features
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Patrole, like Tempest, *implicitly* tests new features because new policies
oftentimes accompany new features. The same `Tempest philosophy`_ regarding
feature flags and new features also applies to Patrole.
.. _Tempest philosophy: https://docs.openstack.org/tempest/latest/HACKING.html#new-tests-for-new-features
2. New Tests for new policies
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When adding tests for new policies that were not in previous releases of the
projects, the new test must be properly skipped with a feature flag. This
involves using the ``testtools.skip(Unless|If)`` decorator above the test
to check if the required policy is enabled. Similarly, a feature flag must
be used whenever an OpenStack service covered by Patrole changes one of its
policies in a backwards-incompatible way. If there isn't a method of selecting
the new policy from the config file then there won't be a mechanism to disable
the test with older stable releases and the new test won't be able to merge.
Introduction of a new feature flag requires specifying a default value for the
corresponding config option that is appropriate in the latest OpenStack
release. Because Patrole is branchless, the feature flag's default value will
need to be overridden to a value that is appropriate in earlier releases in
which the feature isn't available. In DevStack, this can be accomplished by
modifying Patrole's lib installation script for previous branches (because
DevStack is branched).
3. Bug fix on core project needing Patrole changes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When trying to land a bug fix which changes a tested API you'll have to use the
following procedure:
#. Propose change to the project, get a +2 on the change even with the
test failing Patrole side.
#. Propose skip to the relevant Patrole test which will only be approved
after the corresponding change in the project has a +2.
#. Land project change in master and all open stable branches
(if required).
#. Land changed test in Patrole.
Otherwise the bug fix won't be able to land in the project.
4. New Tests for existing features or policies
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The same `Tempest logic`_ regarding new tests for existing features or
policies also applies to Patrole.
.. _Tempest logic: https://docs.openstack.org/tempest/latest/HACKING.html#new-tests-for-existing-features
Black Box vs. White Box Testing
-------------------------------
Tempest is a `black box testing framework`_, meaning that it is concerned with
testing public API endpoints and doesn't concern itself with testing internal
implementation details. Patrole, as a Tempest plugin, also falls underneath
the category of black box testing. However, even with policy in code
documentation, some degree of white box testing is required in order to
correctly write RBAC tests.
This is because :ref:`policy-in-code` documentation, while useful in many
respects, is usually quite brief and its main purpose is to help operators
understand how to customize policy configuration rather than to help
developers understand complex policy authorization work flows. For example,
policy in code documentation doesn't make deriving
:ref:`multiple policies <multiple-policies>` easy. Such documentation also
doesn't usually mention that a specific parameter needs to be set, or that a
particular microversion must be enabled, or that a particular set of
prerequisite API or policy actions must be executed, in order for the policy
under test to be enforced by the server. This means that test writers must
account for the internal RBAC implementation in API code in order to correctly
understand the complete RBAC work flow within an API.
Besides, as mentioned :ref:`elsewhere <design-principles>` in this
documentation, not all services currently implement policy in code, making
some degree of white box testing a "necessary evil" for writing robust RBAC
tests.
.. _black box testing framework: https://docs.openstack.org/tempest/latest/HACKING.html#negative-tests

176
LICENSE
View File

@ -1,176 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

View File

@ -1,251 +1,10 @@
Patrole - RBAC Integration Tempest Plugin
=========================================
Patrole is a set of integration tests to be run against a live OpenStack
cluster. It has a battery of tests dedicated to validating the correctness and
integrity of the cloud's RBAC implementation.
More importantly, Patrole is a security validation tool for verifying that
Role-Based Access Control is correctly configured and enforced in an OpenStack
cloud. It runs `Tempest`_-based API tests using specified RBAC roles, thus
allowing deployments to verify that only intended roles have access to those
APIs.
Patrole is currently undergoing heavy development. As more projects move
toward policy in code, Patrole will align its testing with the appropriate
documentation.
* Free software: Apache license
* Documentation: https://docs.openstack.org/patrole/latest
* Source: https://opendev.org/openstack/patrole
* Bugs: https://storyboard.openstack.org/#!/project/openstack/patrole
* Release notes: https://docs.openstack.org/releasenotes/patrole/
Team and repository tags
------------------------
.. image:: https://governance.openstack.org/tc/badges/patrole.svg
:target: https://governance.openstack.org/tc/reference/tags/index.html
.. _design-principles:
Design Principles
-----------------
As a `Tempest plugin`_, Patrole borrows some design principles from `Tempest design principles`_,
but not all, as its testing scope is confined to policies.
* *Stability*. Patrole uses OpenStack public interfaces. Tests in Patrole
should only touch public OpenStack APIs.
* *Atomicity*. Patrole tests should be atomic: they should test policies in
isolation. Unlike Tempest, a Patrole test strives to only call a single
endpoint at a time. This is because it is important to validate each policy
is authorized correctly and the best way to do that is to validate each
policy alone, to avoid test contamination.
* *Complete coverage*. Patrole should validate all policy in code defaults. For
testing, Patrole uses the API-to-policy mapping contained in each project's
`policy in code`_ documentation where applicable.
For example, Nova's policy in code documentation is located in the
`Nova repository`_ under ``nova/policies``. Likewise, Keystone's policy in
code documentation is located in the `Keystone repository`_ under
``keystone/common/policies``. The other OpenStack services follow the same
directory layout pattern with respect to policy in code.
.. note::
Realistically this is not always possible because some services have
not yet moved to policy in code.
* *Customizable*. Patrole should be able to validate custom policy overrides to
ensure that those overrides enhance rather than undermine the cloud's RBAC
configuration. In addition, Patrole should be able to validate any role.
* *Self-cleaning*. Patrole should attempt to clean up after itself; whenever
possible we should tear down resources when done.
.. note::
Patrole modifies roles dynamically in the background, which affects
pre-provisioned credentials. Work is currently underway to clean up
modifications made to pre-provisioned credentials.
* *Self-testing*. Patrole should be self-testing.
.. _Tempest plugin: https://docs.openstack.org/tempest/latest/plugin.html
.. _Tempest design principles: https://docs.openstack.org/tempest/latest/overview.html#design-principles
.. _policy in code: https://specs.openstack.org/openstack/oslo-specs/specs/newton/policy-in-code.html
.. _Nova repository: https://opendev.org/openstack/nova/src/branch/master/nova/policies
.. _Keystone repository: https://opendev.org/openstack/keystone/src/branch/master/keystone/common/policies
Features
--------
* Validation of default policy definitions located in policy.json files.
* Validation of in-code policy definitions.
* Validation of custom policy file definitions that override default policy
definitions.
* Built-in positive and negative testing. Positive and negative testing
are performed using the same tests and role-switching.
* Valdation of custom roles as well as default OpenStack roles.
.. note::
Patrole does not yet support policy.yaml files, the new file format for
policy files in OpenStack.
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 role and OpenStack service. The output from ``oslo.policy`` (the
expected result) and the actual result from test execution are compared to
each other: if both results match, then the test passes; else it fails.
Terminology
^^^^^^^^^^^
* Expected Result - The expected result of a given test.
* Actual Result - The actual result of a given test.
* Final Result - A match between both expected and actual results. A mismatch
in the expected result and the actual result will result in a test failure.
* Expected: Pass | Actual: Pass - Test Case Success
* Expected: Pass | Actual: Fail - Test Case Under-Permission Failure
* Expected: Fail | Actual: Pass - Test Case Over-Permission Failure
* Expected: Fail | Actual: Fail (Expected exception) - Test Case Success
* Expected: Fail | Actual: Fail (Unexpected exception) - Test Case Failure
Quickstart
----------
To run Patrole, you must first have `Tempest`_ installed and configured
properly. Please reference `Tempest_quickstart`_ guide to do so. Follow all
the steps outlined therein. Afterward, proceed with the steps below.
#. You first need to install Patrole. This is done with pip after you check out
the Patrole repo::
$ git clone https://opendev.org/openstack/patrole
$ pip install patrole/
This can be done within a venv.
.. note::
You may also install Patrole from source code by running::
pip install -e patrole/
#. Next you must properly configure Patrole, which is relatively
straightforward. For details on configuring Patrole refer to the
`Patrole Configuration <https://docs.openstack.org/patrole/latest/configuration.html#patrole-configuration>`_.
#. Once the configuration is done you're now ready to run Patrole. This can
be done using the `tempest_run`_ command. This can be done by running::
$ tempest run --regex '^patrole_tempest_plugin\.tests\.api'
There is also the option to use testr directly, or any `testr`_ based test
runner, like `ostestr`_. For example, from the workspace dir run::
$ stestr --regex '(?!.*\[.*\bslow\b.*\])(^patrole_tempest_plugin\.tests\.api))'
will run the same set of tests as the default gate jobs.
You can also run Patrole tests using `tox`_, but as Patrole needs access to
global packages use ``--sitepackages`` argument. To do so, ``cd`` into the
**Tempest** directory and run::
$ tox -eall --sitepackages -- patrole_tempest_plugin.tests.api
.. note::
It is possible to run Patrole via ``tox -eall`` in order to run Patrole
isolated from other plugins. This can be accomplished by including the
installation of services that currently use policy in code -- for example,
Nova and Keystone. For example::
$ tox -evenv-tempest -- pip install /opt/stack/patrole /opt/stack/keystone /opt/stack/nova
$ tox -eall -- patrole_tempest_plugin.tests.api
#. Log information from tests is captured in ``tempest.log`` under the Tempest
repository. Some Patrole debugging information is captured in that log
related to expected test results and `Role Overriding <https://docs.openstack.org/patrole/latest/test_writing_guide.html#role-overriding>`_.
More detailed RBAC testing log output is emitted to ``patrole.log`` under
the Patrole repository. To configure Patrole's logging, see the
`Patrole Configuration Guide <https://docs.openstack.org/patrole/latest/configuration.html#patrole-configuration>`_.
.. _Tempest: https://opendev.org/openstack/tempest/
.. _Tempest_quickstart: https://docs.openstack.org/tempest/latest/overview.html#quickstart
.. _tempest_run: https://docs.openstack.org/tempest/latest/run.html
.. _testr: https://testrepository.readthedocs.org/en/latest/MANUAL.html
.. _ostestr: https://docs.openstack.org/os-testr/latest/
.. _tox: https://tox.readthedocs.io/en/latest/
RBAC Tests
----------
To change the roles that the patrole tests are being run as, edit
``rbac_test_roles`` in the ``patrole`` section of tempest.conf: ::
[patrole]
rbac_test_roles = member,reader
...
.. note::
The ``rbac_test_roles`` 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, other services may use entirely
different roles or role combinations.
For more information about RBAC, reference the `rbac-overview`_
documentation page.
For information regarding which projects Patrole offers RBAC testing for,
reference the `HACKING`_ documentation page.
.. _rbac-overview: https://docs.openstack.org/patrole/latest/rbac-overview.html
.. _HACKING: https://docs.openstack.org/patrole/latest/HACKING.html#supported-openstack-components
Unit Tests
----------
Patrole also has a set of unit tests which test the Patrole code itself. These
tests can be run by specifying the test discovery path::
$ stestr --test-path ./patrole_tempest_plugin/tests/unit run
By setting ``--test-path`` option to ``./patrole_tempest_plugin/tests/unit``
it specifies that test discovery should only be run on the unit test directory.
Alternatively, there are the py27 and py35 tox jobs which will run the unit
tests with the corresponding version of Python.
One common activity is to just run a single test; you can do this with tox
simply by specifying to just run py27 or py35 tests against a single test::
$ tox -e py27 -- -n patrole_tempest_plugin.tests.unit.test_rbac_utils.RBACUtilsTest.test_override_role_with_missing_admin_role
Or all tests in the test_rbac_utils.py file::
$ tox -e py27 -- -n patrole_tempest_plugin.tests.unit.test_rbac_utils
You may also use regular expressions to run any matching tests::
$ tox -e py27 -- test_rbac_utils
For more information on these options and details about stestr, please see the
`stestr documentation <http://stestr.readthedocs.io/en/latest/MANUAL.html>`_.
Release Versioning
------------------
`Patrole Release Notes <https://docs.openstack.org/releasenotes/patrole/>`_
shows which changes have been released for each version.
Patrole's release versioning follows Tempest's conventions. Like Tempest,
Patrole is branchless and uses versioning instead.
Storyboard
----------
Bugs and enhancements are tracked via Patrole's
`Storyboard Page <https://storyboard.openstack.org/#!/project/openstack/patrole>`_.
This project is no longer maintained.
The contents of this repository are still available in the Git
source code management system. To see the contents of this
repository before it reached its end of life, please check out the
previous commit with "git checkout HEAD^1".
For any further questions, please email
openstack-discuss@lists.openstack.org or join #openstack-dev on
OFTC.

View File

@ -1,189 +0,0 @@
Reviewing Patrole Code
======================
To start read the `OpenStack Common Review Checklist
<https://docs.openstack.org/infra/manual/developers.html#peer-review>`_
Ensuring code is executed
-------------------------
Any new test or change to an existing test has to be verified in the gate. This
means that the first thing to check with any change is that a gate job actually
runs it. Tests which aren't executed either because of configuration or skips
should not be accepted.
Execution time
--------------
Along with checking that the jobs log that a new test is actually executed,
also pay attention to the execution time of that test. Patrole already runs
hundreds of tests per job in its check and gate pipelines and it is important
that the overall runtime of the jobs be constrained as much as possible.
Consider applying the ``@decorators.attr(type='slow')``
`test attribute decorator`_ to a test if its runtime is longer than 30 seconds.
.. _test attribute decorator: https://docs.openstack.org/tempest/latest/HACKING.html#test-attributes
Unit Tests
----------
For any change that adds new functionality to common functionality unit tests
are required. This is to ensure we don't introduce future regressions and to
test conditions which we may not hit in the gate runs.
API Stability
-------------
Tests should only be added for published stable APIs. If a patch contains
tests for an API which hasn't been marked as stable or for an API which
doesn't conform to the `API stability guidelines
<https://wiki.openstack.org/wiki/Governance/Approved/APIStability>`_ then it
should not be approved.
Similarly, tests should only be added for policies that are covered by
`policy in code documentation
<https://specs.openstack.org/openstack/keystone-specs/specs/keystone/pike/policy-in-code.html>`_.
Any existing tests that test policies not covered by such documentation
are either:
* part of a service that has not yet migrated to policy in code; or
* legacy in the sense that they were created prior to policy in code
For the first bullet, the tests should not be considered stable, but should be
kept around to maintain coverage. These tests are a best-effort attempt at
offering RBAC test coverage for the service that has not yet migrated to
policy in code.
For the second bullet, the tests should be updated to conform to policy in
code documentation, if applicable.
Reject Copy and Paste Test Code
-------------------------------
When creating new tests that are similar to existing tests it is tempting to
simply copy the code and make a few modifications. This increases code size and
the maintenance burden. Such changes should not be approved if it is easy to
abstract the duplicated code into a function or method.
Tests overlap
-------------
When a new test is being proposed, question whether this feature is not already
tested with Patrole. Patrole has more than 600 tests, spread amongst many
directories, so it's easy to introduce test duplication.
Test Duplication
^^^^^^^^^^^^^^^^
Test duplication means:
* testing an API endpoint in more than one test
* testing the same policy in more than one test
For the first bullet, try to avoid calling the same API inside the
``self.override_role()`` call.
.. note::
If the same API is tested against different policies, consider combining
the different tests into only 1 test, that tests the API against all
the policies it enforces.
For the second bullet, try to avoid testing the same policies across multiple
tests.
.. note::
This is not always possible since policy granularity doesn't exist for all
APIs. In cases where policy granularity doesn't exist, make sure that the
policy overlap only exists for the non-granular APIs that enforce the same
policy.
Being explicit
--------------
When tests are being added that depend on a configurable feature or extension,
polling the API to discover that it is enabled should not be done. This will
just result in bugs being masked because the test can be skipped automatically.
Instead the config file should be used to determine whether a test should be
skipped or not. Do not approve changes that depend on an API call to determine
whether to skip or not.
Multi-Policy Guidelines
-----------------------
Care should be taken when using multiple policies in an RBAC test. The
following guidelines should be followed before deciding to add multiple
policies to a Patrole test.
.. _general-multi-policy-guidelines:
General Multi-policy API Code Guidelines
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The list below enumerates guidelines beginning with those with the highest
priority and ending with those with the lowest priority. A higher priority
item takes precedence over lower priority items.
#. Order the policies in the ``rules`` parameter chronologically with respect
to the order they're called by the API endpoint under test.
#. Only use policies that map to the API by referencing the appropriate policy
in code documentation.
#. Only include the minimum number of policies needed to test the API
correctly: don't add extraneous policies.
#. If possible, only use policies that directly relate to the API. If the
policies are used across multiple APIs, try to omit it. If a "generic"
policy needs to be added to get the test to pass, then this is fair game.
#. Limit the number of policies to a reasonable number, such as 3.
Neutron Multi-policy API Code Guidelines
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Because Neutron can raise a 403 or 404 following failed authorization, Patrole
uses the ``expected_error_codes`` parameter to accommodate this behavior.
Each policy action enumerated in ``rules`` must have a corresponding entry
in ``expected_error_codes``. Each expected error code must be either a 403 or a
404, which indicates that, when policy enforcement fails for the corresponding
policy action, that error code is expected by Patrole. For more information
about these parameters, see :ref:`rbac-validation`.
The list below enumerates additional multi-policy guidelines that apply in
particular to Neutron. A higher priority item takes precedence over lower
priority items.
#. Order the expected error codes in the ``expected_error_codes`` parameter
chronologically with respect to the order each corresponding policy in
``rules`` is authorized by the API under test.
#. Ensure the :ref:`neutron-multi-policy-validation` is followed when
determining the expected error code for each corresponding policy.
The same guidelines under :ref:`general-multi-policy-guidelines` should be
applied afterward.
Release Notes
-------------
Release notes are how we indicate to users and other consumers of Patrole what
has changed in a given release. There are certain types of changes that
require release notes and we should not approve them without including a release
note. These include but aren't limited to, any addition, deprecation or removal
from the framework code, any change to configuration options (including
deprecation), major feature additions, and anything backwards incompatible or
would require a user to take note or do something extra.
Deprecated Code
---------------
Sometimes we have some bugs in deprecated code. Basically, we leave it. Because
we don't need to maintain it. However, if the bug is critical, we might need to
fix it. When it will happen, we will deal with it on a case-by-case basis.
When to approve
---------------
* Every patch can be approved with single +2 which means single reviewer can approve.
* It's OK to hold off on an approval until a subject matter expert reviews it.
* If a patch has already been approved but requires a trivial rebase to merge,
you do not have to wait for a +2, since the patch has already had +2's. With
single +2 rule, this means that author can also approve this case if he/she has
approve rights.

View File

@ -1,2 +0,0 @@
[python: **.py]

View File

@ -1,25 +0,0 @@
====================
Enabling in Devstack
====================
.. warning::
The ``stack.sh`` script must be run in a disposable VM that is not
being created automatically. See the `README file`_ in the DevStack
repository for more information.
1. Download DevStack::
git clone https://git.openstack.org/openstack-dev/devstack.git
cd devstack
2. Patrole can be installed like any other DevStack plugin by including the
``enable_plugin`` directive inside local.conf::
> cat local.conf
[[local|localrc]]
enable_plugin patrole https://git.openstack.org/openstack/patrole
3. Run ``stack.sh`` found in the DevStack repo.
.. _README file: https://git.openstack.org/cgit/openstack-dev/devstack/plain/README.rst

View File

@ -1,179 +0,0 @@
#!/usr/bin/env bash
# Plugin file for Patrole Tempest plugin
# --------------------------------------
# Dependencies:
# ``functions`` file
# ``DEST`` must be defined
# Save trace setting
XTRACE=$(set +o | grep xtrace)
set -o xtrace
function install_patrole_tempest_plugin {
setup_package $PATROLE_DIR -e
if [[ ${DEVSTACK_SERIES} == 'pike' ]]; then
IFS=',' read -ra roles_array <<< "$RBAC_TEST_ROLES"
RBAC_TEST_ROLES=""
for i in "${roles_array[@]}"; do
if [[ $i == "member" ]]; then
i="Member"
fi
RBAC_TEST_ROLES="$i,$RBAC_TEST_ROLES"
done
# Policies used by Patrole testing that were changed in a backwards-incompatible way.
# TODO(felipemonteiro): Remove these once stable/pike becomes EOL.
iniset $TEMPEST_CONFIG policy-feature-enabled create_port_fixed_ips_ip_address_policy False
iniset $TEMPEST_CONFIG policy-feature-enabled update_port_fixed_ips_ip_address_policy False
iniset $TEMPEST_CONFIG policy-feature-enabled limits_extension_used_limits_policy False
iniset $TEMPEST_CONFIG policy-feature-enabled volume_extension_volume_actions_attach_policy False
iniset $TEMPEST_CONFIG policy-feature-enabled volume_extension_volume_actions_reserve_policy False
iniset $TEMPEST_CONFIG policy-feature-enabled volume_extension_volume_actions_unreserve_policy False
# TODO(cl566n): Remove these once stable/pike becomes EOL.
# These policies were removed in Stein but are available in Pike.
iniset $TEMPEST_CONFIG policy-feature-enabled removed_nova_policies_stein False
iniset $TEMPEST_CONFIG policy-feature-enabled removed_keystone_policies_stein False
iniset $TEMPEST_CONFIG policy-feature-enabled added_cinder_policies_stein False
# TODO(rb560u): Remove this once stable/pike becomes EOL.
# Make the 'test_list_trusts' test backwards compatible.
# The Keystone Trust API is enforced differently depending on passed
# arguments
iniset $TEMPEST_CONFIG policy-feature-enabled keystone_policy_enforcement_train False
# TODO(rb560u): Remove this once stable/pike becomes EOL.
# These policies were removed in Ussuri but are available in Pike.
iniset $TEMPEST_CONFIG policy-feature-enabled changed_nova_policies_ussuri False
iniset $TEMPEST_CONFIG policy-feature-enabled changed_nova_policies_victoria False
# TODO(gmann): Remove these once stable/victoria becomes EOL.
# These policies were removed in Wallaby.
iniset $TEMPEST_CONFIG policy-feature-enabled removed_nova_policies_wallaby False
fi
if [[ ${DEVSTACK_SERIES} == 'queens' ]]; then
IFS=',' read -ra roles_array <<< "$RBAC_TEST_ROLES"
RBAC_TEST_ROLES=""
for i in "${roles_array[@]}"; do
if [[ $i == "member" ]]; then
i="Member"
fi
RBAC_TEST_ROLES="$i,$RBAC_TEST_ROLES"
done
# TODO(cl566n): Remove these once stable/queens becomes EOL.
# These policies were removed in Stein but are available in Queens.
iniset $TEMPEST_CONFIG policy-feature-enabled removed_nova_policies_stein False
iniset $TEMPEST_CONFIG policy-feature-enabled removed_keystone_policies_stein False
iniset $TEMPEST_CONFIG policy-feature-enabled added_cinder_policies_stein False
# TODO(rb560u): Remove this once stable/queens becomes EOL.
# Make the 'test_list_trusts' test backwards compatible.
# The Keystone Trust API is enforced differently depending on passed
# arguments
iniset $TEMPEST_CONFIG policy-feature-enabled keystone_policy_enforcement_train False
# TODO(rb560u): Remove this once stable/queens becomes EOL.
# These policies were removed in Ussuri but are available in Queens.
iniset $TEMPEST_CONFIG policy-feature-enabled changed_nova_policies_ussuri False
iniset $TEMPEST_CONFIG policy-feature-enabled changed_nova_policies_victoria False
# TODO(gmann): Remove these once stable/victoria becomes EOL.
# These policies were removed in Wallaby.
iniset $TEMPEST_CONFIG policy-feature-enabled removed_nova_policies_wallaby False
# TODO(gmann): Remove these once stable/xena becomes EOL.
iniset $TEMPEST_CONFIG policy-feature-enabled changed_cinder_policies_xena False
fi
if [[ ${DEVSTACK_SERIES} == 'rocky' ]]; then
# TODO(cl566n): Policies used by Patrole testing. Remove these once stable/rocky becomes EOL.
iniset $TEMPEST_CONFIG policy-feature-enabled removed_nova_policies_stein False
iniset $TEMPEST_CONFIG policy-feature-enabled added_cinder_policies_stein False
iniset $TEMPEST_CONFIG policy-feature-enabled removed_keystone_policies_stein False
# TODO(rb560u): Remove this once stable/rocky becomes EOL.
# Make the 'test_list_trusts' test backwards compatible.
# The Keystone Trust API is enforced differently depending on passed
# arguments
iniset $TEMPEST_CONFIG policy-feature-enabled keystone_policy_enforcement_train False
# TODO(rb560u): Remove this once stable/rocky becomes EOL.
# These policies were removed in Ussuri but are available in Rocky.
iniset $TEMPEST_CONFIG policy-feature-enabled changed_nova_policies_ussuri False
iniset $TEMPEST_CONFIG policy-feature-enabled changed_nova_policies_victoria False
# TODO(gmann): Remove these once stable/victoria becomes EOL.
# These policies were removed in Wallaby.
iniset $TEMPEST_CONFIG policy-feature-enabled removed_nova_policies_wallaby False
# TODO(gmann): Remove these once stable/xena becomes EOL.
iniset $TEMPEST_CONFIG policy-feature-enabled changed_cinder_policies_xena False
fi
if [[ ${DEVSTACK_SERIES} == 'stein' ]]; then
# TODO(rb560u): Remove this once stable/stein becomes EOL.
# Make the 'test_list_trusts' test backwards compatible.
# The Keystone Trust API is enforced differently depending on passed
# arguments
iniset $TEMPEST_CONFIG policy-feature-enabled keystone_policy_enforcement_train False
# TODO(rb560u): Remove this once stable/stein becomes EOL.
# These policies were removed in Ussuri but are available in Stein.
iniset $TEMPEST_CONFIG policy-feature-enabled changed_nova_policies_ussuri False
iniset $TEMPEST_CONFIG policy-feature-enabled changed_nova_policies_victoria False
# TODO(gmann): Remove these once stable/victoria becomes EOL.
# These policies were removed in Wallaby.
iniset $TEMPEST_CONFIG policy-feature-enabled removed_nova_policies_wallaby False
# TODO(gmann): Remove these once stable/xena becomes EOL.
iniset $TEMPEST_CONFIG policy-feature-enabled changed_cinder_policies_xena False
fi
if [[ ${DEVSTACK_SERIES} == 'train' ]]; then
# Remove this once stable/train becomes EOL.
iniset $TEMPEST_CONFIG policy-feature-enabled changed_nova_policies_ussuri False
iniset $TEMPEST_CONFIG policy-feature-enabled changed_nova_policies_victoria False
# TODO(gmann): Remove these once stable/victoria becomes EOL.
# These policies were removed in Wallaby.
iniset $TEMPEST_CONFIG policy-feature-enabled removed_nova_policies_wallaby False
# TODO(gmann): Remove these once stable/xena becomes EOL.
iniset $TEMPEST_CONFIG policy-feature-enabled changed_cinder_policies_xena False
fi
if [[ ${DEVSTACK_SERIES} == 'ussuri' ]]; then
# Remove this once stable/ussuri becomes EOL.
iniset $TEMPEST_CONFIG policy-feature-enabled changed_nova_policies_victoria False
# TODO(gmann): Remove these once stable/victoria becomes EOL.
# These policies were removed in Wallaby.
iniset $TEMPEST_CONFIG policy-feature-enabled removed_nova_policies_wallaby False
# TODO(gmann): Remove these once stable/xena becomes EOL.
iniset $TEMPEST_CONFIG policy-feature-enabled changed_cinder_policies_xena False
fi
if [[ ${DEVSTACK_SERIES} == 'victoria' ]]; then
# TODO(gmann): Remove these once stable/victoria becomes EOL.
# These policies were removed in Wallaby.
iniset $TEMPEST_CONFIG policy-feature-enabled removed_nova_policies_wallaby False
# TODO(gmann): Remove these once stable/xena becomes EOL.
iniset $TEMPEST_CONFIG policy-feature-enabled changed_cinder_policies_xena False
fi
if [[ ${DEVSTACK_SERIES} == 'wallaby' ]]; then
# TODO(gmann): Remove these once stable/xena becomes EOL.
iniset $TEMPEST_CONFIG policy-feature-enabled changed_cinder_policies_xena False
fi
iniset $TEMPEST_CONFIG patrole rbac_test_roles $RBAC_TEST_ROLES
}
if is_service_enabled tempest; then
if [[ "$1" == "stack" && "$2" == "test-config" ]]; then
echo_summary "Installing Patrole Tempest plugin"
install_patrole_tempest_plugin
fi
fi
# Restore xtrace
$XTRACE

View File

@ -1,8 +0,0 @@
# Settings needed for the Patrole Tempest plugin
# ----------------------------------------------
PATROLE_DIR=$DEST/patrole
TEMPEST_DIR=$DEST/tempest
TEMPEST_CONFIG_DIR=${TEMPEST_CONFIG_DIR:-$TEMPEST_DIR/etc}
TEMPEST_CONFIG=$TEMPEST_CONFIG_DIR/tempest.conf
RBAC_TEST_ROLE=${RBAC_TEST_ROLE:-admin}

View File

@ -1,8 +0,0 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
sphinx>=2.0.0,!=2.1.0 # BSD
openstackdocstheme>=2.2.0 # Apache-2.0
reno>=3.1.0 # Apache-2.0
sphinxcontrib-apidoc>=0.2.0 # BSD
sphinxcontrib-svg2pdfconverter>=0.1.0 # BSD

View File

@ -1,5 +0,0 @@
====================
Patrole Coding Guide
====================
.. include:: ../../HACKING.rst

View File

@ -1,5 +0,0 @@
======================
Reviewing Patrole Code
======================
.. include:: ../../REVIEWING.rst

View File

@ -1,110 +0,0 @@
# -*- coding: utf-8 -*-
# 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.
import os
import sys
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath('../../'))
sys.path.insert(0, os.path.abspath('../'))
sys.path.insert(0, os.path.abspath('./'))
# -- General configuration ----------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.todo',
'sphinx.ext.viewcode',
'sphinxcontrib.rsvgconverter',
'openstackdocstheme',
'oslo_config.sphinxconfiggen',
'sphinxcontrib.apidoc',
]
# sphinxcontrib.apidoc options
apidoc_module_dir = '../../patrole_tempest_plugin'
apidoc_output_dir = 'framework/code'
apidoc_excluded_paths = [
'hacking',
'hacking/*',
'tests',
'tests/*',
'config.py',
'plugin.py',
'version.py'
]
apidoc_separate_modules = True
config_generator_config_file = '../../etc/config-generator.patrole.conf'
sample_config_basename = '_static/patrole'
# autodoc generation is a bit aggressive and a nuisance when doing heavy
# text edit cycles.
# execute "export SPHINX_DEBUG=1" in your terminal to disable
# The suffix of source filenames.
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# General information about the project.
copyright = u'2017, Patrole Developers'
# If true, '()' will be appended to :func: etc. cross-reference text.
add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
add_module_names = True
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# -- Options for HTML output --------------------------------------------------
# The theme to use for HTML and HTML Help pages. Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'.
# html_theme_path = ["."]
html_theme = 'openstackdocs'
# openstackdocstheme options
openstackdocs_repo_name = 'openstack/patrole'
openstackdocs_pdf_link = True
openstackdocs_bug_project = 'patrole'
openstackdocs_bug_tag = ''
# Output file base name for HTML help builder.
htmlhelp_basename = 'patroledoc'
# Example configuration for intersphinx: refer to the Python standard library.
#intersphinx_mapping = {'http://docs.python.org/': None}
# -- Options for LaTeX output -------------------------------------------------
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass
# [howto/manual]).
latex_documents = [
('index', 'doc-patrole.tex', u'Patrole: Tempest Plugin for RBAC Testing',
u'OpenStack Foundation', 'manual'),
]
# Disable usage of xindy https://bugzilla.redhat.com/show_bug.cgi?id=1643664
latex_use_xindy = False

View File

@ -1,94 +0,0 @@
.. _patrole-configuration:
Patrole Configuration Guide
===========================
Patrole can be customized by updating Tempest's ``tempest.conf`` configuration
file. All Patrole-specific configuration options should be included under
the ``patrole`` group.
RBAC Test Roles
---------------
The RBAC test roles govern the list of roles to be used when running Patrole
tests. For example, setting ``rbac_test_roles`` to "admin" will execute all
RBAC tests using admin credentials. Changing the ``rbac_test_roles`` value
will `override` Tempest's primary credentials to use that role.
This implies that, if ``rbac_test_roles`` is "admin", regardless of the Tempest
credentials used by a client, the client will be calling APIs using the admin
role. That is, ``self.os_primary.servers_client`` will run as though it were
``self.os_admin.servers_client``.
Similarly, setting ``rbac_test_roles`` with various roles, results in
Tempest's primary credentials being overridden by the roles specified by
``rbac_test_roles``.
.. note::
Only the roles of the primary Tempest credentials ("os_primary") are
modified. The ``user_id`` and ``project_id`` remain unchanged.
Custom Policy Files
-------------------
Patrole supports testing custom policy file definitions, along with default
policy definitions. Default policy definitions are used if custom file
definitions are not specified. If both are specified, the custom policy
definition takes precedence (that is, replaces the default definition,
as this is the default behavior in OpenStack).
The ``custom_policy_files`` option allows a user to specify a comma-separated
list of custom policy file locations that are on the same host as Patrole.
Each policy file must include the name of the service that is being tested:
for example, if "compute" tests are executed, then Patrole will use the first
policy file contained in ``custom_policy_files`` that contains the "nova"
keyword.
.. note::
Patrole currently does not support policy files located on a host different
than the one on which it is running.
Policy Feature Flags
--------------------
Patrole's ``[policy-feature-enabled]`` configuration group includes one option
per supported policy feature flag. These feature flags are introduced when an
OpenStack service introduces a new policy or changes a policy in a
backwards-incompatible way. Since Patrole is branchless, it copes with the
unexpected policy change by making the relevant policy change as well, but
also introduces a new policy feature flag so that the test won't break N-1/N-2
releases where N is the currently supported release.
The default value for the feature flag is enabled for N and disabled for any
releases prior to N in which the feature is not available. This is done by
overriding the default value of the feature flag in DevStack's ``lib/patrole``
installation script. The change is made in Tempest's DevStack script because
Patrole's DevStack plugin is hosted in-repo, which is branch-less (whereas
the former is branched).
After the backwards-incompatible change no longer affects any supported
release, then the corresponding policy feature flag is removed.
For more information on feature flags, reference the relevant
`Tempest documentation`_.
.. _Tempest documentation: https://docs.openstack.org/tempest/latest/HACKING.html#new-tests-for-new-features
Sample Configuration File
-------------------------
The following is a sample Patrole configuration for adaptation and use. It is
auto-generated from Patrole when this documentation is built, so
if you are having issues with an option, please compare your version of
Patrole with the version of this documentation.
Note that the Patrole configuration options actually live inside the Tempest
configuration file; at runtime, Tempest populates its own configuration
file with Patrole groups and options, assuming that Patrole is correctly
installed and recognized as a plugin.
The sample configuration can also be viewed in `file form <_static/patrole.conf.sample>`_.
.. literalinclude:: _static/patrole.conf.sample

View File

@ -1,57 +0,0 @@
============================
So You Want to Contribute...
============================
For general information on contributing to OpenStack, please check out the
`contributor guide <https://docs.openstack.org/contributors/>`_ to get started.
It covers all the basics that are common to all OpenStack projects: the accounts
you need, the basics of interacting with our Gerrit review system, how we
communicate as a community, etc.
Below will cover the more project specific information you need to get started
with Tempest.
Communication
~~~~~~~~~~~~~
* IRC channel ``#openstack-qa`` at OFTC
* Mailing list (prefix subjects with ``[qa]`` for faster responses)
http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-discuss
Contacting the Core Team
~~~~~~~~~~~~~~~~~~~~~~~~
Please refer to the `Patrole Core Team
<https://review.opendev.org/#/admin/groups/1673,members>`_ contacts.
New Feature Planning
~~~~~~~~~~~~~~~~~~~~
If you want to propose a new feature please read `Feature Proposal Process`_
Patrole features are tracked on `Storyboard <https://storyboard.openstack.org/#!/project/1040>`_.
Task Tracking
~~~~~~~~~~~~~
We track our tasks in `Storyboard <https://storyboard.openstack.org/#!/project/1040>`_.
If you're looking for some smaller, easier work item to pick up and get started
on, search for the 'low-hanging-fruit' tag.
Reporting a Bug
~~~~~~~~~~~~~~~
You found an issue and want to make sure we are aware of it? You can do so on
`StoryBoard <https://storyboard.openstack.org/#!/project/1040>`_.
Getting Your Patch Merged
~~~~~~~~~~~~~~~~~~~~~~~~~
All changes proposed to the Patrole requires one ``Code-Review +2`` votes from
Patrole core reviewers before one of the core reviewers can approve patch by
giving ``Workflow +1`` vote. More detailed guidelines for reviewers are available
at :doc:`../REVIEWING`.
Project Team Lead Duties
~~~~~~~~~~~~~~~~~~~~~~~~
All common PTL duties are enumerated in the `PTL guide
<https://docs.openstack.org/project-team-guide/ptl.html>`_.
The Release Process for QA is documented in `QA Release Process
<https://wiki.openstack.org/wiki/QA/releases>`_.
.. _Feature Proposal Process: https://wiki.openstack.org/wiki/QA#Feature_Proposal_.26_Design_discussions

View File

@ -1,60 +0,0 @@
.. _patrole-field-guide:
============================
Patrole Field Guide Overview
============================
Testing Scope
=============
Patrole testing scope is strictly confined to Role-Based Access Control
(RBAC). In OpenStack, ``oslo.policy`` is the RBAC library used by all
major services. Thus, Patrole is concerned with validating that public API
endpoints are correctly using ``oslo.policy`` for authorization.
In other words, all tests in Patrole are RBAC tests.
:ref:`rbac_field_guide`
=======================
RBAC tests are `Tempest`_-like API tests plus Patrole's
:ref:`rbac-validation`. All Patrole tests are RBAC validation tests for the
OpenStack API.
.. _Tempest: https://docs.openstack.org/tempest/latest/
Stable Tests
============
In the discussion below, "correct" means that a test is consistent with
a service's API-to-policy mapping and "stable" means that a test should
require minimal maintenance for the supported releases.
Present
-------
During the Queens release, a `governance spec`_ was pushed to support policy
in code, which documents the mapping between APIs and each of their policies.
This documentation is an important prerequisite for ensuring that Patrole
tests for a given service are correct. This mapping can be referenced to
confirm that Patrole's assumed mapping for a test is correct. For
example, Nova has implemented policy in code which can be used to verify
that Patrole's Nova RBAC tests use the same mapping.
If a given service does not have policy in code, this implies that it is
*more likely* that the RBAC tests for that service are inconsistent with the
*intended* policy mapping. Until that service implements policy in code, it
is difficult for Patrole maintainers to verify that tests for that service
are correct.
Future
------
Once all services that Patrole tests have implemented policy in code --
and once Patrole has updated all its tests in accordance with the policy in
code documentation -- then Patrole tests can guaranteed to be stable.
This stability will be denoted with a 1.0 version release.
.. _governance spec: https://governance.openstack.org/tc/goals/queens/policy-in-code.html

View File

@ -1,118 +0,0 @@
.. _rbac_field_guide:
Patrole Field Guide to RBAC Tests
=================================
What are these tests?
---------------------
Patrole's primary responsibility is to ensure that your OpenStack cloud
has properly configured Role-Based Access Control (RBAC). All Patrole
tests cases are devoted to this responsibility. Tempest API clients
and utility functions are leveraged to accomplish this goal, but such
functionality is secondary to RBAC validation.
Like Tempest, Patrole not only tests expected positive paths for RBAC
validation, but also -- and more importantly -- negative paths. While
Patrole could be thought of as validating RBAC, it more importantly
verifies that your OpenStack cloud is secure from the perspective of
RBAC (there are many gotchas when it comes to security, not just RBAC).
Negative paths are arguably more important than positive paths when it
comes to RBAC and by extension security, because it is essential that
your cloud be secure from unauthorized access. For example, while it is
important to verify that the admin role has access to admin-level
functionality, it is of critical importance to verify that non-admin roles
*do not* have access to such functionality.
Unlike Tempest, Patrole accomplishes negative testing implicitly -- by
abstracting it away in the background. Patrole dynamically determines
whether a role should have access to an API depending on your cloud's
policy configuration and then confirms whether that is true or false.
Why are these tests in Patrole?
-------------------------------
These tests constitute the core mission in Patrole: to verify RBAC. These
tests are mainly intended to validate RBAC, but can also *unofficially*
be used to discover the policy-to-API mapping for an OpenStack component.
It could be argued that some of these tests could be implemented in
the projects themselves, but that approach has the following shortcomings:
* The projects do not validate RBAC from an integration testing perspective.
* By extension, RBAC across cross-service communication is not usually
validated.
* The projects' tests do not pass all the metadata to ``oslo.policy`` that is
in reality passed by the deployed server to that library to determine
whether a given user is authorized to perform an API action.
* The projects do not exhaustively do RBAC testing for all positive and
negative paths.
* Patrole is designed to work with any role via configuration settings, but
on the other hand the projects handpick which roles to test.
Why not use Patrole framework on Tempest tests?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The Patrole framework can't be applied to existing Tempest tests via
:ref:`rbac-validation`, because:
* Tempest tests aren't factored the right way: They're not granular enough.
They call too many APIs and too many policies are enforced by each test.
* Tempest tests assume default policy rules: Tempest uses ``os_admin``
`credentials`_ for admin APIs and ``os_primary`` for non-admin APIs.
This breaks for custom policy overrides.
* Tempest doesn't have tests that enforce all the policy actions, regardless.
Some RBAC tests require that tests be written a very precise way for the
server to authorize the expected policy actions.
Why are these tests not in Tempest?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Patrole should be a separate project that specializes in RBAC tests. This
was agreed upon during `discussion`_ that led to the approval of the RBAC
testing framework `spec`_, which was the genesis for Patrole.
Philosophically speaking:
* Tempest supports `API and scenario testing`_. RBAC testing is out of scope.
* The `OpenStack project structure reform`_ evolved OpenStack "to a more
decentralized model where [projects like QA] provide processes and tools to
empower projects to do the work themselves". This model resulted in the
creation of the `Tempest external plugin interface`_.
* Tempest supports `plugins`_. Why not use one for RBAC testing?
Practically speaking:
* The Tempest team should not be burdened with having to support Patrole, too.
Tempest is a big project and having to absorb RBAC testing is difficult.
* Tempest already has many in-tree Zuul checks/gates. If Patrole tests lived
in Tempest, then adding more Zuul checks/gates for Patrole would only make it
harder to get changes merged in Tempest.
.. _credentials: https://docs.openstack.org/tempest/latest/write_tests.html#allocating-credentials
.. _discussion: https://review.openstack.org/#/c/382672/
.. _spec: https://specs.openstack.org/openstack/qa-specs/specs/tempest/rbac-policy-testing.html
.. _API and scenario testing: https://docs.openstack.org/tempest/latest/overview.html#tempest-the-openstack-integration-test-suite
.. _OpenStack project structure reform: https://governance.openstack.org/tc/resolutions/20141202-project-structure-reform-spec.html#impact-for-horizontal-teams
.. _Tempest external plugin interface: https://specs.openstack.org/openstack/qa-specs/specs/tempest/implemented/tempest-external-plugin-interface.html
.. _plugins: https://docs.openstack.org/tempest/latest/plugin.html
Scope of these tests
--------------------
RBAC tests should always use the Tempest implementation of the
OpenStack API, to take advantage of Tempest's stable library.
Each test should test a specific API endpoint and the related policy.
Each policy should be tested in isolation of one another -- or at least
as close to this rule as possible -- to ensure proper validation of RBAC.
Each test should be able to work for positive and negative paths.
All tests should be able to be run on their own, not depending on the
state created by a previous test.

View File

@ -1,81 +0,0 @@
RBAC Testing Validation
=======================
.. _validation-workflow-overview:
----------------------------
Validation Workflow Overview
----------------------------
RBAC testing validation is broken up into 3 stages:
#. "Expected" stage. Determine whether the test should be able to succeed
or fail based on the test roles defined by ``[patrole] rbac_test_roles``)
and the policy action that the test enforces.
#. "Actual" stage. Run the test by calling the API endpoint that enforces
the expected policy action using the test roles.
#. Comparing the outputs from both stages for consistency. A "consistent"
result is treated as a pass and an "inconsistent" result is treated
as a failure. "Consistent" (or successful) cases include:
* Expected result is ``True`` and the test passes.
* Expected result is ``False`` and the test fails.
For example, a 200 from the API call and a ``True`` result from
``oslo.policy`` or a 403 from the API call and a ``False`` result from
``oslo.policy`` are successful results.
"Inconsistent" (or failing) cases include:
* Expected result is ``False`` and the test passes. This results in an
:class:`~rbac_exceptions.RbacOverPermissionException` exception
getting thrown.
* Expected result is ``True`` and the test fails. This results in a
:class:`~rbac_exceptions.RbacOverPermissionException` exception
getting thrown.
For example, a 200 from the API call and a ``False`` result from
``oslo.policy`` or a 403 from the API call and a ``True`` result from
``oslo.policy`` are failing results.
.. warning::
Note that Patrole cannot currently derive the expected policy result for
service-specific ``oslo.policy`` `checks`_, like Neutron's `FieldCheck`_,
because such checks are contained within the service's code base itself,
which Patrole cannot import.
.. _checks: https://docs.openstack.org/oslo.policy/latest/reference/api/oslo_policy.policy.html#generic-checks
.. _FieldCheck: https://docs.openstack.org/neutron/pike/contributor/internals/policy.html#fieldcheck-verify-resource-attributes
-------------------------------
The RBAC Rule Validation Module
-------------------------------
High-level module that provides the decorator that wraps around Tempest tests
and serves as the entry point for RBAC testing validation. The workflow
described above is ultimately carried out by the decorator.
For more information about this module, please see :ref:`rbac-validation`.
---------------------------
The Policy Authority Module
---------------------------
Module called by :ref:`rbac-validation` to verify whether the test
roles are allowed to execute a policy action by querying ``oslo.policy`` with
required test data. The result is used by :ref:`rbac-validation` as the
"Expected" result.
For more information about this module, please see :ref:`policy-authority`.
---------------------
The RBAC Utils Module
---------------------
This module is responsible for handling role switching, the mechanism by which
Patrole is able to set up, tear down and execute APIs using the same set
of credentials. Every RBAC test must perform a role switch even if the role
that is being switched to is admin.
For more information about this module, please see :ref:`rbac-utils`.

View File

@ -1,60 +0,0 @@
.. _policy-authority:
Policy Authority Module
=======================
Overview
--------
This module is only called for calculating the "Expected" result if
``[patrole] test_custom_requirements`` is ``False``.
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,
if the policy file exists and the custom policy definition is explicitly
defined therein.
#. Confirming that the policy action -- for example, "list_users" -- exists.
(``oslo.policy`` otherwise claims that role "foo" is allowed to
perform policy action "bar", for example, because it defers to the
"default" policy rule and oftentimes the default can be "anyone allowed").
#. 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
--------------
:py:mod:`Policy Authority Module <patrole_tempest_plugin.policy_authority>`

View File

@ -1,35 +0,0 @@
.. 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
--------------
:py:mod:`RBAC Authority Module <patrole_tempest_plugin.rbac_authority>`

View File

@ -1,33 +0,0 @@
.. _rbac-utils:
RBAC Utils Module
=================
Overview
--------
Patrole manipulates the ``os_primary`` `Tempest credentials`_, which are the
primary set of Tempest credentials. It is necessary to use the same credentials
across the entire test setup/test execution/test teardown workflow
because otherwise 400-level errors will be thrown by OpenStack services.
This is because many services check the request context's project scope -- and
in very rare cases, user scope. However, each set of Tempest credentials (via
`dynamic credentials`_) is allocated its own distinct project. For example, the
``os_admin`` and ``os_primary`` credentials each have a distinct project,
meaning that it is not always possible for the ``os_primary`` credentials to
access resources created by the ``os_admin`` credentials.
The only foolproof solution is to manipulate the role for the same set of
credentials, rather than using distinct credentials for setup/teardown
and test execution, respectively. This is especially true when considering
custom policy rule definitions, which can be arbitrarily complex.
Implementation
--------------
:py:mod:`RBAC Utils Module <patrole_tempest_plugin.rbac_utils>`
.. _Tempest credentials: https://docs.openstack.org/tempest/latest/library/credential_providers.html
.. _dynamic credentials: https://docs.openstack.org/tempest/latest/configuration.html#dynamic-credentials

View File

@ -1,17 +0,0 @@
.. _rbac-validation:
RBAC Rule Validation Module
===========================
Overview
--------
Module that implements the decorator which serves as the entry point for
RBAC validation testing. The decorator should be applied to every RBAC test
with the appropriate ``service`` (OpenStack service) and ``rule`` (OpenStack
policy name defined by the ``service``).
Implementation
--------------
:py:mod:`RBAC Rule Validation Module <patrole_tempest_plugin.rbac_rule_validation>`

View File

@ -1,156 +0,0 @@
.. _requirements-authority:
Requirements Authority Module
=============================
Overview
--------
Requirements-driven approach to declaring the expected RBAC test results
referenced by Patrole. These requirements express the *intention* behind the
policy. A high-level YAML syntax is used to concisely and clearly map each
policy action to the list of associated roles.
.. 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.
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>:
<logical_or_example>:
- <allowed_role_1>
- <allowed_role_2>
<logical_and_example>:
- <allowed_role_3>, <allowed_role_4>
<service_bar>:
<logical_not_example>:
- <!disallowed_role_5>
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.
Each item under ``logical_or_example`` is "logical OR"-ed together. Each role
in the comma-separated string under ``logical_and_example`` is "logical AND"-ed
together. And each item prefixed with "!" under ``logical_not_example`` is
"logical negated".
.. note::
The custom requirements file only allows policy actions to be mapped to
the associated roles that define it. Complex ``oslo.policy`` constructs
like ``literals`` or ``GenericChecks`` are not supported. For more
information, reference the `oslo.policy documentation`_.
.. _oslo.policy documentation: https://docs.openstack.org/oslo.policy/latest/reference/api/oslo_policy.policy.html#policy-rule-expressions
Examples
~~~~~~~~
Items within ``api_action`` are considered as logical or, so you may read:
.. code-block:: yaml
<service_foo>:
# "api_action_a: allowed_role_1 or allowed_role_2 or allowed_role_3"
<api_action_a>:
- <allowed_role_1>
- <allowed_role_2>
- <allowed_role_3>
as ``<allowed_role_1> or <allowed_role_2> or <allowed_role_3>``.
Roles within comma-separated items are considered as logic and, so you may
read:
.. code-block:: yaml
<service_foo>:
# "api_action_a: (allowed_role_1 and allowed_role_2) or allowed_role_3"
<api_action_a>:
- <allowed_role_1>, <allowed_role_2>
- <allowed_role_3>
as ``<allowed_role_1> and <allowed_role_2> or <allowed_role_3>``.
Also negative roles may be defined with an exclamation mark ahead of role:
.. code-block:: yaml
<service_foo>:
# "api_action_a: (allowed_role_1 and allowed_role_2 and not
# disallowed_role_4) or allowed_role_3"
<api_action_a>:
- <allowed_role_1>, <allowed_role_2>, !<disallowed_role_4>
- <allowed_role_3>
This example must be read as ``<allowed_role_1> and <allowed_role_2> and not
<disallowed_role_4> or <allowed_role_3>``.
Implementation
--------------
:py:mod:`Requirements Authority Module <patrole_tempest_plugin.requirements_authority>`

View File

@ -1,92 +0,0 @@
========================================
Patrole: Tempest Plugin for RBAC Testing
========================================
Overview
========
.. toctree::
:maxdepth: 2
overview
RBAC Overview
-------------
.. toctree::
:maxdepth: 2
rbac-overview
multi-policy-validation
User's Guide
============
Patrole Configuration Guide
---------------------------
.. toctree::
:maxdepth: 2
configuration
Patrole Installation Guide
--------------------------
.. toctree::
:maxdepth: 2
installation
Field Guides
============
.. toctree::
:maxdepth: 1
field_guide/index
field_guide/rbac
For Contributors
================
* If you are a new contributor to Patrole please refer: :doc:`contributor/contributing`
.. toctree::
:hidden:
contributor/contributing
Developer's Guide
=================
.. toctree::
:maxdepth: 1
HACKING
REVIEWING
test_writing_guide
Framework
---------
.. toctree::
:maxdepth: 3
framework/overview
framework/rbac_validation
framework/rbac_authority
framework/policy_authority
framework/requirements_authority
framework/rbac_utils
framework/code/modules
Search
======
.. only:: html
* :ref:`Patrole document search <search>`: Search the contents of this document.
* `OpenStack wide search <https://docs.openstack.org>`_: Search the wider
set of OpenStack documentation, including forums.

View File

@ -1,29 +0,0 @@
.. _patrole-installation:
==========================
Patrole Installation Guide
==========================
Manual Installation Information
===============================
At the command line::
$ git clone http://git.openstack.org/openstack/patrole
$ sudo pip install ./patrole
Or, if you have virtualenvwrapper installed::
$ mkvirtualenv patrole_env
$ workon patrole_env
$ pip install ./patrole
Or to install from the source::
$ navigate to patrole directory
$ sudo pip install -e .
DevStack Installation
=====================
.. include:: ../../devstack/README.rst

View File

@ -1,187 +0,0 @@
.. _multi-policy-validation:
=======================
Multi-policy Validation
=======================
Introduction
------------
Multi-policy validation exists in Patrole because if one policy were assumed,
then tests could fail because they would not consider all the policies actually
being enforced. The reasoning can be found in `this spec`_. Basically,
since Patrole derives the expected test result dynamically in order to test any
role, each policy enforced by the API under test must be considered to derive
an accurate expected test result, or else the expected and actual test
results will not always match, resulting in overall test failure. For more
information about Patrole's RBAC validation work flow, reference
:ref:`rbac-validation`.
Multi-policy support allows Patrole to more accurately offer RBAC tests for API
endpoints that enforce multiple policy actions.
.. _this spec: http://specs.openstack.org/openstack/qa-specs/specs/patrole/rbac-testing-multiple-policies.html
Scope
-----
Multiple policies should be applied only to tests that require them. Not all
API endpoints enforce multiple policies. Some services consistently enforce
1 policy per API, while on the other side of the spectrum, services like
Neutron have much more involved policy enforcement work flows. See
:ref:`neutron-multi-policy-validation` for more information.
.. _neutron-multi-policy-validation:
Neutron Multi-policy Validation
-------------------------------
Neutron can raise different :ref:`policy-error-codes` following failed policy
authorization. Many endpoints in Neutron enforce multiple policies, which
complicates matters when trying to determine whether the endpoint raises a
403 or a 404 following unauthorized access.
Multi-policy Examples
---------------------
General Examples
^^^^^^^^^^^^^^^^
Below is an example of multi-policy validation for a carefully chosen Nova API:
.. code-block:: python
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-lock-server:unlock",
"os_compute_api:os-lock-server:unlock:unlock_override"])
@decorators.idempotent_id('40dfeef9-73ee-48a9-be19-a219875de457')
def test_unlock_server_override(self):
"""Test force unlock server, part of os-lock-server.
In order to trigger the unlock:unlock_override policy instead
of the unlock policy, the server must be locked by a different
user than the one who is attempting to unlock it.
"""
self.os_admin.servers_client.lock_server(self.server['id'])
self.addCleanup(self.servers_client.unlock_server, self.server['id'])
with self.override_role():
self.servers_client.unlock_server(self.server['id'])
While the ``expected_error_codes`` parameter is omitted in the example above,
Patrole automatically populates it with a 403 for each policy in ``rules``.
Therefore, in the example above, the following expected error codes/rules
relationship is observed:
* "os_compute_api:os-lock-server:unlock" => 403
* "os_compute_api:os-lock-server:unlock:unlock_override" => 403
Below is an example that uses ``expected_error_codes`` to account for the
fact that Neutron is expected to raise a ``404`` on the first policy that
is enforced server-side ("get_port"). Also, in this example, soft authorization
is performed, meaning that it is necessary to check the response body for an
attribute that is added only following successful policy authorization.
.. code-block:: python
@utils.requires_ext(extension='binding', service='network')
@rbac_rule_validation.action(service="neutron",
rules=["get_port",
"get_port:binding:vif_type"],
expected_error_codes=[404, 403])
@decorators.idempotent_id('125aff0b-8fed-4f8e-8410-338616594b06')
def test_show_port_binding_vif_type(self):
# Verify specific fields of a port
fields = ['binding:vif_type']
with self.override_role():
retrieved_port = self.ports_client.show_port(
self.port['id'], fields=fields)['port']
# Rather than throwing a 403, the field is not present, so raise exc.
if fields[0] not in retrieved_port:
raise rbac_exceptions.RbacMalformedResponse(
attribute='binding:vif_type')
Note that in the example above, failure to authorize
"get_port:binding:vif_type" results in the response body getting successfully
returned by the server, but without additional dictionary keys. If Patrole
fails to find those expected keys, it *acts as though* a 403 was thrown (by
raising an exception itself, the ``rbac_rule_validation`` decorator handles
the rest).
Neutron Examples
^^^^^^^^^^^^^^^^
A basic Neutron example that only expects 403's to be raised:
.. code-block:: python
@utils.requires_ext(extension='external-net', service='network')
@rbac_rule_validation.action(service="neutron",
rules=["create_network",
"create_network:router:external"],
expected_error_codes=[403, 403])
@decorators.idempotent_id('51adf2a7-739c-41e0-8857-3b4c460cbd24')
def test_create_network_router_external(self):
"""Create External Router Network Test
RBAC test for the neutron create_network:router:external policy
"""
with self.override_role():
self._create_network(router_external=True)
Note that above the following expected error codes/rules relationship is
observed:
* "create_network" => 403
* "create_network:router:external" => 403
A more involved example that expects a 404 to be raised, should the first
policy under ``rules`` fail authorization, and a 403 to be raised for any
subsequent policy authorization failure:
.. code-block:: python
@rbac_rule_validation.action(service="neutron",
rules=["get_network",
"update_network",
"update_network:shared"],
expected_error_codes=[404, 403, 403])
@decorators.idempotent_id('37ea3e33-47d9-49fc-9bba-1af98fbd46d6')
def test_update_network_shared(self):
"""Update Shared Network Test
RBAC test for the neutron update_network:shared policy
"""
with self.override_role():
self._update_network(shared_network=True)
self.addCleanup(self._update_network, shared_network=False)
Note that above the following expected error codes/rules relationship is
observed:
* "get_network" => 404
* "update_network" => 403
* "update_network:shared" => 403
Limitations
-----------
Multi-policy validation in RBAC tests comes with limitations, due to technical
and practical challenges.
Technically, there are challenges associated with multiple policies across
cross-service API communication in OpenStack, such as between Nova and Cinder
or Nova and Neutron. The current framework does not account for these
cross-service policy enforcement workflows, and it is still up for debate
whether it should.
Practically, it is not possible to enumerate every policy enforced by every API
in Patrole, as the maintenance overhead would be huge.
.. _Neutron policy documentation: https://docs.openstack.org/neutron/pike/contributor/internals/policy.html

View File

@ -1 +0,0 @@
../../README.rst

View File

@ -1,285 +0,0 @@
.. _rbac-overview:
==================================
Role-Based Access Control Overview
==================================
Introduction
------------
Role-Based Access Control (RBAC) is used by most OpenStack services to control
user access to resources. Authorization is granted if a user has the necessary
role to perform an action. Patrole is concerned with validating that each of
these resources *can* be accessed by authorized users and *cannot* be accessed
by unauthorized users.
OpenStack services use `oslo.policy`_ as the library for RBAC authorization.
Patrole relies on the same library for deriving expected test results.
.. _policy-in-code:
Policy in Code
--------------
Services publish their policy-to-API mapping via policy in code documentation.
This mapping includes the list of APIs that authorize a policy, for each
policy declared within a service.
For example, Nova's policy in code documentation is located in the
`Nova repository`_ under ``nova/policies``. Likewise, Keystone's policy in
code documentation is located in the `Keystone repository`_ under
``keystone/common/policies``. The other OpenStack services follow the same
directory layout pattern with respect to policy in code.
The policy in code `governance goal`_ enumerates many advantages with following
this RBAC design approach. A so-called library of in-code policies offers the
following advantages, with respect to facilitating validation:
* includes every policy enforced by an OpenStack service, enabling the
possibility of complete Patrole test coverage for that service (otherwise
one has to read the source code to discover all the policies)
* provides the policy-to-API mapping for each policy which can be used
to write correct Patrole tests (otherwise reading source code and
experimentation are required to derive this mapping)
* by extension, the policy-to-API mapping facilitates writing multi-policy
Patrole tests (otherwise even more experimentation and code reading is
required to arrive at all the policies enforced by an API)
* policy in code documentation includes additional information, like
descriptions and (in the case of some services, like Keystone)
`scope types`_, which help with understanding how to correctly write
Patrole tests
* by extension, such information helps to determine whether a Patrole test
should assume :term:`hard authorization` or :term:`soft authorization`
Policy in Code (Default) Validation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
By default, Patrole validates default OpenStack policies. This is so that
the out-of-the-box defaults are sanity-checked, to ensure that OpenStack
services are secure, from an RBAC perspective, for each release.
Patrole strives to validate RBAC by using the policy in code documentation,
wherever possible. See :ref:`validation-workflow-overview` for more details.
.. _custom-policies:
Custom Policies
---------------
Operators can override policy in code defaults using `policy.yaml`_. While
this allows operators to offer more fine-grained RBAC control to their tenants,
it opens the door to misconfiguration and bugs. Patrole can be used to validate
that custom policy overrides don't break anything and work as expected.
Custom Policy Validation
^^^^^^^^^^^^^^^^^^^^^^^^
While testing default policy behavior is a valid use case, oftentimes default
policies are modified with custom overrides in production. OpenStack's
`policy.yaml`_ documentation claims that "modifying policy can have unexpected
side effects", which is why Patrole was created: to ensure that custom
overrides allow the principle of least privilege to be tailor-made to exact
specifications via policy overrides, without:
* causing unintended side effects (breaking API endpoints, breaking
cross-service workflows, breaking the policy file itself); or
* resulting in poor RBAC configuration, promoting security vulnerabilities
This has implications on Patrole's :ref:`design-principles`: validating custom
overrides requires the ability to handle arbitrary roles, which requires logic
capable of dynamically determining expected test behavior.
Note that support for custom policies is limited. This is because custom
policies can be arbitrarily complex, requiring that tests be very robust
in order to handle all edge cases.
.. _multiple-policies:
Multiple Policies
-----------------
Behind the scenes, many APIs enforce multiple policies, for many reasons,
including:
* to control complex cross-service workflows;
* to control whether a server is booted from an image or booted from a volume
(for example);
* to control whether a response body should contain additional information
conditioned upon successful policy authorization.
This makes `policy in code`_ especially important for policy validation: it
is difficult to keep track of all the policies being enforced across all the
individual APIs, without policy in code documentation.
Multi-Policy Validation
^^^^^^^^^^^^^^^^^^^^^^^
Patrole offers support for validating APIs that enforce multiple policies.
Perhaps in an ideal world each API endpoint would enforce only one policy,
but in reality some API endpoints enforce multiple policies. Thus, to offer
accurate validation, Patrole handles multiple policies:
* for services *with* policy in code documentation: this documentation
indicates that a single API endpoint enforces multiple policy actions.
* for services *without* policy in code documentation: the API code clearly
shows multiple policy actions being validated. Note that in this case some
degree of log tracing is required by developers to confirm that the expected
policies are getting enforced, prior to the tests getting merged.
For more information, see :ref:`multi-policy-validation`.
.. _policy-error-codes:
Error Codes
-----------
Most OpenStack services raise a ``403 Forbidden`` following failed
:term:`hard authorization`. Neutron, however, can raise a ``404 NotFound``
as well. See Neutron's `authorization policy enforcement`_ documentation
for more details.
Admin Context Policy
--------------------
The so-called "admin context" policy refers to the following policy definition
(using the legacy policy file syntax):
.. code-block:: javascript
{
"context_is_admin": "role:admin"
...
}
Which is unfortunately used to bypass ``oslo.policy`` authorization checks,
for example:
.. code-block:: python
# This function is responsible for calling oslo.policy to check whether
# requests are authorized to perform an API action.
def enforce(context, action, target, [...]):
# Here this condition, if True, skips over the enforce call below which
# is what calls oslo.policy.
if context.is_admin:
return True
_ENFORCER.enforce([...]) # This is what can be skipped over.
[...]
This type of behavior is currently present in many services. Unless such
logic is removed in the future for services that implement it, Patrole
won't really be able to validate that admin role works from an ``oslo.policy``
perspective.
Glossary
--------
The following nomenclature is used throughout Patrole documentation so it is
important to understand what each term means in order to understand concepts
related to RBAC in Patrole.
.. glossary::
authorize
The act of ``oslo.policy`` determining whether a user can perform a
:term:`policy` given his or her :term:`role`.
enforce
See :term:`authorize`.
hard authorization
The `do_raise`_ flag controls whether policy authorization should result
in an exception getting raised or a boolean value getting returned.
Hard authorization results in an exception getting raised. Usually, this
results in a ``403 Forbidden`` getting returned for unauthorized requests.
(See :ref:`policy-error-codes` for further details.)
Related term: :term:`soft authorization`.
oslo.policy
The OpenStack library providing support for RBAC policy enforcement across
all OpenStack services. See the `official documentation`_ for more
information.
policy
Defines an RBAC rule. Each policy is defined by a one-line statement in
the form "<target>" : "<rule>". For more information, reference OpenStack's
`policy documentation`_.
policy action
See :term:`policy target`.
policy file
Prior to `governance goal`_ used by all OpenStack services to define
policy defaults. Still used by some services, which is why Patrole
needs to read the policy files to derive policy information for testing.
policy in code
Registers default OpenStack policies for a service in the service's code
base.
Beginning with the Queens release, policy in code became a
`governance goal`_.
policy rule
The policy rule determines under which circumstances the API call is
permitted.
policy target
The name of a policy.
requirements file
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. See :ref:`requirements-authority`
for more information.
role
A designation for the set of actions that describe what a user can do in
the system. Roles are managed through the `Keystone Roles API`_.
Role-Based Access Control (RBAC)
May be formally defined as "an approach to restricting system access to
authorized users."
rule
See :term:`policy rule`. Note that currently the Patrole code base
conflates "rule" with :term:`policy target` in some places.
soft authorization
The `do_raise`_ flag controls whether policy authorization should result
in an exception getting raised or a boolean value getting returned.
Soft authorization results in a boolean value getting returned. When policy
authorization evaluates to true, additional operations are performed as a
part of the API request or additional information is included in the
response body (see `response filtering`_ for an example).
Related term: :term:`hard authorization`.
.. _Nova repository: https://git.openstack.org/cgit/openstack/nova/tree/nova/policies
.. _Keystone repository: https://git.openstack.org/cgit/openstack/keystone/tree/keystone/common/policies
.. _governance goal: https://governance.openstack.org/tc/goals/queens/policy-in-code.html
.. _scope types: https://docs.openstack.org/keystone/latest/admin/tokens-overview.html#authorization-scopes
.. _policy.yaml: https://docs.openstack.org/ocata/config-reference/policy-yaml-file.html
.. _oslo.policy: https://docs.openstack.org/oslo.policy/latest/
.. _policy documentation: https://docs.openstack.org/kilo/config-reference/content/policy-json-file.html
.. _do_raise: https://docs.openstack.org/oslo.policy/latest/reference/api/oslo_policy.policy.html#oslo_policy.policy.Enforcer.enforce
.. _authorization policy enforcement: https://docs.openstack.org/neutron/latest/contributor/internals/policy.html
.. _official documentation: https://docs.openstack.org/oslo.policy/latest/
.. _Keystone Roles API: https://docs.openstack.org/api-ref/identity/v3/#roles
.. _response filtering: https://docs.openstack.org/neutron/latest/contributor/internals/policy.html#response-filtering

View File

@ -1,166 +0,0 @@
Patrole Test Writing Overview
=============================
Introduction
------------
Patrole tests are broken up into 3 stages:
#. :ref:`rbac-test-setup`
#. :ref:`rbac-test-execution`
#. :ref:`rbac-test-cleanup`
See the :ref:`framework overview documentation <validation-workflow-overview>`
for a high-level explanation of the entire testing work flow and framework
implementation. The guide that follows is concerned with helping developers
know how to write Patrole tests.
.. _role-overriding:
Role Overriding
---------------
Role overriding is the way Patrole is able to create resources and delete
resources -- including those that require admin credentials -- while still
being able to exercise the same set of Tempest credentials to perform the API
action that authorizes the policy under test, by manipulating roles of the
Tempest credentials.
Patrole implicitly splits up each test into 3 stages: set up, test execution,
and teardown.
The role workflow is as follows:
#. Setup: Admin role is used automatically. The primary credentials are
overridden with the admin role.
#. Test execution: ``[patrole] rbac_test_roles`` is used manually via the
call to ``with self.override_role()``. Everything that
is executed within this contextmanager uses the primary
credentials overridden with the ``[patrole] rbac_test_roles``.
#. Teardown: Admin role is used automatically. The primary credentials have
been overridden with the admin role.
.. _rbac-test-setup:
Test Setup
----------
Automatic role override in background.
Resources can be set up inside the ``resource_setup`` class method that Tempest
provides. These resources are typically reserved for "expensive" resources
in terms of memory or storage requirements, like volumes and VMs. These
resources are **always** created via the admin role; Patrole automatically
handles this.
Like Tempest, however, Patrole must also create resources inside tests
themselves. At the beginning of each test, the primary credentials have already
been overridden with the admin role. One can create whatever test-level
resources one needs, without having to worry about permissions.
.. _rbac-test-execution:
Test Execution
--------------
Manual role override required.
"Test execution" here means calling the API endpoint that enforces the policy
action expected by the ``rbac_rule_validation`` decorator. Test execution
should be performed *only after* calling
``with self.override_role()``.
Immediately after that call, the API endpoint that enforces the policy should
be called.
Examples
^^^^^^^^
Always use the contextmanager before calling the API that enforces the
expected policy action.
Example::
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-aggregates:show"])
def test_show_aggregate_rbac(self):
# Do test setup before the ``override_role`` call.
aggregate_id = self._create_aggregate()
# Call the ``override_role`` method so that the primary credentials
# have the test role needed for test execution.
with self.override_role():
self.aggregates_client.show_aggregate(aggregate_id)
When using a waiter, do the wait outside the contextmanager. "Waiting" always
entails executing a ``GET`` request to the server, until the state of the
returned resource matches a desired state. These ``GET`` requests enforce
a different policy than the one expected. This is undesirable because
Patrole should only test policies in isolation from one another.
Otherwise, the test result will be tainted, because instead of only the
expected policy getting enforced with the ``os_primary`` role, at least
two policies get enforced.
Example using waiter::
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-admin-password"])
def test_change_server_password(self):
original_password = self.servers_client.show_password(
self.server['id'])
self.addCleanup(self.servers_client.change_password, self.server['id'],
adminPass=original_password)
with self.override_role():
self.servers_client.change_password(
self.server['id'], adminPass=data_utils.rand_password())
# Call the waiter outside the ``override_role`` contextmanager, so that
# it is executed with admin role.
waiters.wait_for_server_status(
self.servers_client, self.server['id'], 'ACTIVE')
Below is an example of a method that enforces multiple policies getting
called inside the contextmanager. The ``_complex_setup_method`` below
performs the correct API that enforces the expected policy -- in this
case ``self.resources_client.create_resource`` -- but then proceeds to
use a waiter.
Incorrect::
def _complex_setup_method(self):
resource = self.resources_client.create_resource(
**kwargs)['resource']
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self._delete_resource, resource)
waiters.wait_for_resource_status(
self.resources_client, resource['id'], 'available')
return resource
@rbac_rule_validation.action(
service="example-service",
rules=["example-rule"])
def test_change_server_password(self):
# Never call a helper function inside the contextmanager that calls a
# bunch of APIs. Only call the API that enforces the policy action
# contained in the decorator above.
with self.override_role():
self._complex_setup_method()
To fix this test, see the "Example using waiter" section above. It is
recommended to re-implement the logic in a helper method inside a test such
that only the relevant API is called inside the contextmanager, with
everything extraneous outside.
.. _rbac-test-cleanup:
Test Cleanup
------------
Automatic role override in background.
After the test -- no matter whether it ended successfully or in failure --
the credentials are overridden with the admin role by the Patrole framework,
*before* ``tearDown`` or ``tearDownClass`` are called. This means that
resources are always cleaned up using the admin role.

View File

@ -1,3 +0,0 @@
[DEFAULT]
output_file = etc/patrole.conf.sample
namespace = patrole.config

View File

@ -1,172 +0,0 @@
[DEFAULT]
[patrole]
#
# From patrole.config
#
# DEPRECATED: The current RBAC role against which to run
# Patrole tests. (string value)
# This option is deprecated for removal.
# Its value may be silently ignored in the future.
# Reason: This option is deprecated and being
# replaced with ``rbac_test_roles``.
#rbac_test_role =
# The current RBAC roles to be assigned to Keystone
# Group against which to run Patrole tests. (list value)
#rbac_test_roles = admin
# List of the paths to search for policy files. Each
# policy path assumes that the service name is included in the path
# once. Also
# assumes Patrole is on the same host as the policy files. The paths
# should be
# ordered by precedence, with high-priority paths before low-priority
# paths. All
# the paths that are found to contain the service's policy file will
# be used and
# all policy files will be merged. Allowed ``json`` or ``yaml``
# formats.
# (list value)
#custom_policy_files = /etc/%s/policy.json
#
# 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
# is in accordance to requirements. The idea is that the
# ``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``:
#
# YAML definition: allowed
# test run: allowed
# test result: pass
#
# YAML definition: allowed
# test run: not allowed
# test result: fail (under-permission)
#
# YAML definition: not allowed
# test run: allowed
# test result: fail (over-permission)
# (boolean value)
#test_custom_requirements = false
#
# 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.
# (string value)
#custom_requirements_file = <None>
[patrole_log]
#
# From patrole.config
#
# Enables reporting on RBAC expected and actual test results for each
# Patrole test (boolean value)
#enable_reporting = false
# Name of file where output from 'enable_reporting' is logged. Note
# that this file is recreated on each invocation of patrole (string
# value)
#report_log_name = patrole.log
# Path (relative or absolute) where the output from 'enable_reporting'
# is logged. This is combined withreport_log_name to generate the full
# path. (string value)
#report_log_path = .
[policy-feature-enabled]
#
# From patrole.config
#
# Is the Neutron policy
# "create_port:fixed_ips:ip_address" available in the cloud? This
# policy was
# changed in a backwards-incompatible way. (boolean value)
#create_port_fixed_ips_ip_address_policy = true
# Is the Neutron policy
# "update_port:fixed_ips:ip_address" available in the cloud? This
# policy was
# changed in a backwards-incompatible way. (boolean value)
#update_port_fixed_ips_ip_address_policy = true
# Is the Cinder policy
# "limits_extension:used_limits" available in the cloud? This policy
# was
# changed in a backwards-incompatible way. (boolean value)
#limits_extension_used_limits_policy = true
# Is the Cinder policy
# "volume_extension:volume_actions:attach" available in the cloud?
# This policy
# was changed in a backwards-incompatible way. (boolean value)
#volume_extension_volume_actions_attach_policy = true
# Is the Cinder policy
# "volume_extension:volume_actions:reserve" available in the cloud?
# This policy
# was changed in a backwards-incompatible way. (boolean value)
#volume_extension_volume_actions_reserve_policy = true
# Is the Cinder policy
# "volume_extension:volume_actions:unreserve" available in the cloud?
# This policy
# was changed in a backwards-incompatible way. (boolean value)
#volume_extension_volume_actions_unreserve_policy = true
# 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. (boolean value)
#removed_nova_policies_stein = true
# Are the Cinder API extension policies available in the
# cloud (e.g. [create|update|get|delete]_encryption_policy)? These
# policies are
# added in Stein. (boolean value)
#added_cinder_policies_stein = true

View File

@ -1,76 +0,0 @@
alabaster==0.7.10
appdirs==1.4.3
asn1crypto==0.24.0
Babel==2.5.3
bcrypt==3.1.4
certifi==2018.1.18
cffi==1.14.0
chardet==3.0.4
cliff==2.11.0
cmd2==0.8.1
coverage==4.0
cryptography==2.1.4
debtcollector==1.19.0
docutils==0.14
dulwich==0.19.0
extras==1.0.0
fasteners==0.14.1
fixtures==3.0.0
future==0.16.0
idna==2.6
imagesize==1.0.0
iso8601==0.1.12
Jinja2==2.10
jsonschema==2.6.0
keystoneauth1==3.4.0
linecache2==1.0.0
MarkupSafe==1.0
mccabe==0.2.1
mock==2.0.0
monotonic==1.4
mox3==0.25.0
msgpack==0.5.6
netaddr==0.7.19
netifaces==0.10.6
nose==1.3.7
nosexcover==1.0.10
os-client-config==1.29.0
oslo.concurrency==3.26.0
oslo.config==5.2.0
oslo.context==2.20.0
oslo.i18n==3.20.0
oslo.log==3.36.0
oslo.policy==1.30.0
oslo.serialization==2.25.0
oslo.utils==3.36.0
oslotest==3.2.0
paramiko==2.4.1
pbr==2.0.0
prettytable==0.7.2
pyasn1==0.4.2
pycparser==2.18
Pygments==2.2.0
pyinotify==0.9.6
PyNaCl==1.2.1
pyparsing==2.2.0
pyperclip==1.6.0
python-dateutil==2.7.0
python-mimeparse==1.6.0
python-subunit==1.2.0
pytz==2018.3
PyYAML==3.13
requests==2.18.4
requestsexceptions==1.4.0
rfc3986==1.1.0
six==1.11.0
snowballstemmer==1.2.1
stestr==2.0.0
stevedore==1.20.0
tempest==30.0.0
testrepository==0.0.20
testtools==2.3.0
traceback2==1.4.0
unittest2==1.1.0
urllib3==1.22
voluptuous==0.11.1
wrapt==1.10.11

View File

@ -1,229 +0,0 @@
# 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 oslo_config import cfg
patrole_group = cfg.OptGroup(name='patrole', title='Patrole Testing Options')
PatroleGroup = [
cfg.StrOpt('rbac_test_role',
default='admin',
deprecated_for_removal=True,
deprecated_reason="""This option is deprecated and being
replaced with ``rbac_test_roles``.
""",
help="""The current RBAC role against which to run
Patrole tests."""),
cfg.ListOpt('rbac_test_roles',
help="""List of the RBAC roles against which to run
Patrole tests.""",
default=['admin']),
cfg.ListOpt('custom_policy_files',
default=['/etc/%s/policy.json'],
help="""List of the paths to search for policy files. Each
policy path assumes that the service name is included in the path once. Also
assumes Patrole is on the same host as the policy files. The paths should be
ordered by precedence, with high-priority paths before low-priority paths. All
the paths that are found to contain the service's policy file will be used and
all policy files will be merged. Allowed ``json`` or ``yaml`` formats.
"""),
cfg.BoolOpt('test_custom_requirements',
default=False,
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
is in accordance to requirements. The idea is that the
``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``:
YAML definition: allowed
test run: allowed
test result: pass
YAML definition: allowed
test run: not allowed
test result: fail (under-permission)
YAML definition: not allowed
test run: allowed
test result: fail (over-permission)
"""),
cfg.StrOpt('custom_requirements_file',
help="""
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.
"""),
cfg.BoolOpt('validate_deprecated_rules', default=True,
help="""Some of the policy rules have deprecated version,
Patrole should be able to run check against default and deprecated rules,
otherwise the result of the tests may not be correct.
""")
]
patrole_log_group = cfg.OptGroup(
name='patrole_log', title='Patrole Logging Options')
PatroleLogGroup = [
cfg.BoolOpt('enable_reporting',
default=False,
help="Enables reporting on RBAC expected and actual test "
"results for each Patrole test"),
cfg.StrOpt('report_log_name',
default='patrole.log',
help="Name of file where output from 'enable_reporting' is "
"logged. Note that this file is recreated on each "
"invocation of patrole"),
cfg.StrOpt('report_log_path',
default='.',
help="Path (relative or absolute) where the output from "
"'enable_reporting' is logged. This is combined with "
"report_log_name to generate the full path."),
]
policy_feature_enabled = cfg.OptGroup(
name='policy-feature-enabled',
title='Feature Flags for New or Changed Policies')
PolicyFeatureEnabledGroup = [
# TODO(felipemonteiro): The 6 feature flags below should be removed after
# Pike is EOL.
cfg.BoolOpt('create_port_fixed_ips_ip_address_policy',
default=True,
help="""Is the Neutron policy
"create_port:fixed_ips:ip_address" available in the cloud? This policy was
changed in a backwards-incompatible way."""),
cfg.BoolOpt('update_port_fixed_ips_ip_address_policy',
default=True,
help="""Is the Neutron policy
"update_port:fixed_ips:ip_address" available in the cloud? This policy was
changed in a backwards-incompatible way."""),
cfg.BoolOpt('limits_extension_used_limits_policy',
default=True,
help="""Is the Cinder policy
"limits_extension:used_limits" available in the cloud? This policy was
changed in a backwards-incompatible way."""),
cfg.BoolOpt('volume_extension_volume_actions_attach_policy',
default=True,
help="""Is the Cinder policy
"volume_extension:volume_actions:attach" available in the cloud? This policy
was changed in a backwards-incompatible way."""),
cfg.BoolOpt('volume_extension_volume_actions_reserve_policy',
default=True,
help="""Is the Cinder policy
"volume_extension:volume_actions:reserve" available in the cloud? This policy
was changed in a backwards-incompatible way."""),
cfg.BoolOpt('volume_extension_volume_actions_unreserve_policy',
default=True,
help="""Is the Cinder policy
"volume_extension:volume_actions:unreserve" available in the cloud? This policy
was changed in a backwards-incompatible way."""),
# *** Include feature flags for groups of policies below. ***
# Best practice is to capture new policies, removed policies, renamed
# policies in a group, per release.
#
# TODO(felipemonteiro): Remove these feature flags once Stein is EOL.
cfg.BoolOpt('removed_nova_policies_stein',
default=True,
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."""),
# TODO(gmann): Remove these feature flags once Victoria is EOL.
cfg.BoolOpt('removed_nova_policies_wallaby',
default=True,
help="""Are the Nova API policies being removed in wallaby
cycle (e.g. os_compute_api:os-agents)?"""),
cfg.BoolOpt('removed_keystone_policies_stein',
default=True,
help="""Are the obsolete Keystone policies available in the
cloud (e.g. identity:[create|update|get|delete]_credential)? These policies
were removed in Stein."""),
cfg.BoolOpt('added_cinder_policies_stein',
default=True,
help="""Are the Cinder Stein policies available in the cloud
(e.g. [create|update|get|delete]_encryption_policy)? These policies are added
in Stein."""),
cfg.BoolOpt('keystone_policy_enforcement_train',
default=True,
help="""Is the cloud running the Train release or newer? If
so, the Keystone Trust API is enforced differently depending on passed
arguments"""),
cfg.BoolOpt('changed_nova_policies_ussuri',
default=True,
help="""Are the Nova API policies available in the
cloud (e.g. os_compute_api:os-services)? These policies were
changed in Ussuri."""),
cfg.BoolOpt('changed_nova_policies_victoria',
default=True,
help="""Are the Nova deprecated API policies available in the
cloud (e.g. os_compute_api:os-networks)? These policies were
changed in Victoria."""),
cfg.BoolOpt('changed_cinder_policies_xena',
default=True,
help="""Are the Cinder API policies changed in the
cloud (e.g. 'group:group_types_specs')? These policies were
changed in Xena.""")
]
def list_opts():
"""Return a list of oslo.config options available.
The purpose of this is to allow tools like the Oslo sample config file
generator to discover the options exposed to users.
"""
opt_list = [
(patrole_group, PatroleGroup),
(patrole_log_group, PatroleLogGroup),
(policy_feature_enabled, PolicyFeatureEnabledGroup)
]
return opt_list

View File

@ -1,249 +0,0 @@
# Copyright 2013 IBM Corp.
# 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.
import os
import re
from hacking import core
import pycodestyle
PYTHON_CLIENTS = ['cinder', 'glance', 'keystone', 'nova', 'swift', 'neutron',
'ironic', 'heat', 'sahara']
PYTHON_CLIENT_RE = re.compile('import (%s)client' % '|'.join(PYTHON_CLIENTS))
TEST_DEFINITION = re.compile(r'^\s*def test.*')
SETUP_TEARDOWN_CLASS_DEFINITION = re.compile(r'^\s+def (setUp|tearDown)Class')
SCENARIO_DECORATOR = re.compile(r'\s*@.*services\((.*)\)')
RAND_NAME_HYPHEN_RE = re.compile(r".*rand_name\(.+[\-\_][\"\']\)")
MUTABLE_DEFAULT_ARGS = re.compile(r"^\s*def .+\((.+=\{\}|.+=\[\])")
TESTTOOLS_SKIP_DECORATOR = re.compile(r'\s*@testtools\.skip\((.*)\)')
CLASS = re.compile(r"^class .+")
RBAC_CLASS_NAME_RE = re.compile(r'class .+RbacTest')
RULE_VALIDATION_DECORATOR = re.compile(
r'\s*@rbac_rule_validation.action\(.*')
IDEMPOTENT_ID_DECORATOR = re.compile(r'\s*@decorators\.idempotent_id\((.*)\)')
EXT_RBAC_TEST = re.compile(
r"class .+\(.+ExtRbacTest\)|class .+ExtRbacTest\(.+\)")
have_rbac_decorator = False
@core.flake8ext
def import_no_clients_in_api_tests(physical_line, filename):
"""Check for client imports from patrole_tempest_plugin/tests/api
T102: Cannot import OpenStack python clients
"""
if "patrole_tempest_plugin/tests/api" in filename:
res = PYTHON_CLIENT_RE.match(physical_line)
if res:
return (physical_line.find(res.group(1)),
("T102: python clients import not allowed "
"in patrole_tempest_plugin/tests/api/* or "
"patrole_tempest_plugin/tests/scenario/* tests"))
@core.flake8ext
def no_setup_teardown_class_for_tests(physical_line, filename):
"""Check that tests do not use setUpClass/tearDownClass
T105: Tests cannot use setUpClass/tearDownClass
"""
if pycodestyle.noqa(physical_line):
return
if SETUP_TEARDOWN_CLASS_DEFINITION.match(physical_line):
return (physical_line.find('def'),
"T105: (setUp|tearDown)Class can not be used in tests")
@core.flake8ext
def service_tags_not_in_module_path(physical_line, filename):
"""Check that a service tag isn't in the module path
A service tag should only be added if the service name isn't already in
the module path.
T107
"""
matches = SCENARIO_DECORATOR.match(physical_line)
if matches:
services = matches.group(1).split(',')
for service in services:
service_name = service.strip().strip("'")
modulepath = os.path.split(filename)[0]
if service_name in modulepath:
return (physical_line.find(service_name),
"T107: service tag should not be in path")
@core.flake8ext
def no_hyphen_at_end_of_rand_name(logical_line, filename):
"""Check no hyphen at the end of rand_name() argument
T108
"""
msg = "T108: hyphen should not be specified at the end of rand_name()"
if RAND_NAME_HYPHEN_RE.match(logical_line):
return 0, msg
@core.flake8ext
def no_mutable_default_args(logical_line):
"""Check that mutable object isn't used as default argument
N322: Method's default argument shouldn't be mutable
"""
msg = "N322: Method's default argument shouldn't be mutable!"
if MUTABLE_DEFAULT_ARGS.match(logical_line):
yield (0, msg)
@core.flake8ext
def no_testtools_skip_decorator(logical_line):
"""Check that methods do not have the testtools.skip decorator
T109
"""
if TESTTOOLS_SKIP_DECORATOR.match(logical_line):
yield (0, "T109: Cannot use testtools.skip decorator; instead use "
"decorators.skip_because from tempest.lib")
@core.flake8ext
def use_rand_uuid_instead_of_uuid4(logical_line, filename):
"""Check that tests use data_utils.rand_uuid() instead of uuid.uuid4()
T113
"""
if 'uuid.uuid4()' not in logical_line:
return
msg = ("T113: Tests should use data_utils.rand_uuid()/rand_uuid_hex() "
"instead of uuid.uuid4()/uuid.uuid4().hex")
yield (0, msg)
@core.flake8ext
def no_rbac_rule_validation_decorator(physical_line, filename):
"""Check that each test has the ``rbac_rule_validation.action`` decorator.
Checks whether the test function has "@rbac_rule_validation.action"
above it; otherwise checks that it has "@decorators.idempotent_id" above
it and "@rbac_rule_validation.action" above that.
Assumes that ``rbac_rule_validation.action`` decorator is either the first
or second decorator above the test function; otherwise this check fails.
P100
"""
global have_rbac_decorator
if ("patrole_tempest_plugin/tests/api" in filename or
"patrole_tempest_plugin/tests/scenario" in filename):
if RULE_VALIDATION_DECORATOR.match(physical_line):
have_rbac_decorator = True
return
if TEST_DEFINITION.match(physical_line):
if not have_rbac_decorator:
return (0, "Must use rbac_rule_validation.action "
"decorator for API and scenario tests")
have_rbac_decorator = False
@core.flake8ext
def no_rbac_suffix_in_test_filename(filename):
"""Check that RBAC filenames end with "_rbac" suffix.
P101
"""
if "patrole_tempest_plugin/tests/api" in filename:
if filename.endswith('rbac_base.py'):
return
if not filename.endswith('_rbac.py'):
return 0, "RBAC test filenames must end in _rbac suffix"
@core.flake8ext
def no_rbac_test_suffix_in_test_class_name(physical_line, filename):
"""Check that RBAC class names end with "RbacTest"
P102
"""
if "patrole_tempest_plugin/tests/api" in filename:
if filename.endswith('rbac_base.py'):
return
if CLASS.match(physical_line):
if not RBAC_CLASS_NAME_RE.match(physical_line):
return 0, "RBAC test class names must end in 'RbacTest'"
@core.flake8ext
def no_client_alias_in_test_cases(logical_line, filename):
"""Check that test cases don't use "self.client" to define a client.
P103
"""
if "patrole_tempest_plugin/tests/api" in filename:
if "self.client" in logical_line or "cls.client" in logical_line:
return 0, "Do not use 'self.client' as a service client alias"
@core.flake8ext
def no_extension_rbac_test_suffix_in_plugin_test_class_name(physical_line,
filename):
"""Check that Extension RBAC class names end with "ExtRbacTest"
P104
"""
suffix = "ExtRbacTest"
if "patrole_tempest_plugin/tests/api" in filename:
if EXT_RBAC_TEST.match(physical_line):
subclass, superclass = physical_line.split('(')
subclass = subclass.split('class')[1].strip()
superclass = superclass.split(')')[0].strip()
if "." in superclass:
superclass = superclass.split(".")[1]
both_have = all(
clazz.endswith(suffix) for clazz in [subclass, superclass])
none_have = not any(
clazz.endswith(suffix) for clazz in [subclass, superclass])
if not (both_have or none_have):
if (subclass.startswith("Base") and
superclass.startswith("Base")):
return
# Case 1: Subclass of "BaseExtRbacTest" must end in `suffix`
# Case 2: Subclass that ends in `suffix` must inherit from base
# class ending in `suffix`.
if not subclass.endswith(suffix):
error = ("Plugin RBAC test subclasses must end in "
"'ExtRbacTest'")
return len(subclass) - 1, error
elif not superclass.endswith(suffix):
error = ("Plugin RBAC test subclasses must inherit from a "
"'ExtRbacTest' base class")
return len(superclass) - 1, error

View File

@ -1,86 +0,0 @@
# 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.
import logging
import os
from oslo_concurrency import lockutils
from tempest import config
from tempest.test_discover import plugins
from patrole_tempest_plugin import config as pconfig
RBACLOG = logging.getLogger('rbac_reporting')
class PatroleTempestPlugin(plugins.TempestPlugin):
def load_tests(self):
base_path = os.path.split(os.path.dirname(
os.path.abspath(__file__)))[0]
test_dir = "patrole_tempest_plugin/tests/api"
full_test_dir = os.path.join(base_path, test_dir)
return full_test_dir, base_path
@lockutils.synchronized('_reset_log_file')
def _reset_log_file(self, logfile):
try:
os.remove(logfile)
except OSError:
pass
def _configure_per_test_logging(self, conf):
# Separate log handler for rbac reporting
RBACLOG.setLevel(level=logging.INFO)
# Set up proper directory handling
report_abs_path = os.path.abspath(conf.patrole_log.report_log_path)
report_path = os.path.join(
report_abs_path, conf.patrole_log.report_log_name)
# Remove the log file if it exists
self._reset_log_file(report_path)
# Delay=True so that we don't end up creating an empty file if we
# never log to it.
rbac_report_handler = logging.FileHandler(
filename=report_path, delay=True, mode='a')
rbac_report_handler.setFormatter(
fmt=logging.Formatter(fmt='%(message)s'))
RBACLOG.addHandler(rbac_report_handler)
def register_opts(self, conf):
config.register_opt_group(
conf,
pconfig.patrole_group,
pconfig.PatroleGroup)
config.register_opt_group(
conf,
pconfig.patrole_log_group,
pconfig.PatroleLogGroup)
config.register_opt_group(
conf,
pconfig.policy_feature_enabled,
pconfig.PolicyFeatureEnabledGroup)
if conf.patrole_log.enable_reporting:
self._configure_per_test_logging(conf)
def get_opt_lists(self):
return [
(pconfig.patrole_group.name, pconfig.PatroleGroup),
(pconfig.policy_feature_enabled.name,
pconfig.PolicyFeatureEnabledGroup)
]

View File

@ -1,330 +0,0 @@
# 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.
import collections
import copy
import glob
import os
from oslo_log import log as logging
from oslo_policy import policy
import pkg_resources
import stevedore
from tempest import config
from patrole_tempest_plugin.rbac_authority import RbacAuthority
from patrole_tempest_plugin import rbac_exceptions
CONF = config.CONF
LOG = logging.getLogger(__name__)
class PolicyAuthority(RbacAuthority):
"""A class that uses ``oslo.policy`` for validating RBAC."""
os_admin = None
def __init__(self, project_id, user_id, service, extra_target_data=None):
"""Initialization of Policy Authority class.
Validates whether a test role can perform a policy action by querying
``oslo.policy`` with necessary test data.
If a policy file does not exist, checks whether the policy file is
registered as a namespace under "oslo.policy.policies". Nova, for
example, doesn't use a policy file by default; its policies are
implemented in code and registered as "nova" under
"oslo.policy.policies".
If the policy file is not found in either code or in a policy file,
then an exception is raised.
Additionally, if a custom policy file exists along with the default
policy in code implementation, the custom policy is prioritized.
:param uuid project_id: project_id of object performing API call
:param uuid user_id: user_id of object performing API call
:param string service: service of the policy file
:param dict extra_target_data: dictionary containing additional object
data needed by oslo.policy to validate generic checks
Example:
.. code-block:: python
# Below is the default policy implementation in code, defined in
# a service like Nova.
test_policies = [
policy.DocumentedRuleDefault(
'service:test_rule',
base.RULE_ADMIN_OR_OWNER,
"This is a description for a test policy",
[
{
'method': 'POST',
'path': '/path/to/test/resource'
}
]),
'service:another_test_rule',
base.RULE_ADMIN_OR_OWNER,
"This is a description for another test policy",
[
{
'method': 'GET',
'path': '/path/to/test/resource'
}
]),
]
.. code-block:: yaml
# Below is the custom override of the default policy in a YAML
# policy file. Note that the default rule is "rule:admin_or_owner"
# and the custom rule is "rule:admin_api". The `PolicyAuthority`
# class will use the "rule:admin_api" definition for this policy
# action.
"service:test_rule" : "rule:admin_api"
# Note below that no override is provided for
# "service:another_test_rule", which means that the default policy
# rule is used: "rule:admin_or_owner".
"""
if extra_target_data is None:
extra_target_data = {}
self.service = self.validate_service(service)
# Prioritize dynamically searching for policy files over relying on
# deprecated service-specific policy file locations.
if CONF.patrole.custom_policy_files:
self.discover_policy_files()
self.rules = self.get_rules()
self.project_id = project_id
self.user_id = user_id
self.extra_target_data = extra_target_data
@classmethod
def validate_service(cls, service):
"""Validate whether the service passed to ``__init__`` exists."""
service = service.lower().strip() if service else None
# Cache the list of available services in memory to avoid needlessly
# doing an API call every time.
if not hasattr(cls, 'available_services') and cls.os_admin:
services_client = (cls.os_admin.identity_services_v3_client
if CONF.identity_feature_enabled.api_v3
else cls.os_admin.identity_services_client)
services = services_client.list_services()['services']
cls.available_services = [s['name'] for s in services]
if not service or service not in cls.available_services:
LOG.debug("%s is NOT a valid service.", service)
raise rbac_exceptions.RbacInvalidServiceException(
"%s is NOT a valid service." % service)
return service
@classmethod
def discover_policy_files(cls):
"""Dynamically discover the policy file for each service in
``cls.available_services``. Pick all candidate paths found
out of the potential paths in ``[patrole] custom_policy_files``.
"""
if not hasattr(cls, 'policy_files'):
cls.policy_files = collections.defaultdict(list)
for service in cls.available_services:
for candidate_path in CONF.patrole.custom_policy_files:
path = candidate_path % service
for filename in glob.iglob(path):
if os.path.isfile(filename):
cls.policy_files[service].append(filename)
def allowed(self, rule_name, roles):
"""Checks if a given rule in a policy is allowed with given role.
:param string rule_name: Policy name to pass to``oslo.policy``.
:param List[string] roles: List of roles 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(roles)
is_allowed = self._allowed(
access=self._get_access_token(roles),
apply_rule=rule_name,
is_admin=is_admin_context)
return is_allowed
def _handle_deprecated_rule(self, default):
deprecated_rule = default.deprecated_rule
deprecated_msg = (
'Policy "%(old_name)s":"%(old_check_str)s" was deprecated in '
'%(release)s in favor of "%(name)s":"%(check_str)s". Reason: '
'%(reason)s. Either ensure your deployment is ready for the new '
'default or copy/paste the deprecated policy into your policy '
'file and maintain it manually.' % {
'old_name': deprecated_rule.name,
'old_check_str': deprecated_rule.check_str,
'release': default.deprecated_since,
'name': default.name,
'check_str': default.check_str,
'reason': default.deprecated_reason
}
)
LOG.warn(deprecated_msg)
oslo_policy_version = pkg_resources.parse_version(
pkg_resources.get_distribution("oslo.policy").version)
# NOTE(gmann): oslo policy 3.7.0 onwards does not allow to modify
# the Rule object check attribute.
required_version = pkg_resources.parse_version('3.7.0')
if oslo_policy_version >= required_version:
return policy.OrCheck([default.check, deprecated_rule.check])
else:
default.check = policy.OrCheck(
[policy._parser.parse_rule(cs) for cs in
[default.check_str,
deprecated_rule.check_str]])
return default.check
def get_rules(self):
rules = policy.Rules()
# Check whether policy file exists and attempt to read it.
for path in self.policy_files[self.service]:
try:
with open(path, 'r') as fp:
for k, v in policy.Rules.load(fp.read()).items():
if k not in rules:
rules[k] = v
# If the policy name and rule are the same, no
# ambiguity, so no reason to warn.
elif str(v) != str(rules[k]):
msg = ("The same policy name: %s was found in "
"multiple policies files for service %s. "
"This can lead to policy rule ambiguity. "
"Using rule: %s; Rule from file: %s")
LOG.warning(msg, k, self.service, rules[k], v)
except (ValueError, IOError):
LOG.warning("Failed to read policy file '%s' for service %s.",
path, self.service)
# Check whether policy actions are defined in code. Nova and Keystone,
# for example, define their default policy actions in code.
mgr = stevedore.named.NamedExtensionManager(
'oslo.policy.policies',
names=[self.service],
invoke_on_load=True,
warn_on_missing_entrypoint=False)
if mgr:
policy_generator = {plc.name: plc.obj for plc in mgr}
if self.service in policy_generator:
for rule in policy_generator[self.service]:
if rule.name not in rules:
if CONF.patrole.validate_deprecated_rules:
# NOTE (sergey.vilgelm):
# The `DocumentedRuleDefault` object has no
# `deprecated_rule` attribute in Pike
check = rule.check
if getattr(rule, 'deprecated_rule', False):
check = self._handle_deprecated_rule(rule)
rules[rule.name] = check
elif str(rule.check) != str(rules[rule.name]):
msg = ("The same policy name: %s was found in the "
"policies files and in the code for service "
"%s. This can lead to policy rule ambiguity. "
"Using rule: %s; Rule from code: %s")
LOG.warning(msg, rule.name, self.service,
rules[rule.name], rule.check)
if not rules:
msg = (
'Policy files for {0} service were not found among the '
'registered in-code policies or in any of the possible policy '
'files: {1}.'.format(
self.service,
[loc % self.service
for loc in CONF.patrole.custom_policy_files]))
raise rbac_exceptions.RbacParsingException(msg)
return rules
def _is_admin_context(self, roles):
"""Checks whether a role has admin context.
If context_is_admin is contained in the policy file, then checks
whether the given role is contained in context_is_admin. If it is not
in the policy file, then default to context_is_admin: admin.
"""
if 'context_is_admin' in self.rules:
return self._allowed(
access=self._get_access_token(roles),
apply_rule='context_is_admin')
return CONF.identity.admin_role in roles
def _get_access_token(self, roles):
access_token = {
"token": {
"roles": [{'name': r} for r in roles],
"project_id": self.project_id,
"tenant_id": self.project_id,
"user_id": self.user_id
}
}
return access_token
def _allowed(self, access, apply_rule, is_admin=False):
"""Checks if a given rule in a policy is allowed with given ``access``.
:param dict access: Dictionary from ``_get_access_token``.
:param string apply_rule: Rule to be checked using ``oslo.policy``.
:param bool is_admin: Whether admin context is used.
"""
access_data = copy.copy(access['token'])
access_data['roles'] = [role['name'] for role in access_data['roles']]
access_data['is_admin'] = is_admin
# TODO(felipemonteiro): Dynamically calculate is_admin_project rather
# than hard-coding it to True. is_admin_project cannot be determined
# from the role, but rather from project and domain names. For more
# information, see:
# https://git.openstack.org/cgit/openstack/keystone/tree/keystone/token/providers/common.py?id=37ce5417418f8acbd27f3dacb70c605b0fe48301#n150
access_data['is_admin_project'] = True
class Object(object):
pass
o = Object()
o.rules = self.rules
target = {"project_id": access_data['project_id'],
"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
def _try_rule(self, apply_rule, target, access_data, o):
if apply_rule not in self.rules:
message = ('Policy action "{0}" not found in policy files: '
'{1} or among registered policy in code defaults for '
'{2} service.').format(apply_rule,
self.policy_files[self.service],
self.service)
LOG.debug(message)
raise rbac_exceptions.RbacParsingException(message)
else:
rule = self.rules[apply_rule]
return rule(target, access_data, o)

View File

@ -1,38 +0,0 @@
# 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.
import abc
import six
@six.add_metaclass(abc.ABCMeta)
class RbacAuthority(object):
"""Class for validating whether a given role can perform a policy action.
Any class that extends ``RbacAuthority`` provides the logic for determining
whether a role has permissions to execute a policy action.
"""
@abc.abstractmethod
def allowed(self, rule, role):
"""Determine whether the role should be able to perform the API.
:param rule: The name of the policy enforced by the API.
:param role: The role used to determine whether ``rule`` can be
executed.
:returns: True if the ``role`` has permissions to execute
``rule``, else False.
"""

View File

@ -1,119 +0,0 @@
# 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.lib import exceptions
class BasePatroleException(exceptions.TempestException):
message = "An unknown RBAC exception occurred"
class BasePatroleResponseBodyException(BasePatroleException):
message = "Response body incomplete due to RBAC authorization failure"
class RbacMissingAttributeResponseBody(BasePatroleResponseBodyException):
"""Raised when a list or show action is missing an attribute following
RBAC authorization failure.
"""
message = ("The response body is missing the expected %(attribute)s due "
"to policy enforcement failure")
class RbacPartialResponseBody(BasePatroleResponseBodyException):
"""Raised when a list action only returns a subset of the available
resources.
For example, admin can return more resources than member for a list action.
"""
message = ("The response body only lists a subset of the available "
"resources due to partial policy enforcement failure. Response "
"body: %(body)s")
class RbacEmptyResponseBody(BasePatroleResponseBodyException):
"""Raised when a list or show action is empty following RBAC authorization
failure.
"""
message = "The response body is empty due to policy enforcement failure."
class RbacResourceSetupFailed(BasePatroleException):
message = "RBAC resource setup failed"
class RbacOverPermissionException(BasePatroleException):
"""Raised when the expected result is failure but the actual result is
pass.
"""
message = "Unauthorized action was allowed to be performed"
class RbacUnderPermissionException(BasePatroleException):
"""Raised when the expected result is pass but the actual result is
failure.
"""
message = "Authorized action was not allowed to be performed"
class RbacExpectedWrongException(BasePatroleException):
"""Raised when the expected exception does not match the actual exception
raised, when both are instances of Forbidden or NotFound, indicating
the test provides a wrong argument to `expected_error_codes`.
"""
message = ("Expected %(expected)s to be raised but %(actual)s was raised "
"instead. Actual exception: %(exception)s")
class RbacInvalidServiceException(BasePatroleException):
"""Raised when an invalid service is passed to ``rbac_rule_validation``
decorator.
"""
message = "Attempted to test an invalid service"
class RbacParsingException(BasePatroleException):
message = "Attempted to test an invalid policy file or action"
class RbacInvalidErrorCode(BasePatroleException):
message = "Unsupported error code passed in test"
class RbacOverrideRoleException(BasePatroleException):
"""Raised when override_role is used incorrectly or fails somehow.
Used for safeguarding against false positives that might occur when the
expected exception isn't raised inside the ``override_role`` context.
Specifically, when:
* ``override_role`` isn't called
* an exception is raised before ``override_role`` context
* an exception is raised after ``override_role`` context
"""
message = "Override role failure or incorrect usage"
class RbacValidateListException(BasePatroleException):
"""Raised when override_role_and_validate_list is used incorrectly.
Specifically, when:
* Neither ``resource_id`` nor ``resources`` is initialized
* Both ``resource_id`` and ``resources`` are initialized
* The ``ctx.resources`` variable wasn't set in
override_role_and_validate_list context.
"""
message = "Incorrect usage of override_role_and_validate_list: %(reason)s"

View File

@ -1,516 +0,0 @@
# 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.
import functools
import logging
import sys
from oslo_log import versionutils
from oslo_utils import excutils
import six
from tempest import config
from tempest.lib import exceptions as lib_exc
from tempest import test
from patrole_tempest_plugin import policy_authority
from patrole_tempest_plugin import rbac_exceptions
from patrole_tempest_plugin import requirements_authority
import testtools
CONF = config.CONF
LOG = logging.getLogger(__name__)
_SUPPORTED_ERROR_CODES = [403, 404]
_DEFAULT_ERROR_CODE = 403
RBACLOG = logging.getLogger('rbac_reporting')
def action(service,
rules=None,
expected_error_codes=None,
extra_target_data=None):
"""A decorator for verifying OpenStack policy enforcement.
A decorator which allows for positive and negative RBAC testing. Given:
* an OpenStack service,
* a policy action (``rule``) enforced by that service, and
* the test roles defined by ``[patrole] rbac_test_roles``
determines whether the test role has sufficient permissions to perform an
API call that enforces the ``rule``.
This decorator should only be applied to an instance or subclass of
``tempest.test.BaseTestCase``.
The result from ``_is_authorized`` is used to determine the *expected*
test result. The *actual* test result is determined by running the
Tempest test this decorator applies to.
Below are the following possibilities from comparing the *expected* and
*actual* results:
1) If *expected* is True and the test passes (*actual*), this is a success.
2) If *expected* is True and the test fails (*actual*), this results in a
``RbacUnderPermissionException`` exception failure.
3) If *expected* is False and the test passes (*actual*), this results in
an ``RbacOverPermissionException`` exception failure.
4) If *expected* is False and the test fails (*actual*), this is a success.
As such, negative and positive testing can be applied using this decorator.
:param str service: An OpenStack service. Examples: "nova" or "neutron".
:param list rules: A list of policy actions defined in a policy file or in
code. The rules are logical-ANDed together to derive the expected
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 list expected_error_codes: When the ``rules`` list parameter is
used, then this list indicates the expected error code to use if one
of the rules does not allow the role being tested. This list must
coincide with and its elements remain in the same order as the rules
in the rules list.
Example::
rules=["api_action1", "api_action2"]
expected_error_codes=[404, 403]
a) If api_action1 fails and api_action2 passes, then the expected
error code is 404.
b) if api_action2 fails and api_action1 passes, then the expected
error code is 403.
c) if both api_action1 and api_action2 fail, then the expected error
code is the first error seen (404).
If it is not passed, then it is defaulted to 403.
.. warning::
A 404 should not be provided *unless* the endpoint masks a
``Forbidden`` exception as a ``NotFound`` exception.
:type expected_error_codes: list[int]
:param dict extra_target_data: Dictionary, keyed with ``oslo.policy``
generic check names, whose values are string literals that reference
nested ``tempest.test.BaseTestCase`` attributes. Used by
``oslo.policy`` for performing matching against attributes that are
sent along with the API calls. Example::
extra_target_data={
"target.token.user_id":
"os_alt.auth_provider.credentials.user_id"
})
:raises RbacInvalidServiceException: If ``service`` is invalid.
:raises RbacUnderPermissionException: For item (2) above.
:raises RbacOverPermissionException: For item (3) above.
:raises RbacExpectedWrongException: When a 403 is expected but a 404
is raised instead or vice versa.
Examples::
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-agents"])
def test_list_agents_rbac(self):
# The call to `override_role` is mandatory.
with self.override_role():
self.agents_client.list_agents()
"""
if extra_target_data is None:
extra_target_data = {}
rules, expected_error_codes = _prepare_multi_policy(rules,
expected_error_codes)
def decorator(test_func):
roles = CONF.patrole.rbac_test_roles
# TODO(vegasq) drop once CONF.patrole.rbac_test_role is removed
if CONF.patrole.rbac_test_role:
msg = ('CONF.patrole.rbac_test_role is deprecated in favor of '
'CONF.patrole.rbac_test_roles and will be removed in '
'future.')
versionutils.report_deprecated_feature(LOG, msg)
if not roles:
roles.append(CONF.patrole.rbac_test_role)
@functools.wraps(test_func)
def wrapper(*args, **kwargs):
if args and isinstance(args[0], test.BaseTestCase):
test_obj = args[0]
else:
raise rbac_exceptions.RbacResourceSetupFailed(
'`rbac_rule_validation` decorator can only be applied to '
'an instance of `tempest.test.BaseTestCase`.')
allowed = True
disallowed_rules = []
for rule in rules:
_allowed = _is_authorized(
test_obj, service, rule, extra_target_data)
if not _allowed:
disallowed_rules.append(rule)
allowed = allowed and _allowed
if disallowed_rules:
# Choose the first disallowed rule and expect the error
# code corresponding to it.
first_error_index = rules.index(disallowed_rules[0])
exp_error_code = expected_error_codes[first_error_index]
LOG.debug("%s: Expecting %d to be raised for policy name: %s",
test_func.__name__, exp_error_code,
disallowed_rules[0])
else:
exp_error_code = expected_error_codes[0]
expected_exception, irregular_msg = _get_exception_type(
exp_error_code)
caught_exception = None
test_status = 'Allowed'
try:
test_func(*args, **kwargs)
except rbac_exceptions.RbacInvalidServiceException:
with excutils.save_and_reraise_exception():
msg = ("%s is not a valid service." % service)
# FIXME(felipemonteiro): This test_status is logged too
# late. Need a function to log it before re-raising.
test_status = ('Error, %s' % (msg))
LOG.error(msg)
except (expected_exception,
rbac_exceptions.BasePatroleResponseBodyException) \
as actual_exception:
caught_exception = actual_exception
test_status = 'Denied'
if irregular_msg:
LOG.warning(irregular_msg,
test_func.__name__,
', '.join(rules),
service)
if allowed:
msg = ("User with roles %s was not allowed to perform the "
"following actions: %s. Expected allowed actions: "
"%s. Expected disallowed actions: %s." % (
roles, sorted(rules),
sorted(set(rules) - set(disallowed_rules)),
sorted(disallowed_rules)))
LOG.error(msg)
raise rbac_exceptions.RbacUnderPermissionException(
"%s Exception was: %s" % (msg, actual_exception))
except Exception as actual_exception:
caught_exception = actual_exception
if _check_for_expected_mismatch_exception(expected_exception,
actual_exception):
LOG.error('Expected and actual exceptions do not match. '
'Expected: %s. Actual: %s.',
expected_exception,
actual_exception.__class__)
raise rbac_exceptions.RbacExpectedWrongException(
expected=expected_exception,
actual=actual_exception.__class__,
exception=actual_exception)
else:
with excutils.save_and_reraise_exception():
exc_info = sys.exc_info()
error_details = six.text_type(exc_info[1])
msg = ("An unexpected exception has occurred during "
"test: %s. Exception was: %s" % (
test_func.__name__, error_details))
test_status = 'Error, %s' % (error_details)
LOG.error(msg)
else:
if not allowed:
msg = (
"OverPermission: Role %s was allowed to perform the "
"following disallowed actions: %s" % (
roles, sorted(disallowed_rules)
)
)
LOG.error(msg)
raise rbac_exceptions.RbacOverPermissionException(msg)
finally:
if CONF.patrole_log.enable_reporting:
RBACLOG.info(
"[Service]: %s, [Test]: %s, [Rules]: %s, "
"[Expected]: %s, [Actual]: %s",
service, test_func.__name__, ', '.join(rules),
"Allowed" if allowed else "Denied",
test_status)
# Sanity-check that ``override_role`` was called to eliminate
# false-positives and bad test flows resulting from exceptions
# getting raised too early, too late or not at all, within
# the scope of an RBAC test.
_validate_override_role_called(
test_obj,
actual_exception=caught_exception)
return wrapper
return decorator
def _prepare_multi_policy(rules, exp_error_codes):
if exp_error_codes:
if not rules:
msg = ("The `rules` list must be provided if using the "
"`expected_error_codes` list.")
raise ValueError(msg)
if len(rules) != len(exp_error_codes):
msg = ("The `expected_error_codes` list is not the same length "
"as the `rules` list.")
raise ValueError(msg)
if not isinstance(exp_error_codes, (tuple, list)):
exp_error_codes = [exp_error_codes]
else:
exp_error_codes = []
if rules is None:
rules = []
elif not isinstance(rules, (tuple, list)):
rules = [rules]
# Fill in the exp_error_codes if needed. This is needed for the scenarios
# where no exp_error_codes array is provided, so the error codes must be
# set to the default error code value and there must be the same number
# of error codes as rules.
num_ecs = len(exp_error_codes)
num_rules = len(rules)
if (num_ecs < num_rules):
for i in range(num_rules - num_ecs):
exp_error_codes.append(_DEFAULT_ERROR_CODE)
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):
"""Validates whether current RBAC role has permission to do policy action.
:param test_obj: An instance or subclass of ``tempest.test.BaseTestCase``.
:param service: The OpenStack service that enforces ``rule``.
:param rule: The name of the policy action. Examples include
"identity:create_user" or "os_compute_api:os-agents".
:param extra_target_data: Dictionary, keyed with ``oslo.policy`` generic
check names, whose values are string literals that reference nested
``tempest.test.BaseTestCase`` attributes. Used by ``oslo.policy`` for
performing matching against attributes that are sent along with the API
calls.
:returns: True if the current RBAC role can perform the policy action,
else False.
:raises RbacResourceSetupFailed: If `project_id` or `user_id` are missing
from the `auth_provider` attribute in `test_obj`.
"""
try:
project_id = test_obj.os_primary.credentials.project_id
user_id = test_obj.os_primary.credentials.user_id
except AttributeError as e:
msg = ("{0}: project_id or user_id not found in os_primary.credentials"
.format(e))
LOG.error(msg)
raise rbac_exceptions.RbacResourceSetupFailed(msg)
roles = CONF.patrole.rbac_test_roles
# TODO(vegasq) drop once CONF.patrole.rbac_test_role is removed
if CONF.patrole.rbac_test_role:
if not roles:
roles.append(CONF.patrole.rbac_test_role)
# Adding implied roles
roles = test_obj.get_all_needed_roles(roles)
# Test RBAC against custom requirements. Otherwise use oslo.policy.
if CONF.patrole.test_custom_requirements:
authority = requirements_authority.RequirementsAuthority(
CONF.patrole.custom_requirements_file, service)
else:
formatted_target_data = _format_extra_target_data(
test_obj, extra_target_data)
policy_authority.PolicyAuthority.os_admin = test_obj.os_admin
authority = policy_authority.PolicyAuthority(
project_id, user_id, service,
extra_target_data=formatted_target_data)
is_allowed = authority.allowed(rule, roles)
if is_allowed:
LOG.debug("[Policy action]: %s, [Role]: %s is allowed!", rule,
roles)
else:
LOG.debug("[Policy action]: %s, [Role]: %s is NOT allowed!",
rule, roles)
return is_allowed
def _get_exception_type(expected_error_code=_DEFAULT_ERROR_CODE):
"""Dynamically calculate the expected exception to be caught.
Dynamically calculate the expected exception to be caught by the test case.
Only ``Forbidden`` and ``NotFound`` exceptions are permitted. ``NotFound``
is supported because Neutron, for security reasons, masks ``Forbidden``
exceptions as ``NotFound`` exceptions.
:param expected_error_code: the integer representation of the expected
exception to be caught. Must be contained in
``_SUPPORTED_ERROR_CODES``.
:returns: tuple of the exception type corresponding to
``expected_error_code`` and a message explaining that a non-Forbidden
exception was expected, if applicable.
"""
expected_exception = None
irregular_msg = None
if not isinstance(expected_error_code, six.integer_types) \
or expected_error_code not in _SUPPORTED_ERROR_CODES:
msg = ("Please pass an expected error code. Currently "
"supported codes: {0}".format(_SUPPORTED_ERROR_CODES))
LOG.error(msg)
raise rbac_exceptions.RbacInvalidErrorCode(msg)
if expected_error_code == 403:
expected_exception = lib_exc.Forbidden
elif expected_error_code == 404:
expected_exception = lib_exc.NotFound
irregular_msg = ("NotFound exception was caught for test %s. Expected "
"policies which may have caused the error: %s. The "
"service %s throws a 404 instead of a 403, which is "
"irregular")
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
``tempest.test.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 `os_primary` credential"
:param test_obj: An instance or subclass of ``tempest.test.BaseTestCase``.
:param extra_target_data: Dictionary, keyed with ``oslo.policy`` generic
check names, whose values are string literals that reference nested
``tempest.test.BaseTestCase`` attributes. Used by ``oslo.policy`` for
performing matching against attributes that are sent along with the API
calls.
:returns: Dictionary containing additional object data needed by
``oslo.policy`` to validate generic checks.
"""
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
def _check_for_expected_mismatch_exception(expected_exception,
actual_exception):
"""Checks that ``expected_exception`` matches ``actual_exception``.
Since Patrole must handle 403/404 it is important that the expected and
actual error codes match.
:param excepted_exception: Expected exception for test.
:param actual_exception: Actual exception raised by test.
:returns: True if match, else False.
:rtype: boolean
"""
permission_exceptions = (lib_exc.Forbidden, lib_exc.NotFound)
if isinstance(actual_exception, permission_exceptions):
if not isinstance(actual_exception, expected_exception.__class__):
return True
return False
def _validate_override_role_called(test_obj, actual_exception):
"""Validates that :func:`rbac_utils.RbacUtilsMixin.override_role` is called
during each Patrole test.
Useful for validating that the expected exception isn't raised too early
(before ``override_role`` call) or too late (after ``override_call``) or
at all (which is a bad test).
:param test_obj: An instance or subclass of ``tempest.test.BaseTestCase``.
:param actual_exception: Actual exception raised by test.
:raises RbacOverrideRoleException: If ``override_role`` isn't called, is
called too early, or is called too late.
"""
called = test_obj._validate_override_role_called()
base_msg = ('This error is unrelated to RBAC and is due to either '
'an API or override role failure. Exception: %s' %
actual_exception)
if not called:
if actual_exception is not None:
# Use testtools skipException in base TestCase
# to support different skip exceptions used.
# Just return so the skip exception will go up
# the stack and be handled by the unit testing framework
if isinstance(actual_exception,
testtools.testcase.TestCase.skipException):
return
msg = ('Caught exception (%s) but it was raised before the '
'`override_role` context. ' % actual_exception.__class__)
else:
msg = 'Test missing required `override_role` call. '
msg += base_msg
LOG.error(msg)
raise rbac_exceptions.RbacOverrideRoleException(msg)
else:
exc_caught_in_ctx = test_obj._validate_override_role_caught_exc()
# This block is only executed if ``override_role`` is called. If
# an exception is raised and the exception wasn't raised in the
# ``override_role`` context and if the exception isn't a valid
# exception type (instance of ``BasePatroleException``), then this is
# a legitimate error.
if (not exc_caught_in_ctx and
actual_exception is not None and
not isinstance(actual_exception,
rbac_exceptions.BasePatroleException)):
msg = ('Caught exception (%s) but it was raised after the '
'`override_role` context. ' % actual_exception.__class__)
msg += base_msg
LOG.error(msg)
raise rbac_exceptions.RbacOverrideRoleException(msg)

View File

@ -1,501 +0,0 @@
# 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.
import contextlib
import sys
import time
from oslo_log import log as logging
from oslo_utils import excutils
from tempest import config
from tempest.lib import exceptions as lib_exc
from patrole_tempest_plugin import rbac_exceptions
CONF = config.CONF
LOG = logging.getLogger(__name__)
class _ValidateListContext(object):
"""Context class responsible for validation of the list functions.
This class is used in ``override_role_and_validate_list`` function and
the result of a list function must be assigned to the ``ctx.resources``
variable.
Example::
with self.override_role_and_validate_list(...) as ctx:
ctx.resources = list_function()
"""
def __init__(self, admin_resources=None, admin_resource_id=None):
"""Constructor for ``ValidateListContext``.
Either ``admin_resources`` or ``admin_resource_id`` should be used,
not both.
:param list admin_resources: The list of resources received before
calling the ``override_role_and_validate_list`` function. To
validate will be used the ``_validate_len`` function.
:param UUID admin_resource_id: An ID of a resource created before
calling the ``override_role_and_validate_list`` function. To
validate will be used the ``_validate_resource`` function.
:raises RbacValidateListException: if both ``admin_resources`` and
``admin_resource_id`` are set or unset.
"""
self.resources = None
if admin_resources is not None and not admin_resource_id:
self._admin_len = len(admin_resources)
if not self._admin_len:
raise rbac_exceptions.RbacValidateListException(
reason="the list of admin resources cannot be empty")
self._validate_func = self._validate_len
elif admin_resource_id and admin_resources is None:
self._admin_resource_id = admin_resource_id
self._validate_func = self._validate_resource
else:
raise rbac_exceptions.RbacValidateListException(
reason="admin_resources and admin_resource_id are mutually "
"exclusive")
def _validate_len(self):
"""Validates that the number of resources is less than admin resources.
"""
if not len(self.resources):
raise rbac_exceptions.RbacEmptyResponseBody()
elif self._admin_len > len(self.resources):
raise rbac_exceptions.RbacPartialResponseBody(body=self.resources)
def _validate_resource(self):
"""Validates that the admin resource is present in the resources.
"""
for resource in self.resources:
if resource['id'] == self._admin_resource_id:
return
raise rbac_exceptions.RbacPartialResponseBody(body=self.resources)
def _validate(self):
"""Calls the proper validation function.
:raises RbacValidateListException: if the ``ctx.resources`` variable is
not assigned.
"""
if self.resources is None:
raise rbac_exceptions.RbacValidateListException(
reason="ctx.resources is not assigned")
self._validate_func()
class RbacUtilsMixin(object):
"""Utility mixin responsible for switching ``os_primary`` role.
Should be used as a mixin class alongside an instance of
:py:class:`tempest.test.BaseTestCase` to perform Patrole class setup for a
base RBAC class. Child classes should not use this mixin.
Example::
class BaseRbacTest(rbac_utils.RbacUtilsMixin, base.BaseV2ComputeTest):
@classmethod
def setup_clients(cls):
super(BaseRbacTest, cls).setup_clients()
cls.hosts_client = cls.os_primary.hosts_client
...
This class is responsible for overriding the value of the primary Tempest
credential's role (i.e. ``os_primary`` role). By doing so, it is possible
to seamlessly swap between admin credentials, needed for setup and clean
up, and primary credentials, needed to perform the API call which does
policy enforcement. The primary credentials always cycle between roles
defined by ``CONF.identity.admin_role`` and
``CONF.patrole.rbac_test_roles``.
"""
credentials = ['primary', 'admin']
def __init__(self, *args, **kwargs):
super(RbacUtilsMixin, self).__init__(*args, **kwargs)
# Shows if override_role was called.
self.__override_role_called = False
# Shows if exception raised during override_role.
self.__override_role_caught_exc = False
_admin_role_id = None
_rbac_role_ids = None
_project_id = None
_user_id = None
_role_map = None
_role_inferences_mapping = None
_orig_roles = []
admin_roles_client = None
@classmethod
def restore_roles(cls):
if cls._orig_roles:
LOG.info("Restoring original roles %s", cls._orig_roles)
roles_already_present = cls._list_and_clear_user_roles_on_project(
cls._orig_roles)
if not roles_already_present:
cls._create_user_role_on_project(cls._orig_roles)
@classmethod
def setup_clients(cls):
if CONF.identity_feature_enabled.api_v3:
admin_roles_client = cls.os_admin.roles_v3_client
else:
raise lib_exc.InvalidConfiguration(
"Patrole role overriding only supports v3 identity API.")
cls.admin_roles_client = admin_roles_client
cls._project_id = cls.os_primary.credentials.tenant_id
cls._user_id = cls.os_primary.credentials.user_id
cls._role_inferences_mapping = cls._prepare_role_inferences_mapping()
cls._init_roles()
# Store the user's original roles and rollback after testing.
roles = cls.admin_roles_client.list_user_roles_on_project(
cls._project_id, cls._user_id)['roles']
cls._orig_roles = [role['id'] for role in roles]
cls.addClassResourceCleanup(cls.restore_roles)
# Change default role to admin
cls._override_role(False)
super(RbacUtilsMixin, cls).setup_clients()
@classmethod
def _prepare_role_inferences_mapping(cls):
"""Preparing roles mapping to support role inferences
Making query to `list-all-role-inference-rules`_ keystone API
returns all inference rules, which makes it possible to prepare
roles mapping.
It walks recursively through the raw data::
{"role_inferences": [
{
"implies": [{"id": "3", "name": "reader"}],
"prior_role": {"id": "2", "name": "member"}
},
{
"implies": [{"id": "2", "name": "member"}],
"prior_role": {"id": "1", "name": "admin"}
}
]
}
and converts it to the mapping::
{
"2": ["3"], # "member": ["reader"],
"1": ["2", "3"] # "admin": ["member", "reader"]
}
.. _list-all-role-inference-rules: https://docs.openstack.org/api-ref/identity/v3/#list-all-role-inference-rules
""" # noqa: E501
def process_roles(role_id, data):
roles = data.get(role_id, set())
for rid in roles.copy():
roles.update(process_roles(rid, data))
return roles
def convert_data(data):
res = {}
for rule in data:
prior_role = rule['prior_role']['id']
implies = {r['id'] for r in rule['implies']}
res[prior_role] = implies
return res
raw_data = cls.admin_roles_client.list_all_role_inference_rules()
data = convert_data(raw_data['role_inferences'])
res = {}
for role_id in data:
res[role_id] = process_roles(role_id, data)
return res
def get_all_needed_roles(self, roles):
"""Extending given roles with roles from mapping
Examples::
["admin"] >> ["admin", "member", "reader"]
["member"] >> ["member", "reader"]
["reader"] >> ["reader"]
["custom_role"] >> ["custom_role"]
:param roles: list of roles
:return: extended list of roles
"""
res = set(r for r in roles)
for role in res.copy():
role_id = self.__class__._role_map.get(role)
implied_roles = self.__class__._role_inferences_mapping.get(
role_id, set())
role_names = {self.__class__._role_map[rid]
for rid in implied_roles}
res.update(role_names)
LOG.debug('All needed roles: %s; Base roles: %s', res, roles)
return list(res)
@contextlib.contextmanager
def override_role(self):
"""Override the role used by ``os_primary`` Tempest credentials.
Temporarily change the role used by ``os_primary`` credentials to:
* ``[patrole] rbac_test_roles`` before test execution
* ``[identity] admin_role`` after test execution
Automatically switches to admin role after test execution.
:returns: None
.. warning::
This function can alter user roles for pre-provisioned credentials.
Work is underway to safely clean up after this function.
Example::
@rbac_rule_validation.action(service='test',
rules=['a:test:rule'])
def test_foo(self):
# Allocate test-level resources here.
with self.override_role():
# The role for `os_primary` has now been overridden. Within
# this block, call the API endpoint that enforces the
# expected policy specified by "rule" in the decorator.
self.foo_service.bar_api_call()
# The role is switched back to admin automatically. Note that
# if the API call above threw an exception, any code below this
# point in the test is not executed.
"""
self._set_override_role_called()
self._override_role(True)
try:
# Execute the test.
yield
finally:
# Check whether an exception was raised. If so, remember that
# for future validation.
exc = sys.exc_info()[0]
if exc is not None:
self._set_override_role_caught_exc()
# This code block is always executed, no matter the result of the
# test. Automatically switch back to the admin role for test clean
# up.
self._override_role(False)
@classmethod
def _override_role(cls, toggle_rbac_role=False):
"""Private helper for overriding ``os_primary`` Tempest credentials.
:param toggle_rbac_role: Boolean value that controls the role that
overrides default role of ``os_primary`` credentials.
* If True: role is set to ``[patrole] rbac_test_role``
* If False: role is set to ``[identity] admin_role``
"""
LOG.debug('Overriding role to: %s.', toggle_rbac_role)
roles_already_present = False
try:
target_roles = (cls._rbac_role_ids
if toggle_rbac_role else [cls._admin_role_id])
roles_already_present = cls._list_and_clear_user_roles_on_project(
target_roles)
# Do not override roles if `target_role` already exists.
if not roles_already_present:
cls._create_user_role_on_project(target_roles)
except Exception as exp:
with excutils.save_and_reraise_exception():
LOG.exception(exp)
finally:
auth_providers = cls.get_auth_providers()
for provider in auth_providers:
provider.clear_auth()
# Fernet tokens are not subsecond aware so sleep to ensure we are
# passing the second boundary before attempting to authenticate.
# Only sleep if a token revocation occurred as a result of role
# overriding. This will optimize test runtime in the case where
# ``[identity] admin_role`` == ``[patrole] rbac_test_roles``.
if not roles_already_present:
time.sleep(1)
for provider in auth_providers:
provider.set_auth()
@classmethod
def _init_roles(cls):
available_roles = cls.admin_roles_client.list_roles()['roles']
cls._role_map = {r['name']: r['id'] for r in available_roles}
LOG.debug('Available roles: %s', cls._role_map.keys())
rbac_role_ids = []
roles = CONF.patrole.rbac_test_roles
# TODO(vegasq) drop once CONF.patrole.rbac_test_role is removed
if CONF.patrole.rbac_test_role:
if not roles:
roles.append(CONF.patrole.rbac_test_role)
for role_name in roles:
rbac_role_ids.append(cls._role_map.get(role_name))
admin_role_id = cls._role_map.get(CONF.identity.admin_role)
if not all([admin_role_id, all(rbac_role_ids)]):
missing_roles = []
msg = ("Could not find `[patrole] rbac_test_roles` or "
"`[identity] admin_role`, both of which are required for "
"RBAC testing.")
if not admin_role_id:
missing_roles.append(CONF.identity.admin_role)
if not all(rbac_role_ids):
missing_roles += [role_name for role_name in roles
if role_name not in cls._role_map]
msg += " Following roles were not found: %s." % (
", ".join(missing_roles))
msg += " Available roles: %s." % ", ".join(cls._role_map)
raise rbac_exceptions.RbacResourceSetupFailed(msg)
cls._admin_role_id = admin_role_id
cls._rbac_role_ids = rbac_role_ids
# Adding backward mapping
cls._role_map.update({v: k for k, v in cls._role_map.items()})
@classmethod
def _create_user_role_on_project(cls, role_ids):
for role_id in role_ids:
cls.admin_roles_client.create_user_role_on_project(
cls._project_id, cls._user_id, role_id)
@classmethod
def _list_and_clear_user_roles_on_project(cls, role_ids):
roles = cls.admin_roles_client.list_user_roles_on_project(
cls._project_id, cls._user_id)['roles']
all_role_ids = [role['id'] for role in roles]
# NOTE(felipemonteiro): We do not use ``role_id in all_role_ids`` here
# to avoid over-permission errors: if the current list of roles on the
# project includes "admin" and "Member", and we are switching to the
# "Member" role, then we must delete the "admin" role. Thus, we only
# return early if the user's roles on the project are an exact match.
if set(role_ids) == set(all_role_ids):
return True
for role in roles:
cls.admin_roles_client.delete_role_from_user_on_project(
cls._project_id, cls._user_id, role['id'])
return False
@contextlib.contextmanager
def override_role_and_validate_list(self,
admin_resources=None,
admin_resource_id=None):
"""Call ``override_role`` and validate RBAC for a list API action.
List actions usually do soft authorization: partial or empty response
bodies are returned instead of exceptions. This helper validates
that unauthorized roles only return a subset of the available
resources.
Should only be used for validating list API actions.
:param test_obj: Instance of ``tempest.test.BaseTestCase``.
:param list admin_resources: The list of resources received before
calling the ``override_role_and_validate_list`` function.
:param UUID admin_resource_id: An ID of a resource created before
calling the ``override_role_and_validate_list`` function.
:return: py:class:`_ValidateListContext` object.
Example::
# the resource created by admin
admin_resource_id = (
self.ntp_client.create_dscp_marking_rule()
["dscp_marking_rule"]["id'])
with self.override_role_and_validate_list(
admin_resource_id=admin_resource_id) as ctx:
# the list of resources available for member role
ctx.resources = self.ntp_client.list_dscp_marking_rules(
policy_id=self.policy_id)["dscp_marking_rules"]
"""
ctx = _ValidateListContext(admin_resources, admin_resource_id)
with self.override_role():
yield ctx
ctx._validate()
@classmethod
def get_auth_providers(cls):
"""Returns list of auth_providers used within test.
Tests may redefine this method to include their own or third party
client auth_providers.
"""
return [cls.os_primary.auth_provider]
def _set_override_role_called(self):
"""Helper for tracking whether ``override_role`` was called."""
self.__override_role_called = True
def _set_override_role_caught_exc(self):
"""Helper for tracking whether exception was thrown inside
``override_role``.
"""
self.__override_role_caught_exc = True
def _validate_override_role_called(self):
"""Idempotently validate that ``override_role`` is called and reset
its value to False for sequential tests.
"""
was_called = self.__override_role_called
self.__override_role_called = False
return was_called
def _validate_override_role_caught_exc(self):
"""Idempotently validate that exception was caught inside
``override_role``, so that, by process of elimination, it can be
determined whether one was thrown outside (which is invalid).
"""
caught_exception = self.__override_role_caught_exc
self.__override_role_caught_exc = False
return caught_exception
def is_admin():
"""Verifies whether the current test role equals the admin role.
:returns: True if ``rbac_test_roles`` contain the admin role.
"""
roles = CONF.patrole.rbac_test_roles
# TODO(vegasq) drop once CONF.patrole.rbac_test_role is removed
if CONF.patrole.rbac_test_role:
roles.append(CONF.patrole.rbac_test_role)
roles = list(set(roles))
# TODO(felipemonteiro): Make this more robust via a context is admin
# lookup.
return CONF.identity.admin_role in roles

View File

@ -1,150 +0,0 @@
# 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.
import copy
import yaml
from oslo_log import log as logging
from tempest import config
from tempest.lib import exceptions as lib_exc
from patrole_tempest_plugin.rbac_authority import RbacAuthority
from patrole_tempest_plugin import rbac_exceptions
CONF = config.CONF
LOG = logging.getLogger(__name__)
class RequirementsParser(object):
"""A class that parses a custom requirements file."""
_inner = None
class Inner(object):
_rbac_map = None
def __init__(self, filepath):
with open(filepath) as f:
RequirementsParser.Inner._rbac_map = \
list(yaml.safe_load_all(f))
def __init__(self, filepath):
if RequirementsParser._inner is None:
RequirementsParser._inner = RequirementsParser.Inner(filepath)
@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>
- <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:
rules = copy.copy(section[component])
for rule in rules:
rules[rule] = [
roles.split(',') for roles in rules[rule]]
for i, role_pack in enumerate(rules[rule]):
rules[rule][i] = [r.strip() for r in role_pack]
return rules
except yaml.parser.ParserError:
LOG.error("Error while parsing the requirements YAML file. Did "
"you pass a valid component name from the test case?")
return {}
class RequirementsAuthority(RbacAuthority):
"""A class that uses a custom requirements file to validate RBAC."""
def __init__(self, filepath=None, component=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.
"""
self.filepath = filepath or CONF.patrole.custom_requirements_file
if component is not None:
self.roles_dict = RequirementsParser(self.filepath).parse(
component)
else:
self.roles_dict = None
def allowed(self, rule_name, roles):
"""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 List[string] roles: Roles to validate against custom
requirements file.
:returns: True if ``role`` is allowed to perform ``rule_name``, else
False.
:rtype: bool
:raises RbacParsingException: If ``rule_name`` does not exist among the
keyed policy names in the custom requirements file.
"""
if not self.roles_dict:
raise lib_exc.InvalidConfiguration(
"Roles dictionary parsed from requirements YAML file is "
"empty. Ensure the requirements YAML file is correctly "
"formatted.")
try:
requirement_roles = self.roles_dict[rule_name]
except KeyError:
raise rbac_exceptions.RbacParsingException(
"'%s' rule name is not defined in the requirements YAML file: "
"%s" % (rule_name, self.filepath))
for role_reqs in requirement_roles:
required_roles = [
role for role in role_reqs if not role.startswith("!")]
forbidden_roles = [
role[1:] for role in role_reqs if role.startswith("!")]
# User must have all required roles
required_passed = all([r in roles for r in required_roles])
# User must not have any forbidden roles
forbidden_passed = all([r not in forbidden_roles
for r in roles])
if required_passed and forbidden_passed:
return True
return False

View File

@ -1 +0,0 @@
../../../doc/source/field_guide/rbac.rst

View File

@ -1,50 +0,0 @@
# Copyright 2017 AT&T Corporation
# 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.api.compute import base as compute_base
from tempest.lib.common.utils import data_utils
from tempest.lib.common.utils import test_utils
from patrole_tempest_plugin import rbac_utils
class BaseV2ComputeRbacTest(rbac_utils.RbacUtilsMixin,
compute_base.BaseV2ComputeTest):
@classmethod
def setup_clients(cls):
super(BaseV2ComputeRbacTest, cls).setup_clients()
cls.hosts_client = cls.os_primary.hosts_client
cls.tenant_usages_client = cls.os_primary.tenant_usages_client
cls.networks_client = cls.os_primary.networks_client
cls.subnets_client = cls.os_primary.subnets_client
cls.ports_client = cls.os_primary.ports_client
@classmethod
def create_flavor(cls, **kwargs):
flavor_kwargs = {
"name": data_utils.rand_name(cls.__name__ + '-flavor'),
"ram": data_utils.rand_int_id(1, 10),
"vcpus": data_utils.rand_int_id(1, 10),
"disk": data_utils.rand_int_id(1, 10),
"id": data_utils.rand_uuid(),
}
if kwargs:
flavor_kwargs.update(kwargs)
flavor = cls.flavors_client.create_flavor(**flavor_kwargs)['flavor']
cls.addClassResourceCleanup(
cls.flavors_client.wait_for_resource_deletion, flavor['id'])
cls.addClassResourceCleanup(
test_utils.call_and_ignore_notfound_exc,
cls.flavors_client.delete_flavor, flavor['id'])
return flavor

View File

@ -1,118 +0,0 @@
# 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.common import utils
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.compute import rbac_base
CONF = config.CONF
if CONF.policy_feature_enabled.changed_nova_policies_ussuri:
_AGENTS_LIST = "os_compute_api:os-agents:list"
_AGENTS_CREATE = "os_compute_api:os-agents:create"
_AGENTS_UPDATE = "os_compute_api:os-agents:update"
_AGENTS_DELETE = "os_compute_api:os-agents:delete"
else:
_AGENTS_LIST = "os_compute_api:os-agents"
_AGENTS_CREATE = "os_compute_api:os-agents"
_AGENTS_UPDATE = "os_compute_api:os-agents"
_AGENTS_DELETE = "os_compute_api:os-agents"
class AgentsRbacTest(rbac_base.BaseV2ComputeRbacTest):
@classmethod
def skip_checks(cls):
super(AgentsRbacTest, cls).skip_checks()
if not utils.is_extension_enabled('os-agents', 'compute'):
raise cls.skipException(
'%s skipped as os-agents not enabled' % cls.__name__)
if CONF.policy_feature_enabled.removed_nova_policies_wallaby:
raise cls.skipException(
'%s skipped as os-agents APIs/policies are not avaialble '
'any more.' % cls.__name__)
def _param_helper(self, **kwargs):
rand_key = 'architecture'
if rand_key in kwargs:
# NOTE: The rand_name is for avoiding agent conflicts.
# If you try to create an agent with the same hypervisor,
# os and architecture as an existing agent, Nova will return
# an HTTPConflict or HTTPServerError.
kwargs[rand_key] = data_utils.rand_name(kwargs[rand_key])
return kwargs
@rbac_rule_validation.action(
service="nova", rules=[_AGENTS_LIST])
@decorators.idempotent_id('d1bc6d97-07f5-4f45-ac29-1c619a6a7e27')
def test_list_agents_rbac(self):
with self.override_role():
self.agents_client.list_agents()
@rbac_rule_validation.action(
service="nova",
rules=[_AGENTS_CREATE])
@decorators.idempotent_id('77d6cae4-1ced-47f7-af2e-3d6a45958fd6')
def test_create_agent(self):
params = {'hypervisor': 'kvm', 'os': 'win', 'architecture': 'x86',
'version': '7.0', 'url': 'xxx://xxxx/xxx/xxx',
'md5hash': 'add6bb58e139be103324d04d82d8f545'}
with self.override_role():
body = self.agents_client.create_agent(**params)['agent']
self.addCleanup(self.agents_client.delete_agent,
body['agent_id'])
@rbac_rule_validation.action(
service="nova",
rules=[_AGENTS_UPDATE])
@decorators.idempotent_id('b22f2681-9ffb-439b-b240-dae503e41020')
def test_update_agent(self):
params = self._param_helper(
hypervisor='common', os='linux',
architecture='x86_64', version='7.0',
url='xxx://xxxx/xxx/xxx',
md5hash='add6bb58e139be103324d04d82d8f545')
body = self.agents_client.create_agent(**params)['agent']
self.addCleanup(self.agents_client.delete_agent,
body['agent_id'])
update_params = self._param_helper(
version='8.0',
url='xxx://xxxx/xxx/xxx2',
md5hash='add6bb58e139be103324d04d82d8f547')
with self.override_role():
self.agents_client.update_agent(body['agent_id'], **update_params)
@rbac_rule_validation.action(
service="nova",
rules=[_AGENTS_DELETE])
@decorators.idempotent_id('c5042af8-0682-43b0-abc4-bf33349e23dd')
def test_delete_agent(self):
params = self._param_helper(
hypervisor='common', os='linux',
architecture='x86_64', version='7.0',
url='xxx://xxxx/xxx/xxx',
md5hash='add6bb58e139be103324d04d82d8f545')
body = self.agents_client.create_agent(**params)['agent']
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.agents_client.delete_agent,
body['agent_id'])
with self.override_role():
self.agents_client.delete_agent(body['agent_id'])

View File

@ -1,150 +0,0 @@
# 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.
import testtools
from tempest.common import utils
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.compute import rbac_base
CONF = config.CONF
class AggregatesRbacTest(rbac_base.BaseV2ComputeRbacTest):
@classmethod
def skip_checks(cls):
super(AggregatesRbacTest, cls).skip_checks()
if not utils.is_extension_enabled('os-aggregates', 'compute'):
msg = "%s skipped as os-aggregates not enabled." % cls.__name__
raise cls.skipException(msg)
@classmethod
def setup_clients(cls):
super(AggregatesRbacTest, cls).setup_clients()
cls.hosts_client = cls.os_primary.hosts_client
@classmethod
def resource_setup(cls):
super(AggregatesRbacTest, cls).resource_setup()
cls.host = None
hypers = cls.hypervisor_client.list_hypervisors(
detail=True)['hypervisors']
if CONF.compute.hypervisor_type:
hypers = [hyper for hyper in hypers
if (hyper['hypervisor_type'] ==
CONF.compute.hypervisor_type)]
hosts_available = [hyper['service']['host'] for hyper in hypers
if (hyper['state'] == 'up' and
hyper['status'] == 'enabled')]
if hosts_available:
cls.host = hosts_available[0]
else:
msg = "no available compute node found"
if CONF.compute.hypervisor_type:
msg += " for hypervisor_type %s" % CONF.compute.hypervisor_type
raise testtools.TestCase.failureException(msg)
def _create_aggregate(self):
agg_name = data_utils.rand_name(self.__class__.__name__ + '-aggregate')
aggregate = self.aggregates_client.create_aggregate(name=agg_name)
aggregate_id = aggregate['aggregate']['id']
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.aggregates_client.delete_aggregate,
aggregate_id)
return aggregate_id
def _add_host_to_aggregate(self, aggregate_id):
self.aggregates_client.add_host(aggregate_id, host=self.host)
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.aggregates_client.remove_host,
aggregate_id,
host=self.host)
@rbac_rule_validation.action(
service="nova", rules=["os_compute_api:os-aggregates:create"])
@decorators.idempotent_id('ba754393-896e-434a-9704-452ff4a84f3f')
def test_create_aggregate_rbac(self):
with self.override_role():
self._create_aggregate()
@rbac_rule_validation.action(
service="nova", rules=["os_compute_api:os-aggregates:show"])
@decorators.idempotent_id('8fb0b749-b120-4727-b3fb-bcfa3fa6f55b')
def test_show_aggregate_rbac(self):
aggregate_id = self._create_aggregate()
with self.override_role():
self.aggregates_client.show_aggregate(aggregate_id)
@rbac_rule_validation.action(
service="nova", rules=["os_compute_api:os-aggregates:index"])
@decorators.idempotent_id('146284da-5dd6-4c97-b598-42b480f014c6')
def test_list_aggregate_rbac(self):
with self.override_role():
self.aggregates_client.list_aggregates()
@rbac_rule_validation.action(
service="nova", rules=["os_compute_api:os-aggregates:update"])
@decorators.idempotent_id('c94e0d69-99b6-477e-b301-2cd0e9d0ad81')
def test_update_aggregate_rbac(self):
aggregate_id = self._create_aggregate()
new_name = data_utils.rand_name(self.__class__.__name__ + '-aggregate')
with self.override_role():
self.aggregates_client.update_aggregate(aggregate_id,
name=new_name)
@rbac_rule_validation.action(
service="nova", rules=["os_compute_api:os-aggregates:delete"])
@decorators.idempotent_id('5a50c5a6-0f12-4405-a1ce-2288ae895ea6')
def test_delete_aggregate_rbac(self):
aggregate_id = self._create_aggregate()
with self.override_role():
self.aggregates_client.delete_aggregate(aggregate_id)
@rbac_rule_validation.action(
service="nova", rules=["os_compute_api:os-aggregates:add_host"])
@decorators.idempotent_id('97e6e9df-5291-4faa-8147-755b2d1f1ce2')
def test_add_host_to_aggregate_rbac(self):
aggregate_id = self._create_aggregate()
with self.override_role():
self._add_host_to_aggregate(aggregate_id)
@rbac_rule_validation.action(
service="nova", rules=["os_compute_api:os-aggregates:remove_host"])
@decorators.idempotent_id('5b035a25-75d2-4d72-b4d6-0f0337335628')
def test_remove_host_from_aggregate_rbac(self):
aggregate_id = self._create_aggregate()
self._add_host_to_aggregate(aggregate_id)
with self.override_role():
self.aggregates_client.remove_host(aggregate_id, host=self.host)
@rbac_rule_validation.action(
service="nova", rules=["os_compute_api:os-aggregates:set_metadata"])
@decorators.idempotent_id('ed6f3849-065c-4ae9-a81e-6ad7ed0d3d9d')
def test_set_metadata_on_aggregate_rbac(self):
aggregate_id = self._create_aggregate()
rand_key = data_utils.rand_name(self.__class__.__name__ + '-key')
rand_val = data_utils.rand_name(self.__class__.__name__ + '-val')
with self.override_role():
self.aggregates_client.set_metadata(
aggregate_id,
metadata={rand_key: rand_val})

View File

@ -1,45 +0,0 @@
# Copyright 2017 AT&T Corporation.
# 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 import decorators
from patrole_tempest_plugin import rbac_rule_validation
from patrole_tempest_plugin.tests.api.compute import rbac_base
class NovaAvailabilityZoneRbacTest(rbac_base.BaseV2ComputeRbacTest):
@classmethod
def skip_checks(cls):
super(NovaAvailabilityZoneRbacTest, cls).skip_checks()
if not utils.is_extension_enabled('os-availability-zone', 'compute'):
msg = ("%s skipped as os-availability-zone not "
"enabled." % cls.__name__)
raise cls.skipException(msg)
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-availability-zone:list"])
@decorators.idempotent_id('cd34e7ea-d26e-4fa3-a8d0-f8883726ce3d')
def test_get_availability_zone_list_rbac(self):
with self.override_role():
self.availability_zone_client.list_availability_zones()
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-availability-zone:detail"])
@decorators.idempotent_id('2f61c191-6ece-4f21-b487-39d749e3d38e')
def test_get_availability_zone_list_detail_rbac(self):
with self.override_role():
self.availability_zone_client.list_availability_zones(detail=True)

View File

@ -1,116 +0,0 @@
# 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.
import testtools
from tempest import config
from tempest.lib.common.utils import test_utils
from tempest.lib import decorators
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
CONF = config.CONF
class FlavorAccessRbacTest(rbac_base.BaseV2ComputeRbacTest):
@classmethod
def resource_setup(cls):
super(FlavorAccessRbacTest, cls).resource_setup()
cls.flavor_id = cls.create_flavor(is_public=False)['id']
cls.public_flavor_id = CONF.compute.flavor_ref
cls.tenant_id = cls.os_primary.credentials.tenant_id
@testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
"This API extension policy was removed in Stein")
@decorators.idempotent_id('a2bd3740-765d-4c95-ac98-9e027378c75e')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-flavor-access"])
def test_show_flavor_contains_is_public_key(self):
public_flavor_id = CONF.compute.flavor_ref
with self.override_role():
body = self.flavors_client.show_flavor(public_flavor_id)[
'flavor']
expected_attr = 'os-flavor-access:is_public'
if expected_attr not in body:
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=expected_attr)
@testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
"This API extension policy was removed in Stein")
@decorators.idempotent_id('dd388146-9750-4124-82ba-62deff1052bb')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-flavor-access"])
def test_list_flavors_details_contains_is_public_key(self):
expected_attr = 'os-flavor-access:is_public'
with self.override_role():
flavors = self.flavors_client.list_flavors(detail=True)['flavors']
# There should already be a public flavor available, namely
# `CONF.compute.flavor_ref`.
public_flavors = [f for f in flavors if expected_attr in f]
# If the `expected_attr` was not found in any flavor, then policy
# enforcement failed.
if not public_flavors:
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=expected_attr)
@decorators.idempotent_id('39cb5c8f-9990-436f-9282-fc76a41d9bac')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-flavor-access:add_tenant_access"])
def test_add_flavor_access(self):
with self.override_role():
self.flavors_client.add_flavor_access(
flavor_id=self.flavor_id, tenant_id=self.tenant_id)
self.addCleanup(self.flavors_client.remove_flavor_access,
flavor_id=self.flavor_id, tenant_id=self.tenant_id)
@decorators.idempotent_id('61b8621f-52e4-473a-8d07-e228af8853d1')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-flavor-access:remove_tenant_access"])
def test_remove_flavor_access(self):
self.flavors_client.add_flavor_access(
flavor_id=self.flavor_id, tenant_id=self.tenant_id)
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.flavors_client.remove_flavor_access,
flavor_id=self.flavor_id, tenant_id=self.tenant_id)
with self.override_role():
self.flavors_client.remove_flavor_access(
flavor_id=self.flavor_id, tenant_id=self.tenant_id)
@decorators.idempotent_id('e1cf59fb-7f32-40a1-96b9-248ab23dd581')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-flavor-access"])
def test_list_flavor_access(self):
# Add flavor access for os_primary so that it can access the flavor or
# else a NotFound is raised.
self.flavors_client.add_flavor_access(
flavor_id=self.flavor_id, tenant_id=self.tenant_id)
self.addCleanup(self.flavors_client.remove_flavor_access,
flavor_id=self.flavor_id, tenant_id=self.tenant_id)
with self.override_role():
self.flavors_client.list_flavor_access(self.flavor_id)

View File

@ -1,94 +0,0 @@
# 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.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.compute import rbac_base
class FlavorExtraSpecsRbacTest(rbac_base.BaseV2ComputeRbacTest):
@classmethod
def skip_checks(cls):
super(FlavorExtraSpecsRbacTest, cls).skip_checks()
if not utils.is_extension_enabled('os-flavor-extra-specs', 'compute'):
msg = "os-flavor-extra-specs extension not enabled."
raise cls.skipException(msg)
@classmethod
def resource_setup(cls):
super(FlavorExtraSpecsRbacTest, cls).resource_setup()
cls.flavor = cls.create_flavor()
def _set_flavor_extra_spec(self):
rand_key = data_utils.rand_name(self.__class__.__name__ + '-key')
rand_val = data_utils.rand_name(self.__class__.__name__ + '-val')
specs = {rand_key: rand_val}
self.flavors_client.set_flavor_extra_spec(self.flavor['id'],
**specs)['extra_specs']
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.flavors_client.unset_flavor_extra_spec,
self.flavor['id'], rand_key)
return rand_key
@decorators.idempotent_id('daee891d-dfe9-4501-a39c-29f2371bec3c')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-flavor-extra-specs:show"])
def test_show_flavor_extra_spec(self):
key = self._set_flavor_extra_spec()
with self.override_role():
self.flavors_client.show_flavor_extra_spec(self.flavor['id'], key)
@decorators.idempotent_id('fcffeca2-ed04-4e85-bf93-02fb5643f22b')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-flavor-extra-specs:create"])
def test_set_flavor_extra_spec(self):
with self.override_role():
self._set_flavor_extra_spec()
@decorators.idempotent_id('42b85279-6bfa-4f58-b7a2-258c284f03c5')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-flavor-extra-specs:update"])
def test_update_flavor_extra_spec(self):
key = self._set_flavor_extra_spec()
update_val = data_utils.rand_name(self.__class__.__name__ + '-val')
with self.override_role():
self.flavors_client.update_flavor_extra_spec(
self.flavor['id'], key, **{key: update_val})
@decorators.idempotent_id('4b0e5471-e010-4c09-8965-80898e6760a3')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-flavor-extra-specs:delete"])
def test_unset_flavor_extra_spec(self):
key = self._set_flavor_extra_spec()
with self.override_role():
self.flavors_client.unset_flavor_extra_spec(self.flavor['id'], key)
@decorators.idempotent_id('02c3831a-3ce9-476e-a722-d805ac2da621')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-flavor-extra-specs:index"])
def test_list_flavor_extra_specs(self):
self._set_flavor_extra_spec()
with self.override_role():
self.flavors_client.list_flavor_extra_specs(self.flavor['id'])

View File

@ -1,49 +0,0 @@
# 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.common import utils
from tempest.lib import decorators
from patrole_tempest_plugin import rbac_rule_validation
from patrole_tempest_plugin.tests.api.compute import rbac_base
class FlavorManageRbacTest(rbac_base.BaseV2ComputeRbacTest):
@classmethod
def skip_checks(cls):
super(FlavorManageRbacTest, cls).skip_checks()
if not utils.is_extension_enabled('OS-FLV-EXT-DATA', 'compute'):
msg = "OS-FLV-EXT-DATA extension not enabled."
raise cls.skipException(msg)
@decorators.idempotent_id('a4e7faec-7a4b-4809-9856-90d5b747ca35')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-flavor-manage:create"])
def test_create_flavor_manage(self):
with self.override_role():
self.create_flavor()
@decorators.idempotent_id('782e988e-061b-4c40-896f-a77c70c2b057')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-flavor-manage:delete"])
def test_delete_flavor_manage(self):
flavor_id = self.create_flavor()['id']
with self.override_role():
self.flavors_client.delete_flavor(flavor_id)
self.flavors_client.wait_for_resource_deletion(flavor_id)

View File

@ -1,63 +0,0 @@
# 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.
import testtools
from tempest.common import utils
from tempest import config
from tempest.lib import decorators
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
CONF = config.CONF
class FlavorRxtxRbacTest(rbac_base.BaseV2ComputeRbacTest):
@classmethod
def skip_checks(cls):
super(FlavorRxtxRbacTest, cls).skip_checks()
if not utils.is_extension_enabled('os-flavor-rxtx', 'compute'):
msg = "os-flavor-rxtx extension not enabled."
raise cls.skipException(msg)
@testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
"This API extension policy was removed in Stein")
@decorators.idempotent_id('5e1fd9f0-9a08-485a-ad9c-0fc66e4d64b7')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-flavor-rxtx"])
def test_list_flavors_details_rxtx(self):
with self.override_role():
result = self.flavors_client.list_flavors(detail=True)['flavors']
if 'rxtx_factor' not in result[0]:
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute='rxtx_factor')
@testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
"This API extension policy was removed in Stein")
@decorators.idempotent_id('70c55a07-c843-4627-a29d-ba78673c1e63')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-flavor-rxtx"])
def test_get_flavor_rxtx(self):
with self.override_role():
result = self.flavors_client.show_flavor(
CONF.compute.flavor_ref)['flavor']
if 'rxtx_factor' not in result:
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute='rxtx_factor')

View File

@ -1,54 +0,0 @@
# 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.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.compute import rbac_base
CONF = config.CONF
class FloatingIpPoolsRbacTest(rbac_base.BaseV2ComputeRbacTest):
# Tests will fail with a 404 starting from microversion 2.36:
# See the following link for details:
# https://docs.openstack.org/api-ref/compute/#floating-ip-pools-os-floating-ip-pools-deprecated
max_microversion = '2.35'
@classmethod
def setup_clients(cls):
super(FloatingIpPoolsRbacTest, cls).setup_clients()
cls.fip_pools_client = cls.os_primary.floating_ip_pools_client
@classmethod
def skip_checks(cls):
super(FloatingIpPoolsRbacTest, cls).skip_checks()
if not utils.is_extension_enabled('os-floating-ip-pools', 'compute'):
msg = "%s skipped as os-floating-ip-pools extension not enabled." \
% cls.__name__
raise cls.skipException(msg)
if not CONF.network_feature_enabled.floating_ips:
raise cls.skipException("Floating ips are not available")
@decorators.idempotent_id('c1a17153-b25d-4444-a721-5897d7737482')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-floating-ip-pools"])
def test_list_floating_ip_pools(self):
with self.override_role():
self.fip_pools_client.list_floating_ip_pools()

View File

@ -1,101 +0,0 @@
# 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.common import utils
from tempest import config
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.compute import rbac_base
CONF = config.CONF
# RBAC category or action was changed in Victoria release per
# https://docs.openstack.org/api-ref/compute/#floating-ips-os-floating-ips-deprecated
# and
# https://github.com/openstack/nova/blob/master/nova/policies/floating_ips.py#L21
if CONF.policy_feature_enabled.changed_nova_policies_victoria:
_FIP_GET = "os_compute_api:os-floating-ips:list"
_FIP_SHOW = "os_compute_api:os-floating-ips:show"
_FIP_CREATE = "os_compute_api:os-floating-ips:create"
_FIP_DELETE = "os_compute_api:os-floating-ips:delete"
else:
_FIP_GET = "os_compute_api:os-floating-ips"
_FIP_SHOW = "os_compute_api:os-floating-ips"
_FIP_CREATE = "os_compute_api:os-floating-ips"
_FIP_DELETE = "os_compute_api:os-floating-ips"
class FloatingIpsRbacTest(rbac_base.BaseV2ComputeRbacTest):
# Tests will fail with a 404 starting from microversion 2.36:
# See the following link for details:
# https://docs.openstack.org/api-ref/compute/#floating-ips-os-floating-ips-deprecated
max_microversion = '2.35'
@classmethod
def skip_checks(cls):
super(FloatingIpsRbacTest, cls).skip_checks()
if not utils.is_extension_enabled('os-floating-ips', 'compute'):
msg = "%s skipped as os-floating-ips extension not enabled." \
% cls.__name__
raise cls.skipException(msg)
if not CONF.network_feature_enabled.floating_ips:
raise cls.skipException("Floating ips are not available")
@decorators.idempotent_id('ac1b3053-f755-4cda-85a0-30e88b88d7ba')
@rbac_rule_validation.action(
service="nova",
rules=[_FIP_GET])
def test_list_floating_ips(self):
with self.override_role():
self.floating_ips_client.list_floating_ips()
@decorators.idempotent_id('bebe52b3-5269-4e72-80c8-5a4a39c3bfa6')
@rbac_rule_validation.action(
service="nova",
rules=[_FIP_SHOW])
def test_show_floating_ip(self):
body = self.floating_ips_client.create_floating_ip(
pool=CONF.network.floating_network_name)['floating_ip']
self.addCleanup(
self.floating_ips_client.delete_floating_ip, body['id'])
with self.override_role():
self.floating_ips_client.show_floating_ip(body['id'])
@decorators.idempotent_id('2bfb8745-c329-4ee9-95f6-c165a1989dbf')
@rbac_rule_validation.action(
service="nova",
rules=[_FIP_CREATE])
def test_create_floating_ips(self):
with self.override_role():
body = self.floating_ips_client.create_floating_ip(
pool=CONF.network.floating_network_name)['floating_ip']
self.addCleanup(
self.floating_ips_client.delete_floating_ip, body['id'])
@decorators.idempotent_id('d3028373-5027-4e7a-b761-01c515403ecb')
@rbac_rule_validation.action(
service="nova",
rules=[_FIP_DELETE])
def test_delete_floating_ip(self):
body = self.floating_ips_client.create_floating_ip(
pool=CONF.network.floating_network_name)['floating_ip']
self.addCleanup(
test_utils.call_and_ignore_notfound_exc,
self.floating_ips_client.delete_floating_ip, body['id'])
with self.override_role():
self.floating_ips_client.delete_floating_ip(body['id'])

View File

@ -1,64 +0,0 @@
# 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.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.compute import rbac_base
CONF = config.CONF
if CONF.policy_feature_enabled.changed_nova_policies_victoria:
_HOSTS_LIST = "os_compute_api:os-hosts:list"
_HOSTS_SHOW = "os_compute_api:os-hosts:show"
else:
_HOSTS_LIST = "os_compute_api:os-hosts"
_HOSTS_SHOW = "os_compute_api:os-hosts"
class HostsRbacTest(rbac_base.BaseV2ComputeRbacTest):
# These tests will fail with a 404 starting from microversion 2.43:
# See the following links for details:
# https://docs.openstack.org/api-ref/compute/#hosts-os-hosts-deprecated
max_microversion = '2.42'
@classmethod
def skip_checks(cls):
super(HostsRbacTest, cls).skip_checks()
if not utils.is_extension_enabled('os-hosts', 'compute'):
msg = "%s skipped as os-hosts not enabled." % cls.__name__
raise cls.skipException(msg)
@decorators.idempotent_id('035b7935-2fae-4218-8d37-27fa83097494')
@rbac_rule_validation.action(
service="nova",
rules=[_HOSTS_LIST])
def test_list_hosts(self):
with self.override_role():
self.hosts_client.list_hosts()
@decorators.idempotent_id('bc10d8b4-d2c3-4d4e-9d2b-31d1bd3e1b51')
@rbac_rule_validation.action(
service="nova",
rules=[_HOSTS_SHOW])
def test_show_host_details(self):
hosts = self.hosts_client.list_hosts()['hosts']
hosts = [host for host in hosts if host['service'] == 'compute']
self.assertNotEmpty(hosts)
with self.override_role():
self.hosts_client.show_host(hosts[0]['host_name'])

View File

@ -1,138 +0,0 @@
# 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.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.compute import rbac_base
CONF = config.CONF
if CONF.policy_feature_enabled.changed_nova_policies_ussuri:
_HYPERVISOR_LIST = "os_compute_api:os-hypervisors:list"
_HYPERVISOR_SHOW = "os_compute_api:os-hypervisors:show"
_HYPERVISOR_LIST_DETAIL = "os_compute_api:os-hypervisors:list-detail"
_HYPERVISOR_STATISTICS = "os_compute_api:os-hypervisors:statistics"
_HYPERVISOR_UPTIME = "os_compute_api:os-hypervisors:uptime"
_HYPERVISOR_SEARCH = "os_compute_api:os-hypervisors:search"
_HYPERVISOR_SERVER = "os_compute_api:os-hypervisors:servers"
else:
_HYPERVISOR_LIST = "os_compute_api:os-hypervisors"
_HYPERVISOR_SHOW = "os_compute_api:os-hypervisors"
_HYPERVISOR_LIST_DETAIL = "os_compute_api:os-hypervisors"
_HYPERVISOR_STATISTICS = "os_compute_api:os-hypervisors"
_HYPERVISOR_UPTIME = "os_compute_api:os-hypervisors"
_HYPERVISOR_SEARCH = "os_compute_api:os-hypervisors"
_HYPERVISOR_SERVER = "os_compute_api:os-hypervisors"
class HypervisorRbacTest(rbac_base.BaseV2ComputeRbacTest):
@classmethod
def skip_checks(cls):
super(HypervisorRbacTest, cls).skip_checks()
if not utils.is_extension_enabled('os-hypervisors', 'compute'):
msg = "%s skipped as os-hypervisors extension not enabled." \
% cls.__name__
raise cls.skipException(msg)
@classmethod
def resource_setup(cls):
super(HypervisorRbacTest, cls).resource_setup()
cls.hypervisor =\
cls.hypervisor_client.list_hypervisors()['hypervisors'][0]
@decorators.idempotent_id('17bbeb9a-e73e-445f-a771-c794448ef562')
@rbac_rule_validation.action(
service="nova",
rules=[_HYPERVISOR_LIST])
def test_list_hypervisors(self):
with self.override_role():
self.hypervisor_client.list_hypervisors()
@decorators.idempotent_id('36b95c7d-1085-487a-a674-b7c1ca35f520')
@rbac_rule_validation.action(
service="nova",
rules=[_HYPERVISOR_LIST_DETAIL])
def test_list_hypervisors_with_details(self):
with self.override_role():
self.hypervisor_client.list_hypervisors(detail=True)
@decorators.idempotent_id('8a7f6f9e-34a6-4480-8875-bba566c3a581')
@rbac_rule_validation.action(
service="nova",
rules=[_HYPERVISOR_SHOW])
def test_show_hypervisor(self):
with self.override_role():
self.hypervisor_client.show_hypervisor(self.hypervisor['id'])
@decorators.idempotent_id('ca0e465c-6365-4a7f-ae58-6f8ddbca06c2')
@rbac_rule_validation.action(
service="nova",
rules=[_HYPERVISOR_STATISTICS])
def test_show_hypervisor_statistics(self):
with self.override_role():
self.hypervisor_client.show_hypervisor_statistics()
@decorators.idempotent_id('109b37c5-91ba-4da5-b2a2-d7618d84406d')
@rbac_rule_validation.action(
service="nova",
rules=[_HYPERVISOR_UPTIME])
def test_show_hypervisor_uptime(self):
with self.override_role():
self.hypervisor_client.show_hypervisor_uptime(
self.hypervisor['id'])
class HypervisorMaxv252RbacTest(rbac_base.BaseV2ComputeRbacTest):
# These tests will fail with a 404 starting from microversion 2.53:
# See the following links for details:
# https://docs.openstack.org/api-ref/compute/#list-hypervisor-servers
# https://docs.openstack.org/api-ref/compute/#search-hypervisor
max_microversion = '2.52'
@classmethod
def skip_checks(cls):
super(HypervisorMaxv252RbacTest, cls).skip_checks()
if not utils.is_extension_enabled('os-hypervisors', 'compute'):
msg = "%s skipped as os-hypervisors extension not enabled." \
% cls.__name__
raise cls.skipException(msg)
@classmethod
def resource_setup(cls):
super(HypervisorMaxv252RbacTest, cls).resource_setup()
cls.hypervisor =\
cls.hypervisor_client.list_hypervisors()['hypervisors'][0]
@decorators.idempotent_id('b86f03cf-2e79-4d88-9eea-62f761591413')
@rbac_rule_validation.action(
service="nova",
rules=[_HYPERVISOR_SERVER])
def test_list_servers_on_hypervisor(self):
with self.override_role():
self.hypervisor_client.list_servers_on_hypervisor(
self.hypervisor['hypervisor_hostname'])
@decorators.idempotent_id('3dbc71c1-8f04-4674-a67c-dcb2fd99b1b4')
@rbac_rule_validation.action(
service="nova",
rules=[_HYPERVISOR_SEARCH])
def test_search_hypervisor(self):
with self.override_role():
self.hypervisor_client.search_hypervisor(
self.hypervisor['hypervisor_hostname'])

View File

@ -1,314 +0,0 @@
# 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.
import testtools
from tempest.common import image as common_image
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 tempest.lib import exceptions as lib_exc
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
CONF = config.CONF
class ImagesRbacTest(rbac_base.BaseV2ComputeRbacTest):
"""RBAC tests for the Nova images API.
These APIs are proxy calls to the Image service. Consequently, no Nova
policy actions are enforced; instead, only Glance policy actions are
enforced. As such, these tests check that only Glance policy actions are
executed.
"""
# These tests will fail with a 404 starting from microversion 2.36.
# See the following link for details:
# https://docs.openstack.org/api-ref/compute/#images-deprecated
max_microversion = '2.35'
@classmethod
def skip_checks(cls):
super(ImagesRbacTest, cls).skip_checks()
if not CONF.service_available.glance:
skip_msg = ("%s skipped as glance is not available" % cls.__name__)
raise cls.skipException(skip_msg)
@classmethod
def setup_clients(cls):
super(ImagesRbacTest, cls).setup_clients()
if CONF.image_feature_enabled.api_v2:
cls.glance_image_client = cls.os_primary.image_client_v2
elif CONF.image_feature_enabled.api_v1:
cls.glance_image_client = cls.os_primary.image_client
else:
raise lib_exc.InvalidConfiguration(
'Either api_v1 or api_v2 must be True in '
'[image-feature-enabled].')
@classmethod
def resource_setup(cls):
super(ImagesRbacTest, cls).resource_setup()
params = {'name': data_utils.rand_name(cls.__name__ + '-image')}
if CONF.image_feature_enabled.api_v1:
params = {'headers': common_image.image_meta_to_headers(**params)}
cls.image = cls.glance_image_client.create_image(**params)
cls.addClassResourceCleanup(
cls.glance_image_client.wait_for_resource_deletion,
cls.image['id'])
cls.addClassResourceCleanup(
cls.glance_image_client.delete_image, cls.image['id'])
@decorators.idempotent_id('b861f302-b72b-4055-81db-c62ff30b136d')
@rbac_rule_validation.action(
service="glance",
rules=["get_images"])
def test_list_images(self):
with self.override_role():
self.compute_images_client.list_images()
@decorators.idempotent_id('4365ae0f-15ee-4b54-a527-1679faaed140')
@rbac_rule_validation.action(
service="glance",
rules=["get_images"])
def test_list_images_with_details(self):
with self.override_role():
self.compute_images_client.list_images(detail=True)
@decorators.idempotent_id('886dfcae-51bf-4610-9e52-82d7189524c2')
@rbac_rule_validation.action(
service="glance",
rules=["get_image"])
def test_show_image_details(self):
with self.override_role():
self.compute_images_client.show_image(self.image['id'])
@decorators.idempotent_id('5888c7aa-0803-46d4-a3fb-5d4729465cd5')
@rbac_rule_validation.action(
service="glance",
rules=["delete_image"])
def test_delete_image(self):
image = self.glance_image_client.create_image(
name=data_utils.rand_name(self.__class__.__name__ + '-image'))
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.glance_image_client.delete_image, image['id'])
with self.override_role():
self.compute_images_client.delete_image(image['id'])
class ImagesMetadataRbacTest(rbac_base.BaseV2ComputeRbacTest):
"""RBAC tests for the Nova metadata images API.
These APIs are proxy calls to the Image service. Consequently, no Nova
policy actions are enforced; instead, only Glance policy actions are
enforced. As such, these tests check that only Glance policy actions are
executed.
"""
# These tests will fail with a 404 starting from microversion 2.39.
# See the following link for details:
# https://docs.openstack.org/api-ref/compute/#images-deprecated
max_microversion = '2.38'
@classmethod
def skip_checks(cls):
super(ImagesMetadataRbacTest, cls).skip_checks()
if not CONF.service_available.glance:
skip_msg = ("%s skipped as glance is not available" % cls.__name__)
raise cls.skipException(skip_msg)
@classmethod
def setup_clients(cls):
super(ImagesMetadataRbacTest, cls).setup_clients()
if CONF.image_feature_enabled.api_v2:
cls.glance_image_client = cls.os_primary.image_client_v2
elif CONF.image_feature_enabled.api_v1:
cls.glance_image_client = cls.os_primary.image_client
else:
raise lib_exc.InvalidConfiguration(
'Either api_v1 or api_v2 must be True in '
'[image-feature-enabled].')
@classmethod
def resource_setup(cls):
super(ImagesMetadataRbacTest, cls).resource_setup()
params = {'name': data_utils.rand_name(cls.__name__ + '-image')}
if CONF.image_feature_enabled.api_v1:
params = {'headers': common_image.image_meta_to_headers(**params)}
cls.image = cls.glance_image_client.create_image(**params)
cls.addClassResourceCleanup(
cls.glance_image_client.wait_for_resource_deletion,
cls.image['id'])
cls.addClassResourceCleanup(
cls.glance_image_client.delete_image, cls.image['id'])
@decorators.idempotent_id('dbe09d4c-e615-48cb-b908-a06a0f410a8e')
@rbac_rule_validation.action(
service="glance",
rules=["get_image"])
def test_show_image_metadata_item(self):
self.compute_images_client.set_image_metadata(self.image['id'],
meta={'foo': 'bar'})
self.addCleanup(self.compute_images_client.delete_image_metadata_item,
self.image['id'], key='foo')
with self.override_role():
self.compute_images_client.show_image_metadata_item(
self.image['id'], key='foo')
@decorators.idempotent_id('59f66079-d564-47e8-81b0-03c2e84d339e')
@rbac_rule_validation.action(
service="glance",
rules=["get_image"])
def test_list_image_metadata(self):
with self.override_role():
self.compute_images_client.list_image_metadata(self.image['id'])
@decorators.idempotent_id('575604aa-909f-4b1b-a5a5-cfae1f63044b')
@rbac_rule_validation.action(
service="glance",
rules=["modify_image"])
def test_create_image_metadata(self):
with self.override_role():
# NOTE(felipemonteiro): Although the name of the client function
# appears wrong, it's actually correct: update_image_metadata does
# an http post.
self.compute_images_client.update_image_metadata(
self.image['id'], meta={'foo': 'bar'})
self.addCleanup(self.compute_images_client.delete_image_metadata_item,
self.image['id'], key='foo')
@decorators.idempotent_id('fb8c4eb6-00e5-454c-b8bc-0e801ec369f1')
@rbac_rule_validation.action(
service="glance",
rules=["modify_image"])
def test_update_image_metadata(self):
with self.override_role():
self.compute_images_client.set_image_metadata(self.image['id'],
meta={'foo': 'bar'})
self.addCleanup(self.compute_images_client.delete_image_metadata_item,
self.image['id'], key='foo')
@decorators.idempotent_id('9c7c2036-af9b-49a8-8ba1-09b027ee5def')
@rbac_rule_validation.action(
service="glance",
rules=["modify_image"])
def test_update_image_metadata_item(self):
with self.override_role():
self.compute_images_client.set_image_metadata_item(
self.image['id'], meta={'foo': 'bar'}, key='foo')
self.addCleanup(self.compute_images_client.delete_image_metadata_item,
self.image['id'], key='foo')
@decorators.idempotent_id('5f0dc4e6-0761-4613-9bde-0a6acdc78f46')
@rbac_rule_validation.action(
service="glance",
rules=["modify_image"])
def test_delete_image_metadata_item(self):
self.compute_images_client.set_image_metadata(self.image['id'],
meta={'foo': 'bar'})
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.compute_images_client.delete_image_metadata_item,
self.image['id'], key='foo')
with self.override_role():
self.compute_images_client.delete_image_metadata_item(
self.image['id'], key='foo')
class ImageSizeRbacTest(rbac_base.BaseV2ComputeRbacTest):
"""Tests the ``image_size`` compute policies.
NOTE(felipemonteiro): If Patrole is enhanced to test multiple policies
simultaneously, these policy actions can be combined with the related
tests from ``ImagesRbacTest`` above.
"""
# These tests will fail with a 404 starting from microversion 2.36.
# See the following link for details:
# https://docs.openstack.org/api-ref/compute/#images-deprecated
max_microversion = '2.35'
@classmethod
def skip_checks(cls):
super(ImageSizeRbacTest, cls).skip_checks()
if not CONF.service_available.glance:
skip_msg = ("%s skipped as glance is not available" % cls.__name__)
raise cls.skipException(skip_msg)
@classmethod
def setup_clients(cls):
super(ImageSizeRbacTest, cls).setup_clients()
if CONF.image_feature_enabled.api_v2:
cls.glance_image_client = cls.os_primary.image_client_v2
elif CONF.image_feature_enabled.api_v1:
cls.glance_image_client = cls.os_primary.image_client
else:
raise lib_exc.InvalidConfiguration(
'Either api_v1 or api_v2 must be True in '
'[image-feature-enabled].')
@classmethod
def resource_setup(cls):
super(ImageSizeRbacTest, cls).resource_setup()
params = {'name': data_utils.rand_name(cls.__name__ + '-image')}
if CONF.image_feature_enabled.api_v1:
params = {'headers': common_image.image_meta_to_headers(**params)}
cls.image = cls.glance_image_client.create_image(**params)
cls.addClassResourceCleanup(
cls.glance_image_client.wait_for_resource_deletion,
cls.image['id'])
cls.addClassResourceCleanup(
cls.glance_image_client.delete_image, cls.image['id'])
@testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
"This API extension policy was removed in Stein")
@decorators.idempotent_id('fe34d2a6-5743-45bf-8f92-a1d703d7c7ab')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:image-size"])
def test_show_image_includes_image_size(self):
with self.override_role():
body = self.compute_images_client.show_image(self.image['id'])[
'image']
expected_attr = 'OS-EXT-IMG-SIZE:size'
if expected_attr not in body:
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=expected_attr)
@testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
"This API extension policy was removed in Stein")
@decorators.idempotent_id('08342c7d-297d-42ee-b398-90fce2443792')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:image-size"])
def test_list_images_with_details_includes_image_size(self):
with self.override_role():
body = self.compute_images_client.list_images(detail=True)[
'images']
expected_attr = 'OS-EXT-IMG-SIZE:size'
if expected_attr not in body[0]:
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=expected_attr)

View File

@ -1,64 +0,0 @@
# 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.
import datetime
from six.moves.urllib import parse as urllib
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.compute import rbac_base
CONF = config.CONF
if CONF.policy_feature_enabled.changed_nova_policies_ussuri:
_INSTANCE_USAGE_LIST = "os_compute_api:os-instance-usage-audit-log:list"
_INSTANCE_USAGE_SHOW = "os_compute_api:os-instance-usage-audit-log:show"
else:
_INSTANCE_USAGE_LIST = "os_compute_api:os-instance-usage-audit-log"
_INSTANCE_USAGE_SHOW = "os_compute_api:os-instance-usage-audit-log"
class InstanceUsagesAuditLogRbacTest(rbac_base.BaseV2ComputeRbacTest):
@classmethod
def skip_checks(cls):
super(InstanceUsagesAuditLogRbacTest, cls).skip_checks()
if not utils.is_extension_enabled('os-instance-usage-audit-log',
'compute'):
msg = "os-instance-usage-audit-log extension not enabled."
raise cls.skipException(msg)
@decorators.idempotent_id('c80246c0-5c13-4ab0-97ba-91551cd53dc1')
@rbac_rule_validation.action(
service="nova", rules=[_INSTANCE_USAGE_LIST])
def test_list_instance_usage_audit_logs(self):
with self.override_role():
(self.instance_usages_audit_log_client
.list_instance_usage_audit_logs())
@decorators.idempotent_id('ded8bfbd-5d90-4a58-aee0-d31231bf3c9b')
@rbac_rule_validation.action(
service="nova", rules=[_INSTANCE_USAGE_SHOW])
def test_show_instance_usage_audit_log(self):
now = datetime.datetime.now()
with self.override_role():
(self.instance_usages_audit_log_client.
show_instance_usage_audit_log(
urllib.quote(now.strftime("%Y-%m-%d %H:%M:%S"))))

View File

@ -1,66 +0,0 @@
# 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.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.compute import rbac_base
class KeypairsRbacTest(rbac_base.BaseV2ComputeRbacTest):
def _create_keypair(self):
key_name = data_utils.rand_name(self.__class__.__name__ + '-key')
keypair = self.keypairs_client.create_keypair(name=key_name)
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.keypairs_client.delete_keypair,
key_name)
return keypair
@decorators.idempotent_id('16e0ae81-e05f-48cd-b253-cf31ab0732f0')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-keypairs:create"])
def test_create_keypair(self):
with self.override_role():
self._create_keypair()
@decorators.idempotent_id('85a5eb99-40ec-4e77-9358-bee2cdf9d7df')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-keypairs:show"])
def test_show_keypair(self):
kp_name = self._create_keypair()['keypair']['name']
with self.override_role():
self.keypairs_client.show_keypair(kp_name)
@decorators.idempotent_id('6bff9f1c-b809-43c1-8d63-61fbd19d49d3')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-keypairs:delete"])
def test_delete_keypair(self):
kp_name = self._create_keypair()['keypair']['name']
with self.override_role():
self.keypairs_client.delete_keypair(kp_name)
@decorators.idempotent_id('6bb31346-ff7f-4b10-978e-170ac5fcfa3e')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-keypairs:index"])
def test_index_keypair(self):
with self.override_role():
self.keypairs_client.list_keypairs()

View File

@ -1,35 +0,0 @@
# Copyright 2017 AT&T Corporation
# 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 import decorators
from patrole_tempest_plugin import rbac_rule_validation
from patrole_tempest_plugin.tests.api.compute import rbac_base
class LimitsRbacTest(rbac_base.BaseV2ComputeRbacTest):
@classmethod
def skip_checks(cls):
super(LimitsRbacTest, cls).skip_checks()
if not utils.is_extension_enabled('os-limits', 'compute'):
msg = "%s skipped as os-limits not enabled." % cls.__name__
raise cls.skipException(msg)
@rbac_rule_validation.action(service="nova",
rules=["os_compute_api:limits"])
@decorators.idempotent_id('3fb60f83-9a5f-4fdd-89d9-26c3710844a1')
def test_show_limits(self):
with self.override_role():
self.limits_client.show_limits()

View File

@ -1,38 +0,0 @@
# 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.common import utils
from tempest.lib import decorators
from patrole_tempest_plugin import rbac_rule_validation
from patrole_tempest_plugin.tests.api.compute import rbac_base
class MigrationsRbacTest(rbac_base.BaseV2ComputeRbacTest):
@classmethod
def skip_checks(cls):
super(MigrationsRbacTest, cls).skip_checks()
if not utils.is_extension_enabled('os-migrations', 'compute'):
msg = "%s skipped as os-migrations not enabled." % cls.__name__
raise cls.skipException(msg)
@decorators.idempotent_id('5795231c-3729-448c-a072-9a225db1a328')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-migrations:index"])
def test_list_services(self):
with self.override_role():
self.migrations_client.list_migrations()

View File

@ -1,85 +0,0 @@
# 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.common import identity
from tempest.common import tempest_fixtures as fixtures
from tempest.common import utils
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
from patrole_tempest_plugin import rbac_rule_validation
from patrole_tempest_plugin.tests.api.compute import rbac_base
class QuotaClassesRbacTest(rbac_base.BaseV2ComputeRbacTest):
credentials = ['primary', 'admin']
def setUp(self):
# All test cases in this class need to externally lock on doing
# anything with default quota values.
self.useFixture(fixtures.LockFixture('compute_quotas'))
super(QuotaClassesRbacTest, self).setUp()
@classmethod
def skip_checks(cls):
super(QuotaClassesRbacTest, cls).skip_checks()
if not utils.is_extension_enabled('os-quota-class-sets', 'compute'):
msg = "%s skipped as os-quota-class-sets extension not enabled."\
% cls.__name__
raise cls.skipException(msg)
@classmethod
def setup_clients(cls):
super(QuotaClassesRbacTest, cls).setup_clients()
cls.quota_classes_client = cls.os_primary.quota_classes_client
cls.identity_projects_client = cls.os_primary.projects_client
@classmethod
def resource_setup(cls):
super(QuotaClassesRbacTest, cls).resource_setup()
# Create a project with its own quota.
project_name = data_utils.rand_name(cls.__name__)
project_desc = project_name + '-desc'
project = identity.identity_utils(cls.os_admin).create_project(
name=project_name, description=project_desc)
cls.project_id = project['id']
cls.addClassResourceCleanup(
identity.identity_utils(cls.os_admin).delete_project,
cls.project_id)
@decorators.idempotent_id('c10198ed-9df2-440e-a49b-367dadc6de94')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-quota-class-sets:show"])
def test_show_quota_class_set(self):
with self.override_role():
self.quota_classes_client.show_quota_class_set('default')
@decorators.idempotent_id('81889e69-efd2-4e96-bb4c-ee3b646b9755')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-quota-class-sets:update"])
def test_update_quota_class_set(self):
# Update the pre-existing quotas for the project_id.
quota_class_set = self.quota_classes_client.show_quota_class_set(
self.project_id)['quota_class_set']
quota_class_set.pop('id')
for quota, default in quota_class_set.items():
quota_class_set[quota] = default + 100
with self.override_role():
self.quota_classes_client.update_quota_class_set(
self.project_id, **quota_class_set)

View File

@ -1,112 +0,0 @@
# 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.common import identity
from tempest.common import utils
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
from patrole_tempest_plugin import rbac_rule_validation
from patrole_tempest_plugin.tests.api.compute import rbac_base
class QuotaSetsRbacTest(rbac_base.BaseV2ComputeRbacTest):
credentials = ['primary', 'admin']
@classmethod
def setup_clients(cls):
super(QuotaSetsRbacTest, cls).setup_clients()
cls.projects_client = cls.os_primary.projects_client
@classmethod
def skip_checks(cls):
super(QuotaSetsRbacTest, cls).skip_checks()
if not utils.is_extension_enabled('os-quota-sets', 'compute'):
msg = "%s skipped as os-quota-sets extension not enabled."\
% cls.__name__
raise cls.skipException(msg)
@classmethod
def resource_setup(cls):
super(QuotaSetsRbacTest, cls).resource_setup()
cls.tenant_id = cls.quotas_client.tenant_id
cls.user_id = cls.quotas_client.user_id
cls.quota_set = set(('injected_file_content_bytes',
'metadata_items', 'injected_files',
'ram', 'floating_ips', 'fixed_ips', 'key_pair',
'injected_file_path_bytes', 'instances',
'security_group_rules', 'cores',
'security_groups'))
@decorators.idempotent_id('8229ceb0-db6a-4a2c-99c2-de226905d8b6')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-quota-sets:update"])
def test_update_quota_set(self):
default_quota_set = self.quotas_client.show_default_quota_set(
self.tenant_id)['quota_set']
default_quota_set.pop('id')
new_quota_set = {'injected_file_content_bytes': 20480}
with self.override_role():
self.quotas_client.update_quota_set(self.tenant_id,
force=True,
**new_quota_set)
self.addCleanup(self.quotas_client.update_quota_set, self.tenant_id,
**default_quota_set)
@decorators.idempotent_id('58df5613-8f3c-400a-8b4b-2bae624d05e9')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-quota-sets:defaults"])
def test_show_default_quota_set(self):
with self.override_role():
self.quotas_client.show_default_quota_set(self.tenant_id)
@decorators.idempotent_id('e8169ac4-c402-4864-894e-aba74e3a459c')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-quota-sets:show"])
def test_show_quota_set(self):
with self.override_role():
self.quotas_client.show_quota_set(self.tenant_id)
@decorators.idempotent_id('4e240644-bf61-4872-9c32-8289ee2fdbbd')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-quota-sets:delete"])
def test_delete_quota_set(self):
project_name = data_utils.rand_name(
self.__class__.__name__)
project_desc = project_name + '-desc'
project = identity.identity_utils(self.os_admin).create_project(
name=project_name, description=project_desc)
project_id = project['id']
self.addCleanup(
identity.identity_utils(self.os_admin).delete_project,
project_id)
with self.override_role():
self.quotas_client.delete_quota_set(project_id)
@decorators.idempotent_id('ac9184b6-f3b3-4e17-a632-4b92c6500f86')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-quota-sets:detail"])
def test_show_quota_set_details(self):
with self.override_role():
self.quotas_client.show_quota_set(self.tenant_id,
detail=True)

View File

@ -1,187 +0,0 @@
# 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.compute import rbac_base
CONF = config.CONF
if CONF.policy_feature_enabled.changed_nova_policies_ussuri:
_SG_LIST = "os_compute_api:os-security-groups:list"
_SG_ADD = "os_compute_api:os-security-groups:add"
_SG_REMOVE = "os_compute_api:os-security-groups:remove"
else:
_SG_LIST = "os_compute_api:os-security-groups"
_SG_ADD = "os_compute_api:os-security-groups"
_SG_REMOVE = "os_compute_api:os-security-groups"
if CONF.policy_feature_enabled.changed_nova_policies_victoria:
_SG_GET = "os_compute_api:os-security-groups:get"
_SG_SHOW = "os_compute_api:os-security-groups:show"
_SG_CREATE = "os_compute_api:os-security-groups:create"
_SG_UPDATE = "os_compute_api:os-security-groups:update"
_SG_DELETE = "os_compute_api:os-security-groups:delete"
else:
_SG_GET = "os_compute_api:os-security-groups"
_SG_SHOW = "os_compute_api:os-security-groups"
_SG_CREATE = "os_compute_api:os-security-groups"
_SG_UPDATE = "os_compute_api:os-security-groups"
_SG_DELETE = "os_compute_api:os-security-groups"
class SecurtiyGroupsRbacTest(rbac_base.BaseV2ComputeRbacTest):
"""Tests non-deprecated security group policies. Requires network service.
This class tests non-deprecated policies for adding and removing a security
group to and from a server.
"""
@classmethod
def skip_checks(cls):
super(SecurtiyGroupsRbacTest, cls).skip_checks()
# All the tests below require the network service.
# NOTE(gmann) Currently 'network' service is always True in
# utils.get_service_list() So below check is not much of use.
# Commenting the below check as Tempest is moving the get_service_list
# from test.py to utils.
# If we want to check 'network' service availability, then
# get_service_list can be used from new location.
# if not utils.get_service_list()['network']:
# raise cls.skipException(
# 'Skipped because the network service is not available')
@classmethod
def setup_credentials(cls):
# A network and a subnet will be created for these tests.
cls.set_network_resources(network=True, subnet=True)
super(SecurtiyGroupsRbacTest, cls).setup_credentials()
@classmethod
def resource_setup(cls):
super(SecurtiyGroupsRbacTest, cls).resource_setup()
cls.server = cls.create_test_server(wait_until='ACTIVE')
@rbac_rule_validation.action(
service="nova",
rules=[_SG_LIST])
@decorators.idempotent_id('3db159c6-a467-469f-9a25-574197885520')
def test_list_security_groups_by_server(self):
with self.override_role():
self.servers_client.list_security_groups_by_server(
self.server['id'])
@rbac_rule_validation.action(
service="nova",
rules=[_SG_ADD])
@decorators.idempotent_id('ea1ca73f-2d1d-43cb-9a46-900d7927b357')
def test_create_security_group_for_server(self):
sg_name = self.create_security_group()['name']
with self.override_role():
self.servers_client.add_security_group(self.server['id'],
name=sg_name)
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.servers_client.remove_security_group,
self.server['id'], name=sg_name)
@rbac_rule_validation.action(
service="nova",
rules=[_SG_REMOVE])
@decorators.idempotent_id('0ad2e856-e2d3-4ac5-a620-f93d0d3d2626')
def test_remove_security_group_from_server(self):
sg_name = self.create_security_group()['name']
self.servers_client.add_security_group(self.server['id'], name=sg_name)
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.servers_client.remove_security_group,
self.server['id'], name=sg_name)
with self.override_role():
self.servers_client.remove_security_group(
self.server['id'], name=sg_name)
class SecurityGroupsRbacMaxV235Test(rbac_base.BaseV2ComputeRbacTest):
# Tests in this class will fail with a 404 from microversion 2.36,
# according to:
# https://docs.openstack.org/api-ref/compute/#security-groups-os-security-groups-deprecated
max_microversion = '2.35'
@classmethod
def skip_checks(cls):
super(SecurityGroupsRbacMaxV235Test, cls).skip_checks()
# All the tests below require the network service.
# NOTE(gmann) Currently 'network' service is always True in
# utils.get_service_list() So below check is not much of use.
# Commenting the below check as Tempest is moving the get_service_list
# from test.py to utils.
# If we want to check 'network' service availability, then
# get_service_list can be used from new location.
# if not utils.get_service_list()['network']:
# raise cls.skipException(
# 'Skipped because the network service is not available')
@rbac_rule_validation.action(
service="nova",
rules=[_SG_GET])
@decorators.idempotent_id('4ac58e49-48c1-4fca-a6c3-3f95fb99eb77')
def test_list_security_groups(self):
with self.override_role():
self.security_groups_client.list_security_groups()
@rbac_rule_validation.action(
service="nova",
rules=[_SG_CREATE])
@decorators.idempotent_id('e8fe7f5a-69ee-412d-81d3-a8c7a488b54d')
def test_create_security_groups(self):
with self.override_role():
self.create_security_group()['id']
@rbac_rule_validation.action(
service="nova",
rules=[_SG_DELETE])
@decorators.idempotent_id('59127e8e-302d-11e7-93ae-92361f002671')
def test_delete_security_groups(self):
sec_group_id = self.create_security_group()['id']
with self.override_role():
self.security_groups_client.delete_security_group(sec_group_id)
@rbac_rule_validation.action(
service="nova",
rules=[_SG_UPDATE])
@decorators.idempotent_id('3de5c6bc-b822-469e-a627-82427d38b067')
def test_update_security_groups(self):
sec_group_id = self.create_security_group()['id']
new_name = data_utils.rand_name()
new_desc = data_utils.rand_name()
with self.override_role():
self.security_groups_client.update_security_group(
sec_group_id, name=new_name, description=new_desc)
@rbac_rule_validation.action(
service="nova",
rules=[_SG_SHOW])
@decorators.idempotent_id('6edc0320-302d-11e7-93ae-92361f002671')
def test_show_security_groups(self):
sec_group_id = self.create_security_group()['id']
with self.override_role():
self.security_groups_client.show_security_group(sec_group_id)

View File

@ -1,422 +0,0 @@
# 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.
import testtools
from tempest.common import utils
from tempest.common import waiters
from tempest import config
from tempest.lib.common import api_version_utils
from tempest.lib.common.utils import data_utils
from tempest.lib.common.utils import test_utils
from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc
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
CONF = config.CONF
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 setup_credentials(cls):
cls.set_network_resources(network=True, subnet=True, router=True)
super(ServerActionsRbacTest, cls).setup_credentials()
@classmethod
def resource_setup(cls):
super(ServerActionsRbacTest, cls).resource_setup()
cls.server_id = cls.create_test_server(wait_until='ACTIVE')['id']
cls.flavor_ref = CONF.compute.flavor_ref
cls.flavor_ref_alt = CONF.compute.flavor_ref_alt
cls.image_ref = CONF.compute.image_ref
def setUp(self):
super(ServerActionsRbacTest, self).setUp()
try:
waiters.wait_for_server_status(self.servers_client,
self.server_id, 'ACTIVE')
except lib_exc.NotFound:
# If the server was found to be deleted by a previous test,
# a new one is built
server = self.create_test_server(wait_until='ACTIVE')
self.__class__.server_id = server['id']
except Exception:
# Recreating the server in case something happened during a test
self.__class__.server_id = self.recreate_server(self.server_id)
def _stop_server(self):
self.servers_client.stop_server(self.server_id)
waiters.wait_for_server_status(
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.admin_servers_client, self.server_id, 'VERIFY_RESIZE')
def _confirm_resize_server(self):
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.admin_servers_client, self.server_id, 'ACTIVE')
def _shelve_server(self):
self.servers_client.shelve_server(self.server_id)
self.addCleanup(self._cleanup_server_actions,
self.servers_client.unshelve_server,
self.server_id)
offload_time = CONF.compute.shelved_offload_time
if offload_time >= 0:
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.admin_servers_client,
self.server_id,
'SHELVED')
def _pause_server(self):
self.servers_client.pause_server(self.server_id)
self.addCleanup(self._cleanup_server_actions,
self.servers_client.unpause_server,
self.server_id)
waiters.wait_for_server_status(
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']
if server['status'] != 'ACTIVE':
function(server_id, **kwargs)
@decorators.idempotent_id('117f4ff2-8544-437b-824f-5e41cb6640ee')
@testtools.skipUnless(CONF.compute_feature_enabled.pause,
'Pause is not available.')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-pause-server:pause"])
def test_pause_server(self):
with self.override_role():
self._pause_server()
@decorators.idempotent_id('087008cf-82fa-4eeb-ae8b-32c4126456ad')
@testtools.skipUnless(CONF.compute_feature_enabled.pause,
'Pause is not available.')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-pause-server:unpause"])
def test_unpause_server(self):
self._pause_server()
with self.override_role():
self.servers_client.unpause_server(self.server_id)
waiters.wait_for_server_status(
self.servers_client, self.server_id, 'ACTIVE')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:servers:stop"])
@decorators.idempotent_id('ab4a17d2-166f-4a6d-9944-f17baa576cf2')
def test_stop_server(self):
with self.override_role():
self._stop_server()
@decorators.attr(type='slow')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:servers:start"])
@decorators.idempotent_id('8876bfa9-4d10-406e-a335-a57e451abb12')
def test_start_server(self):
self._stop_server()
with self.override_role():
self.servers_client.start_server(self.server_id)
waiters.wait_for_server_status(
self.servers_client, self.server_id, 'ACTIVE')
@decorators.attr(type='slow')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:servers:resize"])
@decorators.idempotent_id('0546fbdd-2d8f-4ce8-ac00-f1e2129d0765')
@testtools.skipUnless(CONF.compute_feature_enabled.resize,
'Resize is not available.')
def test_resize_server(self):
with self.override_role():
self._resize_server(self.flavor_ref_alt)
@decorators.attr(type='slow')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:servers:revert_resize"])
@decorators.idempotent_id('d41b64b8-a72d-414a-a4c5-94e1eb5e5a96')
@testtools.skipUnless(CONF.compute_feature_enabled.resize,
'Resize is not available.')
def test_revert_resize_server(self):
self._resize_server(self.flavor_ref_alt)
with self.override_role():
self.servers_client.revert_resize_server(self.server_id)
waiters.wait_for_server_status(
self.servers_client, self.server_id, 'ACTIVE')
@decorators.attr(type='slow')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:servers:confirm_resize"])
@decorators.idempotent_id('f51620cb-dfcb-4e5d-b421-2e0edaa1316e')
@testtools.skipUnless(CONF.compute_feature_enabled.resize,
'Resize is not available.')
def test_confirm_resize_server(self):
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.override_role():
self._confirm_resize_server()
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:servers:rebuild"])
@decorators.idempotent_id('54b1a30b-c96c-472c-9c83-ccaf6ec7e20b')
def test_rebuild_server(self):
with self.override_role():
self.servers_client.rebuild_server(self.server_id, self.image_ref)
waiters.wait_for_server_status(
self.servers_client, self.server_id, 'ACTIVE')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:servers:reboot"])
@decorators.idempotent_id('19f27856-56e1-44f8-8615-7257f6b85cbb')
def test_reboot_server(self):
with self.override_role():
self.servers_client.reboot_server(self.server_id, type='HARD')
waiters.wait_for_server_status(
self.servers_client, self.server_id, 'ACTIVE')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:servers:index"])
@decorators.idempotent_id('631f0d86-7607-4198-8312-9da2f05464a4')
def test_server_index(self):
with self.override_role():
self.servers_client.list_servers(minimal=True)
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:servers:detail"])
@decorators.idempotent_id('96093480-3ce5-4a8b-b569-aed870379c24')
def test_server_detail(self):
with self.override_role():
self.servers_client.list_servers(detail=True)
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:servers:detail:get_all_tenants"])
@decorators.idempotent_id('a9e5a1c0-acfe-49a2-b2b1-fd8b19d61f71')
def test_server_detail_all_tenants(self):
with self.override_role():
self.servers_client.list_servers(detail=True, all_tenants=1)
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:servers:index:get_all_tenants"])
@decorators.idempotent_id('4b93ba56-69e6-41f5-82c4-84a5c4c42091')
def test_server_index_all_tenants(self):
with self.override_role():
self.servers_client.list_servers(minimal=True, all_tenants=1)
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:servers:show"])
@decorators.idempotent_id('eaaf4f51-31b5-497f-8f0f-f527e5f70b83')
def test_show_server(self):
with self.override_role():
self.servers_client.show_server(self.server_id)
@utils.services('image')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:servers:create_image"])
@decorators.idempotent_id('ba0ac859-99f4-4055-b5e0-e0905a44d331')
def test_create_image(self):
with self.override_role():
# This function will also call show image
self.create_image_from_server(self.server_id, wait_until='ACTIVE')
@utils.services('image', 'volume')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:servers:create_image:allow_volume_backed"])
@decorators.idempotent_id('8b869f73-49b3-4cc4-a0ce-ef64f8e1d6f9')
def test_create_image_from_volume_backed_server(self):
# volume_backed=True creates a volume and create server will be
# requested with 'block_device_mapping_v2' with necessary values for
# this test.
server = self.create_test_server(volume_backed=True,
wait_until='ACTIVE')
with self.override_role():
# This function will also call show image.
image = self.create_image_from_server(server['id'],
wait_until='ACTIVE',
wait_for_server=False)
self.addCleanup(self.compute_images_client.wait_for_resource_deletion,
image['id'])
self.addCleanup(
test_utils.call_and_ignore_notfound_exc,
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')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-create-backup"])
def test_create_backup(self):
# Prioritize glance v2 over v1 for deleting/waiting for image status.
if CONF.image_feature_enabled.api_v2:
glance_client = self.os_primary.image_client_v2
elif CONF.image_feature_enabled.api_v1:
glance_client = self.os_primary.image_client
else:
raise lib_exc.InvalidConfiguration(
'Either api_v1 or api_v2 must be True in '
'[image-feature-enabled].')
backup_name = data_utils.rand_name(self.__class__.__name__ + '-Backup')
with self.override_role():
resp = self.servers_client.create_backup(
self.server_id, backup_type='daily', rotation=1,
name=backup_name).response
# Prior to microversion 2.45, image ID must be parsed from location
# header. With microversion 2.45+, image_id is returned.
if api_version_utils.compare_version_header_to_response(
"OpenStack-API-Version", "2.45", resp, "lt"):
image_id = resp['image_id']
else:
image_id = data_utils.parse_image_id(resp['location'])
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
glance_client.delete_image, image_id)
waiters.wait_for_image_status(glance_client, image_id, 'active')
@decorators.attr(type='slow')
@decorators.idempotent_id('0b70c527-af75-4bed-9ccf-4f1310a8b60f')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-shelve:shelve"])
def test_shelve_server(self):
with self.override_role():
self._shelve_server()
@decorators.attr(type='slow')
@decorators.idempotent_id('4b6e849a-9182-49ff-9257-e97e751b475e')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-shelve:unshelve"])
def test_unshelve_server(self):
self._shelve_server()
with self.override_role():
self.servers_client.unshelve_server(self.server_id)
waiters.wait_for_server_status(
self.servers_client, self.server_id, 'ACTIVE')
class ServerActionsV214RbacTest(rbac_base.BaseV2ComputeRbacTest):
min_microversion = '2.14'
max_microversion = 'latest'
@classmethod
def setup_credentials(cls):
cls.set_network_resources(network=True, subnet=True, router=True)
super(ServerActionsV214RbacTest, cls).setup_credentials()
@classmethod
def resource_setup(cls):
super(ServerActionsV214RbacTest, cls).resource_setup()
cls.server_id = cls.create_test_server(wait_until='ACTIVE')['id']
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-evacuate"])
@decorators.idempotent_id('78ecef3c-faff-412a-83be-47651963eb21')
def test_evacuate_server(self):
fake_host_name = data_utils.rand_name(
self.__class__.__name__ + '-fake-host')
# NOTE(felipemonteiro): Because evacuating a server is a risky action
# to test in the gates, a 404 is coerced using a fake host. However,
# the policy check is done before the 404 is thrown.
with self.override_role():
self.assertRaisesRegex(
lib_exc.NotFound,
"Compute host %s not found." % fake_host_name,
self.servers_client.evacuate_server, self.server_id,
host=fake_host_name)
class ServerActionsV216RbacTest(rbac_base.BaseV2ComputeRbacTest):
# This class has test case(s) that requires at least microversion 2.16.
# See the following link for details:
# https://docs.openstack.org/api-ref/compute/#show-server-details
min_microversion = '2.16'
max_microversion = 'latest'
@classmethod
def setup_credentials(cls):
cls.set_network_resources(network=True, subnet=True, router=True)
super(ServerActionsV216RbacTest, cls).setup_credentials()
@classmethod
def resource_setup(cls):
super(ServerActionsV216RbacTest, cls).resource_setup()
cls.server_id = cls.create_test_server(wait_until='ACTIVE')['id']
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:servers:show:host_status"])
@decorators.idempotent_id('736da575-86f8-4b2a-9902-dd37dc9a409b')
def test_show_server_host_status(self):
with self.override_role():
server = self.servers_client.show_server(self.server_id)['server']
if 'host_status' not in server:
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute='host_status')

View File

@ -1,109 +0,0 @@
# 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 patrole_tempest_plugin import rbac_rule_validation
from patrole_tempest_plugin.tests.api.compute import rbac_base
CONF = config.CONF
class ServerConsolesRbacTest(rbac_base.BaseV2ComputeRbacTest):
@classmethod
def skip_checks(cls):
super(ServerConsolesRbacTest, cls).skip_checks()
if not CONF.compute_feature_enabled.console_output:
raise cls.skipException('Console output not available.')
@classmethod
def setup_credentials(cls):
cls.set_network_resources(network=True, subnet=True, router=True)
super(ServerConsolesRbacTest, cls).setup_credentials()
@classmethod
def resource_setup(cls):
super(ServerConsolesRbacTest, cls).resource_setup()
cls.server_id = cls.create_test_server(wait_until='ACTIVE')['id']
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-console-output"])
@decorators.idempotent_id('90fd80f6-456c-11e7-a919-92ebcb67fe33')
def test_get_console_output(self):
with self.override_role():
self.servers_client.get_console_output(self.server_id)
class ServerConsolesMaxV25RbacTest(rbac_base.BaseV2ComputeRbacTest):
max_microversion = '2.5'
@classmethod
def skip_checks(cls):
super(ServerConsolesMaxV25RbacTest, cls).skip_checks()
if not CONF.compute_feature_enabled.console_output:
raise cls.skipException('Console output not available.')
@classmethod
def setup_credentials(cls):
cls.set_network_resources(network=True, subnet=True, router=True)
super(ServerConsolesMaxV25RbacTest, cls).setup_credentials()
@classmethod
def resource_setup(cls):
super(ServerConsolesMaxV25RbacTest, cls).resource_setup()
cls.server_id = cls.create_test_server(wait_until='ACTIVE')['id']
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-remote-consoles"])
@decorators.idempotent_id('b0a72c02-9b15-4dcb-b186-efe8753370ab')
def test_get_vnc_console_output(self):
with self.override_role():
self.servers_client.get_vnc_console(self.server_id, type="novnc")
class ServerConsolesV26RbacTest(rbac_base.BaseV2ComputeRbacTest):
min_microversion = '2.6'
max_microversion = 'latest'
@classmethod
def skip_checks(cls):
super(ServerConsolesV26RbacTest, cls).skip_checks()
if not CONF.compute_feature_enabled.console_output:
raise cls.skipException('Console output not available.')
@classmethod
def setup_credentials(cls):
cls.set_network_resources(network=True, subnet=True, router=True)
super(ServerConsolesV26RbacTest, cls).setup_credentials()
@classmethod
def resource_setup(cls):
super(ServerConsolesV26RbacTest, cls).resource_setup()
cls.server_id = cls.create_test_server(wait_until='ACTIVE')['id']
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-remote-consoles"])
@decorators.idempotent_id('879597de-87e0-4da9-a60a-28c8088dc508')
def test_get_remote_console_output(self):
with self.override_role():
self.servers_client.get_remote_console(self.server_id,
"novnc", "vnc")

View File

@ -1,64 +0,0 @@
# 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.common import utils
from tempest.lib import decorators
from patrole_tempest_plugin import rbac_rule_validation
from patrole_tempest_plugin.tests.api.compute import rbac_base
class ServerGroupsRbacTest(rbac_base.BaseV2ComputeRbacTest):
@classmethod
def skip_checks(cls):
super(ServerGroupsRbacTest, cls).skip_checks()
if not utils.is_extension_enabled('os-server-groups', 'compute'):
msg = "%s skipped as os-server-groups not enabled." % cls.__name__
raise cls.skipException(msg)
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-server-groups:create"])
@decorators.idempotent_id('7f3eae94-6130-47e9-81ac-34009f55be2f')
def test_create_server_group(self):
with self.override_role():
self.create_test_server_group()
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-server-groups:delete"])
@decorators.idempotent_id('832d9be3-632e-47b2-93d2-5897db43e3e2')
def test_delete_server_group(self):
server_group = self.create_test_server_group()
with self.override_role():
self.server_groups_client.delete_server_group(server_group['id'])
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-server-groups:index"])
@decorators.idempotent_id('5eccd67f-5945-483b-b1c8-de851ebfc1c1')
def test_list_server_groups(self):
with self.override_role():
self.server_groups_client.list_server_groups()
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-server-groups:show"])
@decorators.idempotent_id('62534e3f-7e99-4a3d-a08e-33e056460cf2')
def test_show_server_group(self):
server_group = self.create_test_server_group()
with self.override_role():
self.server_groups_client.show_server_group(server_group['id'])

View File

@ -1,89 +0,0 @@
# 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.lib import decorators
from patrole_tempest_plugin import rbac_rule_validation
from patrole_tempest_plugin.tests.api.compute import rbac_base
class ServerMetadataRbacTest(rbac_base.BaseV2ComputeRbacTest):
@classmethod
def setup_credentials(cls):
cls.set_network_resources(network=True, subnet=True, router=True)
super(ServerMetadataRbacTest, cls).setup_credentials()
@classmethod
def resource_setup(cls):
super(ServerMetadataRbacTest, cls).resource_setup()
cls.server = cls.create_test_server(metadata={}, wait_until='ACTIVE')
cls.meta = {'default_key': 'value1', 'delete_key': 'value2'}
def setUp(self):
super(ServerMetadataRbacTest, self).setUp()
self.servers_client.set_server_metadata(self.server['id'], self.meta)[
'metadata']
@decorators.idempotent_id('b07bbc27-58e2-4581-869d-ad228cec5d9a')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:server-metadata:index"])
def test_list_server_metadata(self):
with self.override_role():
self.servers_client.list_server_metadata(self.server['id'])
@decorators.idempotent_id('6e76748b-2417-4fa2-b41a-c0cc4bff356b')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:server-metadata:update_all"])
def test_set_server_metadata(self):
with self.override_role():
self.servers_client.set_server_metadata(self.server['id'], {})
@decorators.idempotent_id('1060bac4-fe16-4a77-be64-d8e482a06eab')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:server-metadata:create"])
def test_update_server_metadata(self):
with self.override_role():
self.servers_client.update_server_metadata(self.server['id'], {})
@decorators.idempotent_id('93dd8323-d3fa-48d1-8bd6-91c1b62fc341')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:server-metadata:show"])
def test_show_server_metadata_item(self):
with self.override_role():
self.servers_client.show_server_metadata_item(
self.server['id'], 'default_key')
@decorators.idempotent_id('79511293-4bd7-447d-ba7e-634d0f4da70c')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:server-metadata:update"])
def test_set_server_metadata_item(self):
with self.override_role():
self.servers_client.set_server_metadata_item(
self.server['id'], 'default_key', {'default_key': 'value2'})
@decorators.idempotent_id('feec5064-678d-40bc-a88f-c856e18d1e31')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:server-metadata:delete"])
def test_delete_server_metadata_item(self):
with self.override_role():
self.servers_client.delete_server_metadata_item(
self.server['id'], 'delete_key')

View File

@ -1,92 +0,0 @@
# 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.
import testtools
from tempest.common import waiters
from tempest import config
from tempest.lib import decorators
from patrole_tempest_plugin import rbac_rule_validation
from patrole_tempest_plugin.tests.api.compute import rbac_base as base
CONF = config.CONF
class MigrateServerV225RbacTest(base.BaseV2ComputeRbacTest):
min_microversion = '2.25'
max_microversion = 'latest'
block_migration = 'auto'
@classmethod
def skip_checks(cls):
super(MigrateServerV225RbacTest, cls).skip_checks()
if CONF.compute.min_compute_nodes < 2:
raise cls.skipException(
"Less than 2 compute nodes, skipping migration tests.")
def _get_server_details(self, server_id):
body = self.servers_client.show_server(server_id)['server']
return body
def _get_host_for_server(self, server_id):
return self._get_server_details(server_id)['OS-EXT-SRV-ATTR:host']
def _get_host_other_than(self, host):
for target_host in self._get_compute_hostnames():
if host != target_host:
return target_host
def _get_compute_hostnames(self):
body = self.hosts_client.list_hosts()['hosts']
return [
host_record['host_name']
for host_record in body
if host_record['service'] == 'compute'
]
@decorators.attr(type='slow')
@testtools.skipUnless(CONF.compute_feature_enabled.cold_migration,
'Cold migration not available.')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-migrate-server:migrate"])
@decorators.idempotent_id('c6f1607c-9fed-4c00-807e-9ba675b98b1b')
def test_cold_migration(self):
server = self.create_test_server(wait_until="ACTIVE")
with self.override_role():
self.servers_client.migrate_server(server['id'])
waiters.wait_for_server_status(self.servers_client,
server['id'], 'VERIFY_RESIZE')
@decorators.attr(type='slow')
@testtools.skipUnless(CONF.compute_feature_enabled.live_migration,
'Live migration feature is not enabled.')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-migrate-server:migrate_live"])
@decorators.idempotent_id('33520834-72c8-4edb-a189-a2e0fc9eb0d3')
def test_migration_live(self):
server_id = self.create_test_server(wait_until="ACTIVE",
volume_backed=False)['id']
actual_host = self._get_host_for_server(server_id)
target_host = self._get_host_other_than(actual_host)
with self.override_role():
self.servers_client.live_migrate_server(
server_id, host=target_host,
block_migration=self.block_migration)
waiters.wait_for_server_status(self.servers_client,
server_id, "ACTIVE")

View File

@ -1,828 +0,0 @@
# 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.
import netaddr
import testtools
from tempest.common import utils
from tempest.common import waiters
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 tempest.lib import exceptions as lib_exc
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
CONF = config.CONF
if CONF.policy_feature_enabled.changed_nova_policies_ussuri:
_DEFERRED_FORCE = "os_compute_api:os-deferred-delete:force"
_ATTACH_INTERFACES_LIST = "os_compute_api:os-attach-interfaces:list"
_ATTACH_INTERFACES_SHOW = "os_compute_api:os-attach-interfaces:show"
_INSTANCE_ACTIONS_LIST = "os_compute_api:os-instance-actions:list"
_SERVER_PASSWORD_SHOW = "os_compute_api:os-server-password:show"
_SERVER_PASSWORD_CLEAR = "os_compute_api:os-server-password:clear"
else:
_DEFERRED_FORCE = "os_compute_api:os-deferred-delete"
_ATTACH_INTERFACES_LIST = "os_compute_api:os-attach-interfaces"
_ATTACH_INTERFACES_SHOW = "os_compute_api:os-attach-interfaces"
_INSTANCE_ACTIONS_LIST = "os_compute_api:os-instance-actions"
_SERVER_PASSWORD_SHOW = "os_compute_api:os-server-password"
_SERVER_PASSWORD_CLEAR = "os_compute_api:os-server-password"
if CONF.policy_feature_enabled.changed_nova_policies_victoria:
_MULTINIC_ADD = "os_compute_api:os-multinic:add"
else:
_MULTINIC_ADD = "os_compute_api:os-multinic"
class MiscPolicyActionsRbacTest(rbac_base.BaseV2ComputeRbacTest):
"""Test multiple policy actions that require a server to be created.
Minimize the number of servers that need to be created across classes
by consolidating test cases related to different policy "families" into
one class. This reduces the risk of running into `BuildErrorException`
errors being raised due to too many servers being created simultaneously
especially when higher concurrency is used.
Only applies to:
* policy "families" that require server creation
* small policy "families" -- i.e. containing one to three policies
Tests are ordered by policy name.
"""
credentials = ['primary', 'admin']
@classmethod
def setup_credentials(cls):
cls.set_network_resources(network=True, subnet=True, router=True)
super(MiscPolicyActionsRbacTest, cls).setup_credentials()
@classmethod
def resource_setup(cls):
super(MiscPolicyActionsRbacTest, cls).resource_setup()
cls.server = cls.create_test_server(wait_until='ACTIVE')
def setUp(self):
super(MiscPolicyActionsRbacTest, self).setUp()
try:
waiters.wait_for_server_status(self.servers_client,
self.server['id'], 'ACTIVE')
except lib_exc.NotFound:
# If the server was found to be deleted by a previous test,
# a new one is built
self.__class__.server = self.create_test_server(
wait_until='ACTIVE')
except Exception:
# Rebuilding the server in case something happened during a test
self.__class__.server = self._rebuild_server(self.server['id'])
def _rebuild_server(self, server_id):
# Destroy an existing server and creates a new one.
if server_id:
self.delete_server(server_id)
self.password = data_utils.rand_password()
return self.create_test_server(
wait_until='ACTIVE', adminPass=self.password)
@utils.requires_ext(extension='os-admin-actions', service='compute')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-admin-actions:reset_state"])
@decorators.idempotent_id('ae84dd0b-f364-462e-b565-3457f9c019ef')
def test_reset_server_state(self):
"""Test reset server state, part of os-admin-actions."""
with self.override_role():
self.servers_client.reset_state(self.server['id'], state='error')
self.addCleanup(self.servers_client.reset_state, self.server['id'],
state='active')
@utils.requires_ext(extension='os-admin-actions', service='compute')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-admin-actions:inject_network_info"])
@decorators.idempotent_id('ce48c340-51c1-4cff-9b6e-0cc5ef008630')
def test_inject_network_info(self):
"""Test inject network info, part of os-admin-actions."""
with self.override_role():
self.servers_client.inject_network_info(self.server['id'])
@testtools.skipIf(
CONF.policy_feature_enabled.removed_nova_policies_wallaby,
"This API extension policy was removed in Wallaby")
@utils.requires_ext(extension='os-admin-actions', service='compute')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-admin-actions:reset_network"])
@decorators.idempotent_id('2911a242-15c4-4fcb-80d5-80a8930661b0')
def test_reset_network(self):
"""Test reset network, part of os-admin-actions."""
with self.override_role():
self.servers_client.reset_network(self.server['id'])
@testtools.skipUnless(CONF.compute_feature_enabled.change_password,
'Change password not available.')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-admin-password"])
@decorators.idempotent_id('908a7d59-3a66-441c-94cf-38e57ed14956')
def test_change_server_password(self):
"""Test change admin password, part of os-admin-password."""
original_password = self.servers_client.show_password(
self.server['id'])
with self.override_role():
self.servers_client.change_password(
self.server['id'], adminPass=data_utils.rand_password())
self.addCleanup(self.servers_client.change_password, self.server['id'],
adminPass=original_password)
waiters.wait_for_server_status(
self.servers_client, self.server['id'], 'ACTIVE')
@testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
"This API extension policy was removed in Stein")
@utils.requires_ext(extension='os-config-drive', service='compute')
@decorators.idempotent_id('2c82e819-382d-4d6f-87f0-a45954cbbc64')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-config-drive"])
def test_list_servers_with_details_config_drive(self):
"""Test list servers with config_drive property in response body."""
with self.override_role():
body = self.servers_client.list_servers(detail=True)['servers']
expected_attr = 'config_drive'
# If the first server contains "config_drive", then all the others do.
if expected_attr not in body[0]:
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=expected_attr)
@testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
"This API extension policy was removed in Stein")
@utils.requires_ext(extension='os-config-drive', service='compute')
@decorators.idempotent_id('55c62ef7-b72b-4970-acc6-05b0a4316e5d')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-config-drive"])
def test_show_server_config_drive(self):
"""Test show server with config_drive property in response body."""
with self.override_role():
body = self.servers_client.show_server(self.server['id'])['server']
expected_attr = 'config_drive'
if expected_attr not in body:
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=expected_attr)
@utils.requires_ext(extension='os-deferred-delete', service='compute')
@decorators.idempotent_id('189bfed4-1e6d-475c-bb8c-d57e60895391')
@rbac_rule_validation.action(
service="nova",
rules=[_DEFERRED_FORCE])
def test_force_delete_server(self):
"""Test force delete server, part of os-deferred-delete."""
with self.override_role():
# Force-deleting a server enforces os-deferred-delete.
self.servers_client.force_delete_server(self.server['id'])
@testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
"This API extension policy was removed in Stein")
@decorators.idempotent_id('d873740a-7b10-40a9-943d-7cc18115370e')
@utils.requires_ext(extension='OS-EXT-AZ', service='compute')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-extended-availability-zone"])
def test_list_servers_with_details_extended_availability_zone(self):
"""Test list servers OS-EXT-AZ:availability_zone attr in resp body."""
expected_attr = 'OS-EXT-AZ:availability_zone'
with self.override_role():
body = self.servers_client.list_servers(detail=True)['servers']
# If the first server contains `expected_attr`, then all the others do.
if expected_attr not in body[0]:
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=expected_attr)
@testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
"This API extension policy was removed in Stein")
@decorators.idempotent_id('727e5360-770a-4b9c-8015-513a40216635')
@utils.requires_ext(extension='OS-EXT-AZ', service='compute')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-extended-availability-zone"])
def test_show_server_extended_availability_zone(self):
"""Test show server OS-EXT-AZ:availability_zone attr in resp body."""
expected_attr = 'OS-EXT-AZ:availability_zone'
with self.override_role():
body = self.servers_client.show_server(self.server['id'])['server']
if expected_attr not in body:
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=expected_attr)
@testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
"This API extension policy was removed in Stein")
@decorators.idempotent_id('4aa5d93e-4887-468a-8eb4-b6eca0ca6437')
@utils.requires_ext(extension='OS-EXT-SRV-ATTR', service='compute')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-extended-server-attributes"])
def test_list_servers_extended_server_attributes(self):
"""Test list servers with details, with extended server attributes in
response body.
"""
with self.override_role():
body = self.servers_client.list_servers(detail=True)['servers']
# NOTE(felipemonteiro): The attributes included below should be
# returned by all microversions. We don't include tests for other
# microversions since Tempest schema validation takes care of that in
# `show_server` call above. (Attributes there are *optional*.)
for attr in ('host', 'instance_name'):
whole_attr = 'OS-EXT-SRV-ATTR:%s' % attr
if whole_attr not in body[0]:
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=whole_attr)
@testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
"This API extension policy was removed in Stein")
@decorators.idempotent_id('2ed7aee2-94b2-4a9f-ae63-a51b7f94fe30')
@utils.requires_ext(extension='OS-EXT-SRV-ATTR', service='compute')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-extended-server-attributes"])
def test_show_server_extended_server_attributes(self):
"""Test show server with extended server attributes in response
body.
"""
with self.override_role():
body = self.servers_client.show_server(self.server['id'])['server']
# NOTE(felipemonteiro): The attributes included below should be
# returned by all microversions. We don't include tests for other
# microversions since Tempest schema validation takes care of that in
# `show_server` call above. (Attributes there are *optional*.)
for attr in ('host', 'instance_name'):
whole_attr = 'OS-EXT-SRV-ATTR:%s' % attr
if whole_attr not in body:
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=whole_attr)
@testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
"This API extension policy was removed in Stein")
@decorators.idempotent_id('82053c27-3134-4003-9b55-bc9fafdb0e3b')
@utils.requires_ext(extension='OS-EXT-STS', service='compute')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-extended-status"])
def test_list_servers_extended_status(self):
"""Test list servers with extended properties in response body."""
with self.override_role():
body = self.servers_client.list_servers(detail=True)['servers']
expected_attrs = ('OS-EXT-STS:task_state', 'OS-EXT-STS:vm_state',
'OS-EXT-STS:power_state')
for attr in expected_attrs:
if attr not in body[0]:
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=attr)
@testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
"This API extension policy was removed in Stein")
@decorators.idempotent_id('7d2620a5-eea1-4a8b-96ea-86ad77a73fc8')
@utils.requires_ext(extension='OS-EXT-STS', service='compute')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-extended-status"])
def test_show_server_extended_status(self):
"""Test show server with extended properties in response body."""
with self.override_role():
body = self.servers_client.show_server(self.server['id'])['server']
expected_attrs = ('OS-EXT-STS:task_state', 'OS-EXT-STS:vm_state',
'OS-EXT-STS:power_state')
for attr in expected_attrs:
if attr not in body:
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=attr)
@testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
"This API extension policy was removed in Stein")
@decorators.idempotent_id('21e39cbe-6c32-48fc-80dd-3e1fece6053f')
@utils.requires_ext(extension='os-extended-volumes', service='compute')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-extended-volumes"])
def test_list_servers_with_details_extended_volumes(self):
"""Test list servers os-extended-volumes:volumes_attached attr in resp
body.
"""
expected_attr = 'os-extended-volumes:volumes_attached'
with self.override_role():
body = self.servers_client.list_servers(detail=True)['servers']
if expected_attr not in body[0]:
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=expected_attr)
@testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
"This API extension policy was removed in Stein")
@decorators.idempotent_id('7f163708-0d25-4138-8512-dfdd72a92989')
@utils.requires_ext(extension='os-extended-volumes', service='compute')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-extended-volumes"])
def test_show_server_extended_volumes(self):
"""Test show server os-extended-volumes:volumes_attached attr in resp
body.
"""
expected_attr = 'os-extended-volumes:volumes_attached'
with self.override_role():
body = self.servers_client.show_server(self.server['id'])['server']
if expected_attr not in body:
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=expected_attr)
@utils.requires_ext(extension='os-instance-actions', service='compute')
@decorators.idempotent_id('9d1b131d-407e-4fa3-8eef-eb2c4526f1da')
@rbac_rule_validation.action(
service="nova",
rules=[_INSTANCE_ACTIONS_LIST])
def test_list_instance_actions(self):
"""Test list instance actions, part of os-instance-actions."""
with self.override_role():
self.servers_client.list_instance_actions(self.server['id'])
@utils.requires_ext(extension='os-instance-actions', service='compute')
@decorators.idempotent_id('eb04c439-4215-4029-9ccb-5b3c041bfc25')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-instance-actions:events"])
def test_show_instance_action(self):
"""Test show instance action, part of os-instance-actions.
Expect "events" details to be included in the response body.
"""
# NOTE: "os_compute_api:os-instance-actions" is also enforced.
request_id = self.server.response['x-compute-request-id']
with self.override_role():
instance_action = self.servers_client.show_instance_action(
self.server['id'], request_id)['instanceAction']
if 'events' not in instance_action:
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute='events')
# Microversion 2.51+ returns 'events' always, but not 'traceback'. If
# 'traceback' is also present then policy enforcement passed.
if 'traceback' not in instance_action['events'][0]:
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute='events.traceback')
@testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
"This API extension policy was removed in Stein")
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-keypairs"])
@decorators.idempotent_id('81e6fa34-c06b-42ca-b195-82bf8699b940')
def test_show_server_keypair(self):
with self.override_role():
result = self.servers_client.show_server(self.server['id'])[
'server']
if 'key_name' not in result:
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute='key_name')
@testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
"This API extension policy was removed in Stein")
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-keypairs"])
@decorators.idempotent_id('41ca4280-ec59-4b80-a9b1-6bc6366faf39')
def test_list_servers_keypairs(self):
with self.override_role():
result = self.servers_client.list_servers(detail=True)['servers']
if 'key_name' not in result[0]:
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute='key_name')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-lock-server:lock"])
@decorators.idempotent_id('b81e10fb-1864-498f-8c1d-5175c6fec5fb')
def test_lock_server(self):
"""Test lock server, part of os-lock-server."""
with self.override_role():
self.servers_client.lock_server(self.server['id'])
self.addCleanup(self.servers_client.unlock_server, self.server['id'])
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-lock-server:unlock"])
@decorators.idempotent_id('d50ef8e8-4bce-11e7-b114-b2f933d5fe66')
def test_unlock_server(self):
"""Test unlock server, part of os-lock-server."""
self.servers_client.lock_server(self.server['id'])
self.addCleanup(self.servers_client.unlock_server, self.server['id'])
with self.override_role():
self.servers_client.unlock_server(self.server['id'])
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-lock-server:unlock",
"os_compute_api:os-lock-server:unlock:unlock_override"])
@decorators.idempotent_id('40dfeef9-73ee-48a9-be19-a219875de457')
def test_unlock_server_override(self):
"""Test force unlock server, part of os-lock-server.
In order to trigger the unlock:unlock_override policy instead
of the unlock policy, the server must be locked by a different
user than the one who is attempting to unlock it.
"""
self.os_admin.servers_client.lock_server(self.server['id'])
self.addCleanup(self.servers_client.unlock_server, self.server['id'])
with self.override_role():
self.servers_client.unlock_server(self.server['id'])
@utils.requires_ext(extension='os-rescue', service='compute')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-rescue"])
@decorators.idempotent_id('fbbb2afc-ed0e-4552-887d-ac00fb5d436e')
def test_rescue_server(self):
"""Test rescue server, part of os-rescue."""
with self.override_role():
self.servers_client.rescue_server(self.server['id'])
waiters.wait_for_server_status(
self.servers_client, self.server['id'], 'RESCUE')
@decorators.idempotent_id('ac2d956f-d6a3-4184-b814-b44d05c9574c')
@utils.requires_ext(extension='os-rescue', service='compute')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-rescue"])
def test_unrescue_server(self):
"""Test unrescue server, part of os-rescue."""
self.servers_client.rescue_server(self.server['id'])
waiters.wait_for_server_status(
self.servers_client, self.server['id'], 'RESCUE')
with self.override_role():
self.servers_client.unrescue_server(self.server['id'])
waiters.wait_for_server_status(
self.servers_client, self.server['id'], 'ACTIVE')
@utils.requires_ext(extension='os-server-diagnostics', service='compute')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-server-diagnostics"])
@decorators.idempotent_id('5dabfcc4-bedb-417b-8247-b3ee7c5c0f3e')
def test_show_server_diagnostics(self):
"""Test show server diagnostics, part of os-server-diagnostics."""
with self.override_role():
self.servers_client.show_server_diagnostics(self.server['id'])
@utils.requires_ext(extension='os-server-password', service='compute')
@decorators.idempotent_id('aaf43f78-c178-4581-ac18-14afd3f1f6ba')
@rbac_rule_validation.action(
service="nova",
rules=[_SERVER_PASSWORD_CLEAR])
def test_delete_server_password(self):
"""Test delete server password, part of os-server-password."""
with self.override_role():
self.servers_client.delete_password(self.server['id'])
@utils.requires_ext(extension='os-server-password', service='compute')
@rbac_rule_validation.action(
service="nova",
rules=[_SERVER_PASSWORD_SHOW])
@decorators.idempotent_id('f677971a-7d20-493c-977f-6ff0a74b5b2c')
def test_get_server_password(self):
"""Test show server password, part of os-server-password."""
with self.override_role():
self.servers_client.show_password(self.server['id'])
@testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
"This API extension policy was removed in Stein")
@utils.requires_ext(extension='OS-SRV-USG', service='compute')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-server-usage"])
@decorators.idempotent_id('f0437ead-b9fb-462a-9f3d-ce53fac9d57a')
def test_show_server_usage(self):
"""Test show server usage, part of os-server-usage.
TODO(felipemonteiro): Once multiple policy testing is supported, this
test should also check for additional policies mentioned here:
https://git.openstack.org/cgit/openstack/nova/tree/nova/policies/server_usage.py?h=17.0.0
"""
expected_attrs = ('OS-SRV-USG:launched_at',
'OS-SRV-USG:terminated_at')
with self.override_role():
body = self.servers_client.show_server(self.server['id'])['server']
for expected_attr in expected_attrs:
if expected_attr not in body:
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=expected_attr)
@utils.requires_ext(extension='os-simple-tenant-usage', service='compute')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-simple-tenant-usage:list"])
@decorators.idempotent_id('2aef094f-0452-4df6-a66a-0ec22a92b16e')
def test_list_simple_tenant_usages(self):
"""Test list tenant usages, part of os-simple-tenant-usage."""
with self.override_role():
self.tenant_usages_client.list_tenant_usages()
@utils.requires_ext(extension='os-simple-tenant-usage', service='compute')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-simple-tenant-usage:show"])
@decorators.idempotent_id('fe7eacda-15c4-4bf7-93ef-1091c4546a9d')
def test_show_simple_tenant_usage(self):
"""Test show tenant usage, part of os-simple-tenant-usage."""
tenant_id = self.os_primary.credentials.tenant_id
with self.override_role():
self.tenant_usages_client.show_tenant_usage(tenant_id=tenant_id)
@testtools.skipUnless(CONF.compute_feature_enabled.suspend,
"Suspend compute feature is not available.")
@decorators.idempotent_id('b775930f-237c-431c-83ae-d33ed1b9700b')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-suspend-server:suspend"])
def test_suspend_server(self):
"""Test suspend server, part of os-suspend-server."""
with self.override_role():
self.servers_client.suspend_server(self.server['id'])
self.addCleanup(self.servers_client.resume_server, self.server['id'])
waiters.wait_for_server_status(
self.servers_client, self.server['id'], 'SUSPENDED')
@testtools.skipUnless(CONF.compute_feature_enabled.suspend,
"Suspend compute feature is not available.")
@decorators.idempotent_id('4d90bd02-11f8-45b1-a8a1-534665584675')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-suspend-server:resume"])
def test_resume_server(self):
"""Test resume server, part of os-suspend-server."""
self.servers_client.suspend_server(self.server['id'])
waiters.wait_for_server_status(
self.servers_client, self.server['id'], 'SUSPENDED')
with self.override_role():
self.servers_client.resume_server(self.server['id'])
waiters.wait_for_server_status(
self.servers_client, self.server['id'], 'ACTIVE')
class MiscPolicyActionsNetworkRbacTest(rbac_base.BaseV2ComputeRbacTest):
"""Test multiple policy actions that require a server to be created.
Only applies to:
* policy "families" that require server creation
* small policy "families" -- i.e. containing one to three policies
* tests that require network resources
"""
@classmethod
def skip_checks(cls):
super(MiscPolicyActionsNetworkRbacTest, cls).skip_checks()
# All tests below require Neutron availability.
if not CONF.service_available.neutron:
raise cls.skipException(
'%s skipped as Neutron is required' % cls.__name__)
@classmethod
def setup_clients(cls):
super(MiscPolicyActionsNetworkRbacTest, cls).setup_clients()
cls.servers_admin_client = cls.os_admin.servers_client
@classmethod
def setup_credentials(cls):
cls.prepare_instance_network()
super(MiscPolicyActionsNetworkRbacTest, cls).setup_credentials()
@classmethod
def resource_setup(cls):
def _cleanup_ports(network_id):
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,
port['id'])
super(MiscPolicyActionsNetworkRbacTest, cls).resource_setup()
cls.server = cls.create_test_server(wait_until='ACTIVE')
# Create network the interface will be attached to
network_name = data_utils.rand_name(cls.__name__ + '-network')
post_body = {'name': network_name}
post_body['router:external'] = False
post_body['shared'] = True
post_body['port_security_enabled'] = True
cls.network = \
cls.networks_client.create_network(**post_body)['network']
cls.addClassResourceCleanup(
cls.networks_client.delete_network,
cls.network['id'])
# Create subnet for network
cls.cidr = netaddr.IPNetwork(CONF.network.project_network_cidr)
cls.subnet = cls.subnets_client.create_subnet(
network_id=cls.network['id'],
cidr=cls.cidr,
ip_version=4)['subnet']
cls.addClassResourceCleanup(
cls.subnets_client.delete_subnet,
cls.subnet['id'])
# ports on the network need to be deleted before the network can
# be deleted
cls.addClassResourceCleanup(_cleanup_ports, cls.network['id'])
def _delete_and_wait_for_interface_detach(
self, server_id, port_id):
req_id = self.interfaces_client.delete_interface(
server_id, port_id
).response['x-openstack-request-id']
waiters.wait_for_interface_detach(
self.servers_admin_client, server_id, port_id, req_id)
def _delete_and_wait_for_interface_detach_ignore_timeout(
self, server_id, port_id):
try:
self._delete_and_wait_for_interface_detach(
server_id, port_id)
except lib_exc.TimeoutException:
pass
def _attach_interface_to_server(self):
network_id = self.network['id']
interface = self.interfaces_client.create_interface(
self.server['id'], net_id=network_id)['interfaceAttachment']
self.addCleanup(
test_utils.call_and_ignore_notfound_exc,
self._delete_and_wait_for_interface_detach_ignore_timeout,
self.server['id'], interface['port_id'])
waiters.wait_for_interface_status(
self.interfaces_client, self.server['id'],
interface['port_id'], 'ACTIVE')
return interface
@testtools.skipUnless(CONF.compute_feature_enabled.interface_attach,
"Interface attachment is not available.")
@utils.requires_ext(extension='os-attach-interfaces', service='compute')
@decorators.idempotent_id('ddf53cb6-4a0a-4e5a-91e3-6c32aaa3b9b6')
@rbac_rule_validation.action(
service="nova",
rules=[_ATTACH_INTERFACES_LIST])
def test_list_interfaces(self):
"""Test list interfaces, part of os-attach-interfaces."""
with self.override_role():
self.interfaces_client.list_interfaces(self.server['id'])
@decorators.idempotent_id('1b9cf7db-dc50-48a2-8eb9-8c25af5e934a')
@testtools.skipUnless(CONF.compute_feature_enabled.interface_attach,
"Interface attachment is not available.")
@utils.requires_ext(extension='os-attach-interfaces', service='compute')
@rbac_rule_validation.action(
service="nova",
rules=[_ATTACH_INTERFACES_SHOW])
def test_show_interface(self):
"""Test show interfaces, part of os-attach-interfaces."""
interface = self._attach_interface_to_server()
with self.override_role():
self.interfaces_client.show_interface(
self.server['id'], interface['port_id'])
@testtools.skipUnless(CONF.compute_feature_enabled.interface_attach,
"Interface attachment is not available.")
@utils.requires_ext(extension='os-attach-interfaces', service='compute')
@decorators.idempotent_id('d2d3a24d-4738-4bce-a287-36d664746cde')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-attach-interfaces:create"])
def test_create_interface(self):
"""Test create interface, part of os-attach-interfaces."""
network_id = self.network['id']
with self.override_role():
interface = self.interfaces_client.create_interface(
self.server['id'], net_id=network_id)['interfaceAttachment']
self.addCleanup(
test_utils.call_and_ignore_notfound_exc,
self._delete_and_wait_for_interface_detach_ignore_timeout,
self.server['id'], interface['port_id'])
waiters.wait_for_interface_status(
self.interfaces_client, self.server['id'],
interface['port_id'], 'ACTIVE')
@testtools.skipUnless(CONF.compute_feature_enabled.interface_attach,
"Interface attachment is not available.")
@utils.requires_ext(extension='os-attach-interfaces', service='compute')
@decorators.idempotent_id('55b05692-ed44-4608-a84c-cd4219c82799')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-attach-interfaces:delete"])
def test_delete_interface(self):
"""Test delete interface, part of os-attach-interfaces."""
interface = self._attach_interface_to_server()
with self.override_role():
req_id = self.interfaces_client.delete_interface(
self.server['id'], interface['port_id'])
try:
# interface may be not found - we need to ignore that
waiters.wait_for_interface_detach(
self.servers_admin_client, self.server['id'],
interface['port_id'], req_id)
except lib_exc.NotFound:
pass
@decorators.idempotent_id('6886d360-0d86-4760-b1a3-882d81fbebcc')
@utils.requires_ext(extension='os-ips', service='compute')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:ips:index"])
def test_list_addresses(self):
"""Test list server addresses, part of ips policy family."""
with self.override_role():
self.servers_client.list_addresses(self.server['id'])
@decorators.idempotent_id('fa43e7e5-0db9-48eb-9c6b-c11eb766b8e4')
@utils.requires_ext(extension='os-ips', service='compute')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:ips:show"])
def test_list_addresses_by_network(self):
"""Test list server addresses by network, part of ips policy family."""
addresses = self.servers_client.list_addresses(self.server['id'])[
'addresses']
address = next(iter(addresses))
with self.override_role():
self.servers_client.list_addresses_by_network(
self.server['id'], address)
@testtools.skipUnless(CONF.compute_feature_enabled.interface_attach,
"Interface attachment is not available.")
@utils.requires_ext(extension='os-multinic', service='compute')
@rbac_rule_validation.action(
service="nova", rules=[_MULTINIC_ADD])
@decorators.idempotent_id('bd3e2c74-130a-40f0-8085-124d93fe67da')
def test_add_fixed_ip(self):
"""Test add fixed ip to server network, part of os-multinic."""
interfaces = (self.interfaces_client.list_interfaces(self.server['id'])
['interfaceAttachments'])
if interfaces:
network_id = interfaces[0]['net_id']
else:
interface = self.interfaces_client.create_interface(
self.server['id'])['interfaceAttachment']
network_id = interface['net_id']
self.addCleanup(
self._delete_and_wait_for_interface_detach,
self.server['id'], interface['port_id'])
with self.override_role():
self.servers_client.add_fixed_ip(self.server['id'],
networkId=network_id)
# Get the Fixed IP from server.
server_detail = self.servers_client.show_server(
self.server['id'])['server']
fixed_ip = None
for ip_set in server_detail['addresses']:
for ip in server_detail['addresses'][ip_set]:
if ip['OS-EXT-IPS:type'] == 'fixed':
fixed_ip = ip['addr']
break
if fixed_ip is not None:
break
# Remove the fixed IP from server.
# TODO(gmann): separate the remve fixded ip test as it has
# separate policy now.
# self.servers_client.remove_fixed_ip(self.server['id'],
# address=fixed_ip)

View File

@ -1,204 +0,0 @@
# 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 oslo_log import log
from tempest.common import utils
from tempest.common import waiters
from tempest import config
from tempest.lib.common import fixed_network
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.compute import rbac_base as base
CONF = config.CONF
LOG = log.getLogger(__name__)
class ComputeServersRbacTest(base.BaseV2ComputeRbacTest):
@classmethod
def setup_credentials(cls):
cls.set_network_resources(network=True, subnet=True, router=True)
super(ComputeServersRbacTest, cls).setup_credentials()
@classmethod
def setup_clients(cls):
super(ComputeServersRbacTest, cls).setup_clients()
cls.networks_client = cls.os_primary.networks_client
cls.ports_client = cls.os_primary.ports_client
cls.subnets_client = cls.os_primary.subnets_client
@classmethod
def resource_setup(cls):
super(ComputeServersRbacTest, cls).resource_setup()
cls.server = cls.create_test_server(wait_until='ACTIVE')
cls.tenant_network = fixed_network.set_networks_kwarg(
cls.get_tenant_network()
)
def _get_instance_config(self):
instance_params = {
'name': data_utils.rand_name(self.__class__.__name__ + '-Server'),
'flavorRef': CONF.compute.flavor_ref,
'imageRef': CONF.compute.image_ref
}
if 'networks' in self.tenant_network:
instance_params['networks'] = self.tenant_network['networks']
return instance_params
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:servers:create"])
@decorators.idempotent_id('4f34c73a-6ddc-4677-976f-71320fa855bd')
def test_create_server(self):
with self.override_role():
instance_params = self._get_instance_config()
server = self.servers_client.create_server(
**instance_params
)['server']
self.addCleanup(waiters.wait_for_server_termination,
self.servers_client, server['id'])
self.addCleanup(self.servers_client.delete_server, server['id'])
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:servers:create:forced_host"])
@decorators.idempotent_id('0ae3c401-52ab-41bc-ab96-c598a65d9ae5')
def test_create_server_forced_host(self):
# Retrieve 'nova' zone host information from availiability_zone_list
zones = self.availability_zone_client.list_availability_zones(
detail=True)['availabilityZoneInfo']
hosts = [zone['hosts'] for zone in zones if zone['zoneName'] == 'nova']
# We just need any host out of the hosts list to build the
# availability_zone attribute. So, picking the first one is fine.
# The first key of the dictionary specifies the host name.
host = list(hosts[0].keys())[0]
availability_zone = 'nova:' + host
with self.override_role():
instance_params = self._get_instance_config()
instance_params['availability_zone'] = availability_zone
server = self.servers_client.create_server(
**instance_params
)['server']
self.addCleanup(waiters.wait_for_server_termination,
self.servers_client, server['id'])
self.addCleanup(self.servers_client.delete_server, server['id'])
@utils.services('volume')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:servers:create:attach_volume"])
@decorators.idempotent_id('eeddac5e-15aa-454f-838d-db608aae4dd8')
def test_create_server_attach_volume(self):
# To create a bootable volume, the UUID of the image from which
# to create the volume must be included as the imageRef attribute in
# the request body.
volume_id = self.create_volume(
imageRef=CONF.compute.image_ref,
size=CONF.volume.volume_size)['id']
bd_map_v2 = [{'uuid': volume_id,
'source_type': 'volume',
'destination_type': 'volume',
'boot_index': 0,
'delete_on_termination': False}]
with self.override_role():
instance_params = self._get_instance_config()
# Use imageRef='' to avoid using the default image in tempest.conf.
instance_params['imageRef'] = ''
instance_params['block_device_mapping_v2'] = bd_map_v2
server = self.servers_client.create_server(
**instance_params
)['server']
waiters.wait_for_server_status(
self.servers_client, server['id'], 'ACTIVE')
# Delete the server and wait for the volume to become available to
# avoid clean up errors.
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
waiters.wait_for_volume_resource_status,
self.volumes_client, volume_id, 'available')
self.addCleanup(waiters.wait_for_server_termination,
self.servers_client, server['id'])
self.addCleanup(self.delete_server, server['id'])
@utils.services('network')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:servers:create:attach_network"])
@decorators.idempotent_id('b44cd4ff-50a4-42ce-ada3-724e213cd540')
def test_create_server_attach_network(self):
def _create_network_resources():
# Create network
network_name = data_utils.rand_name(
self.__class__.__name__ + '-network')
network = self.networks_client.create_network(
name=network_name, port_security_enabled=True)['network']
self.addCleanup(self.networks_client.delete_network, network['id'])
# Create subnet for the network
subnet_name = data_utils.rand_name(
self.__class__.__name__ + '-subnet')
subnet = self.subnets_client.create_subnet(
name=subnet_name,
network_id=network['id'],
cidr=CONF.network.project_network_cidr,
ip_version=4)['subnet']
self.addCleanup(self.subnets_client.delete_subnet, subnet['id'])
return network
network = _create_network_resources()
network_id = {'uuid': network['id']}
with self.override_role():
instance_params = self._get_instance_config()
instance_params['networks'] = [network_id]
server = self.servers_client.create_server(
**instance_params
)['server']
self.addCleanup(waiters.wait_for_server_termination,
self.servers_client, server['id'])
self.addCleanup(self.servers_client.delete_server, server['id'])
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:servers:delete"])
@decorators.idempotent_id('062e3440-e873-4b41-9317-bf6d8be50c12')
def test_delete_server(self):
server = self.create_test_server(wait_until='ACTIVE')
with self.override_role():
self.servers_client.delete_server(server['id'])
waiters.wait_for_server_termination(
self.servers_client, server['id'])
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:servers:update"])
@decorators.idempotent_id('077b17cb-5621-43b9-8adf-5725f0d7a863')
def test_update_server(self):
new_name = data_utils.rand_name(self.__class__.__name__ + '-server')
with self.override_role():
self.servers_client.update_server(self.server['id'],
name=new_name)
waiters.wait_for_server_status(self.servers_client,
self.server['id'], 'ACTIVE')

View File

@ -1,103 +0,0 @@
# 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.common import utils
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
from patrole_tempest_plugin import rbac_rule_validation
from patrole_tempest_plugin.tests.api.compute import rbac_base
class ServerTagsRbacTest(rbac_base.BaseV2ComputeRbacTest):
min_microversion = '2.26'
max_microversion = 'latest'
@classmethod
def skip_checks(cls):
super(ServerTagsRbacTest, cls).skip_checks()
if not utils.is_extension_enabled('os-server-tags', 'compute'):
msg = "os-server-tags extension is not enabled."
raise cls.skipException(msg)
@classmethod
def setup_credentials(cls):
cls.set_network_resources(network=True, subnet=True, router=True)
super(ServerTagsRbacTest, cls).setup_credentials()
@classmethod
def resource_setup(cls):
super(ServerTagsRbacTest, cls).resource_setup()
cls.server = cls.create_test_server(wait_until='ACTIVE')
def _add_tag_to_server(self):
tag_name = data_utils.rand_name(self.__class__.__name__ + '-tag')
self.servers_client.update_tag(self.server['id'], tag_name)
self.addCleanup(self.servers_client.delete_all_tags, self.server['id'])
return tag_name
@decorators.idempotent_id('99e73dd3-adec-4044-b46c-84bdded35d09')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-server-tags:index"])
def test_list_tags(self):
with self.override_role():
self.servers_client.list_tags(self.server['id'])
@decorators.idempotent_id('9297c99e-94eb-429f-93cf-9b1838e33622')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-server-tags:show"])
def test_check_tag_existence(self):
tag_name = self._add_tag_to_server()
with self.override_role():
self.servers_client.check_tag_existence(self.server['id'],
tag_name)
@decorators.idempotent_id('0d84ee94-d3ca-4635-8edf-b7f67ab8e4a3')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-server-tags:update"])
def test_update_tag(self):
with self.override_role():
self._add_tag_to_server()
@decorators.idempotent_id('115c2694-00aa-41ee-99f6-9eab4040c182')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-server-tags:delete"])
def test_delete_tag(self):
tag_name = self._add_tag_to_server()
with self.override_role():
self.servers_client.delete_tag(self.server['id'], tag_name)
@decorators.idempotent_id('a8e19b87-6580-4bc8-9933-e62561ff667d')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-server-tags:update_all"])
def test_update_all_tags(self):
new_tag_name = data_utils.rand_name(self.__class__.__name__ + '-tag')
with self.override_role():
self.servers_client.update_all_tags(self.server['id'],
[new_tag_name])
@decorators.idempotent_id('89d51936-e333-42f9-a045-132a4865ba1a')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-server-tags:delete_all"])
def test_delete_all_tags(self):
with self.override_role():
self.servers_client.delete_all_tags(self.server['id'])

View File

@ -1,221 +0,0 @@
# 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.
import time
from oslo_log import log as logging
import testtools
from tempest.common import waiters
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
LOG = logging.getLogger(__name__)
# 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
def setup_credentials(cls):
cls.set_network_resources(network=True, subnet=True, router=True)
super(ServerVolumeAttachmentRbacTest, cls).setup_credentials()
@classmethod
def setup_clients(cls):
super(ServerVolumeAttachmentRbacTest, cls).setup_clients()
cls.volumes_client = cls.os_primary.volumes_client_latest
@classmethod
def skip_checks(cls):
super(ServerVolumeAttachmentRbacTest, cls).skip_checks()
if not CONF.service_available.cinder:
skip_msg = ("%s skipped as Cinder is not available" % cls.__name__)
raise cls.skipException(skip_msg)
@classmethod
def resource_setup(cls):
super(ServerVolumeAttachmentRbacTest, cls).resource_setup()
cls.server = cls.create_test_server(wait_until='ACTIVE')
cls.volume = cls.create_volume()
def _detach_volume_and_wait_until_available(self, server, volume):
self.servers_client.detach_volume(server['id'],
volume['id'])
waiters.wait_for_volume_resource_status(self.volumes_client,
volume['id'], 'available')
def _recreate_volume(self):
try:
# In case detachment failed, update the DB status of the volume
# to avoid error getting thrown when deleting the volume.
self.volumes_client.reset_volume_status(
self.volume['id'], status='available',
attach_status='detached')
waiters.wait_for_volume_resource_status(
self.volumes_client, self.volume['id'], 'available')
# Next, forcibly delete the volume.
self.volumes_client.force_delete_volume(self.volume['id'])
self.volumes_client.wait_for_resource_deletion(self.volume['id'])
except lib_exc.TimeoutException:
LOG.exception('Failed to delete volume %s', self.volume['id'])
# Finally, re-create the volume.
self.__class__.volume = self.create_volume()
def _restore_volume_status(self):
# Forcibly detach any attachments still attached to the volume.
try:
attachments = self.volumes_client.show_volume(
self.volume['id'])['volume']['attachments']
if attachments:
# Tests below only ever create one attachment for the volume.
attachment = attachments[0]
self.volumes_client.force_detach_volume(
self.volume['id'], connector=None,
attachment_id=attachment['id'])
waiters.wait_for_volume_resource_status(self.volumes_client,
self.volume['id'],
'available')
except lib_exc.TimeoutException:
# If all else fails, rebuild the volume.
self._recreate_volume()
def setUp(self):
super(ServerVolumeAttachmentRbacTest, self).setUp()
self._restore_volume_status()
def wait_for_server_volume_swap(self, server_id, old_volume_id,
new_volume_id):
"""Waits for a server to swap the old volume to a new one."""
volume_attachments = self.servers_client.list_volume_attachments(
server_id)['volumeAttachments']
attached_volume_ids = [attachment['volumeId']
for attachment in volume_attachments]
start = int(time.time())
while (old_volume_id in attached_volume_ids) \
or (new_volume_id not in attached_volume_ids):
time.sleep(self.servers_client.build_interval)
volume_attachments = self.servers_client.list_volume_attachments(
server_id)['volumeAttachments']
attached_volume_ids = [attachment['volumeId']
for attachment in volume_attachments]
if int(time.time()) - start >= self.servers_client.build_timeout:
old_vol_bdm_status = 'in BDM' \
if old_volume_id in attached_volume_ids else 'not in BDM'
new_vol_bdm_status = 'in BDM' \
if new_volume_id in attached_volume_ids else 'not in BDM'
message = ('Failed to swap old volume %(old_volume_id)s '
'(current %(old_vol_bdm_status)s) to new volume '
'%(new_volume_id)s (current %(new_vol_bdm_status)s)'
' on server %(server_id)s within the required time '
'(%(timeout)s s)' %
{'old_volume_id': old_volume_id,
'old_vol_bdm_status': old_vol_bdm_status,
'new_volume_id': new_volume_id,
'new_vol_bdm_status': new_vol_bdm_status,
'server_id': server_id,
'timeout': self.servers_client.build_timeout})
raise lib_exc.TimeoutException(message)
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-volumes-attachments:index"])
@decorators.idempotent_id('529b668b-6edb-41d5-8886-d7dbd0614678')
def test_list_volume_attachments(self):
with self.override_role():
self.servers_client.list_volume_attachments(self.server['id'])
@decorators.attr(type='slow')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-volumes-attachments:create"])
@decorators.idempotent_id('21c2c3fd-fbe8-41b1-8ef8-115ec47d54c1')
def test_create_volume_attachment(self):
with self.override_role():
self.servers_client.attach_volume(self.server['id'],
volumeId=self.volume['id'])
# On teardown detach the volume and wait for it to be available. This
# is so we don't error out when trying to delete the volume during
# teardown.
self.addCleanup(waiters.wait_for_volume_resource_status,
self.volumes_client, self.volume['id'], 'available')
# Ignore 404s on detach in case the server is deleted or the volume
# is already detached.
self.addCleanup(self._detach_volume, self.server, self.volume)
waiters.wait_for_volume_resource_status(self.volumes_client,
self.volume['id'], 'in-use')
@decorators.attr(type='slow')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-volumes-attachments:show"])
@decorators.idempotent_id('997df9c2-6e54-47b6-ab74-e4fdb500f385')
def test_show_volume_attachment(self):
attachment = self.attach_volume(self.server, self.volume)
with self.override_role():
self.servers_client.show_volume_attachment(
self.server['id'], attachment['id'])
@decorators.skip_because(bug='2008051', bug_type='storyboard')
@decorators.attr(type='slow')
@testtools.skipUnless(CONF.compute_feature_enabled.swap_volume,
'In-place swapping of volumes not supported.')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-volumes-attachments:update"])
@decorators.idempotent_id('bd667186-eca6-4b78-ab6a-3e2fabcb971f')
def test_update_volume_attachment(self):
volume1 = self.volume
volume2 = self.create_volume()
# Attach "volume1" to server
self.attach_volume(self.server, volume1)
with self.override_role():
# Swap volume from "volume1" to "volume2"
self.servers_client.update_attached_volume(
self.server['id'], volume1['id'], volumeId=volume2['id'])
self.addCleanup(self._detach_volume_and_wait_until_available,
self.server, volume2)
waiters.wait_for_volume_resource_status(self.volumes_client,
volume1['id'], 'available')
waiters.wait_for_volume_resource_status(self.volumes_client,
volume2['id'], 'in-use')
self.wait_for_server_volume_swap(self.server['id'], volume1['id'],
volume2['id'])
@decorators.attr(type='slow')
@rbac_rule_validation.action(
service="nova",
rules=["os_compute_api:os-volumes-attachments:delete"])
@decorators.idempotent_id('12b03e90-d087-46af-9c4d-507d021c4984')
def test_delete_volume_attachment(self):
self.attach_volume(self.server, self.volume)
with self.override_role():
self.servers_client.detach_volume(self.server['id'],
self.volume['id'])
waiters.wait_for_volume_resource_status(self.volumes_client,
self.volume['id'], 'available')

View File

@ -1,46 +0,0 @@
# 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.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.compute import rbac_base
CONF = config.CONF
if CONF.policy_feature_enabled.changed_nova_policies_ussuri:
_OS_COMPUTE_API_OS_SERVICES = "os_compute_api:os-services:list"
else:
_OS_COMPUTE_API_OS_SERVICES = "os_compute_api:os-services"
class ServicesRbacTest(rbac_base.BaseV2ComputeRbacTest):
@classmethod
def skip_checks(cls):
super(ServicesRbacTest, cls).skip_checks()
if not utils.is_extension_enabled('os-services', 'compute'):
raise cls.skipException(
'%s skipped as os-services not enabled' % cls.__name__)
@rbac_rule_validation.action(
service="nova",
rules=[_OS_COMPUTE_API_OS_SERVICES])
@decorators.idempotent_id('7472261b-9c6d-453a-bcb3-aecaa29ad281')
def test_list_services(self):
with self.override_role():
self.services_client.list_services()['services']

View File

@ -1,65 +0,0 @@
# 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.common import utils
from tempest.lib import decorators
from patrole_tempest_plugin import rbac_rule_validation
from patrole_tempest_plugin.tests.api.compute import rbac_base
CONF = config.CONF
if CONF.policy_feature_enabled.changed_nova_policies_victoria:
_TENANT_NET_LIST = "os_compute_api:os-tenant-networks:list"
else:
_TENANT_NET_LIST = "os_compute_api:os-tenant-networks"
class TenantNetworksRbacTest(rbac_base.BaseV2ComputeRbacTest):
# Tests will fail with a 404 starting from microversion 2.36.
# See the following link for details:
# https://docs.openstack.org/api-ref/compute/#project-networks-os-tenant-networks-deprecated
max_microversion = '2.35'
@classmethod
def setup_clients(cls):
super(TenantNetworksRbacTest, cls).setup_clients()
cls.tenant_networks_client = cls.os_primary.tenant_networks_client
@classmethod
def skip_checks(cls):
super(TenantNetworksRbacTest, cls).skip_checks()
if not utils.is_extension_enabled('os-tenant-networks', 'compute'):
msg = "os-tenant-networks extension not enabled."
raise cls.skipException(msg)
if not CONF.service_available.neutron:
raise cls.skipException(
'%s skipped as Neutron is required' % cls.__name__)
@classmethod
def setup_credentials(cls):
cls.set_network_resources(network=True)
super(TenantNetworksRbacTest, cls).setup_credentials()
@decorators.idempotent_id('42b39ba1-14aa-4799-9518-34367d0da67a')
@rbac_rule_validation.action(
service="nova",
rules=[_TENANT_NET_LIST])
def test_list_show_tenant_networks(self):
with self.override_role():
self.tenant_networks_client.list_tenant_networks()

View File

@ -1,167 +0,0 @@
# 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.common import waiters
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.compute import rbac_base
from tempest import config
CONF = config.CONF
if CONF.policy_feature_enabled.changed_nova_policies_victoria:
_VOLUME_LIST = "os_compute_api:os-volumes:list"
_VOLUME_CREATE = "os_compute_api:os-volumes:create"
_VOLUME_SHOW = "os_compute_api:os-volumes:show"
_VOLUME_DELETE = "os_compute_api:os-volumes:delete"
_SNAPSHOT_LIST = "os_compute_api:os-volumes:snapshots:list"
_SNAPSHOT_CREATE = "os_compute_api:os-volumes:snapshots:create"
_SNAPSHOT_SHOW = "os_compute_api:os-volumes:snapshots:show"
_SNAPSHOT_DELETE = "os_compute_api:os-volumes:snapshots:delete"
else:
_VOLUME_LIST = "os_compute_api:os-volumes"
_VOLUME_CREATE = "os_compute_api:os-volumes"
_VOLUME_SHOW = "os_compute_api:os-volumes"
_VOLUME_DELETE = "os_compute_api:os-volumes"
_SNAPSHOT_LIST = "os_compute_api:os-volumes"
_SNAPSHOT_CREATE = "os_compute_api:os-volumes"
_SNAPSHOT_SHOW = "os_compute_api:os-volumes"
_SNAPSHOT_DELETE = "os_compute_api:os-volumes"
class VolumeRbacTest(rbac_base.BaseV2ComputeRbacTest):
"""RBAC tests for the Nova Volume client."""
# These tests will fail with a 404 starting from microversion 2.36.
# For more information, see:
# https://docs.openstack.org/api-ref/compute/#volume-extension-os-volumes-os-snapshots-deprecated
max_microversion = '2.35'
@classmethod
def skip_checks(cls):
super(VolumeRbacTest, cls).skip_checks()
if not CONF.service_available.cinder:
skip_msg = ("%s skipped as Cinder is not available" % cls.__name__)
raise cls.skipException(skip_msg)
if not CONF.volume_feature_enabled.snapshot:
skip_msg = ("Cinder volume snapshots are disabled")
raise cls.skipException(skip_msg)
@classmethod
def resource_setup(cls):
super(VolumeRbacTest, cls).resource_setup()
cls.volume = cls.create_volume()
def _delete_snapshot(self, snapshot_id):
waiters.wait_for_volume_resource_status(
self.snapshots_extensions_client, snapshot_id,
'available')
self.snapshots_extensions_client.delete_snapshot(snapshot_id)
self.snapshots_extensions_client.wait_for_resource_deletion(
snapshot_id)
@decorators.idempotent_id('2402013e-a624-43e3-9518-44a5d1dbb32d')
@rbac_rule_validation.action(
service="nova",
rules=[_VOLUME_CREATE])
def test_create_volume(self):
with self.override_role():
volume = self.volumes_extensions_client.create_volume(
size=CONF.volume.volume_size)['volume']
waiters.wait_for_volume_resource_status(self.volumes_client,
volume['id'], 'available')
# Use non-deprecated volumes_client for deletion.
self.addCleanup(self.volumes_client.delete_volume, volume['id'])
@decorators.idempotent_id('69b3888c-dff2-47b0-9fa4-0672619c9054')
@rbac_rule_validation.action(
service="nova",
rules=[_VOLUME_LIST])
def test_list_volumes(self):
with self.override_role():
self.volumes_extensions_client.list_volumes()
@decorators.idempotent_id('4ba0a820-040f-488b-86bb-be2e920ea12c')
@rbac_rule_validation.action(
service="nova",
rules=[_VOLUME_SHOW])
def test_show_volume(self):
with self.override_role():
self.volumes_extensions_client.show_volume(self.volume['id'])
@decorators.idempotent_id('6e7870f2-1bb2-4b58-96f8-6782071ef327')
@rbac_rule_validation.action(
service="nova",
rules=[_VOLUME_DELETE])
def test_delete_volume(self):
volume = self.create_volume()
with self.override_role():
self.volumes_extensions_client.delete_volume(volume['id'])
@decorators.idempotent_id('0c3eaa4f-69d6-4a13-9dda-19585f36b1c1')
@rbac_rule_validation.action(
service="nova",
rules=[_SNAPSHOT_CREATE])
def test_create_snapshot(self):
s_name = data_utils.rand_name(self.__class__.__name__ + '-Snapshot')
with self.override_role():
snapshot = self.snapshots_extensions_client.create_snapshot(
volume_id=self.volume['id'], display_name=s_name)['snapshot']
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self._delete_snapshot, snapshot['id'])
@decorators.idempotent_id('e944e816-416c-11e7-a919-92ebcb67fe33')
@rbac_rule_validation.action(
service="nova",
rules=[_SNAPSHOT_LIST])
def test_list_snapshots(self):
with self.override_role():
self.snapshots_extensions_client.list_snapshots()
@decorators.idempotent_id('19c2e6bd-585b-472f-a8d7-71ea9299c655')
@rbac_rule_validation.action(
service="nova",
rules=[_SNAPSHOT_SHOW])
def test_show_snapshot(self):
s_name = data_utils.rand_name(self.__class__.__name__ + '-Snapshot')
snapshot = self.snapshots_extensions_client.create_snapshot(
volume_id=self.volume['id'], display_name=s_name)['snapshot']
self.addCleanup(self._delete_snapshot, snapshot['id'])
with self.override_role():
self.snapshots_extensions_client.show_snapshot(snapshot['id'])
@decorators.idempotent_id('f4f5635c-416c-11e7-a919-92ebcb67fe33')
@rbac_rule_validation.action(
service="nova",
rules=[_SNAPSHOT_DELETE])
def test_delete_snapshot(self):
s_name = data_utils.rand_name(self.__class__.__name__ + '-Snapshot')
snapshot = self.snapshots_extensions_client.create_snapshot(
volume_id=self.volume['id'], display_name=s_name)['snapshot']
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self._delete_snapshot, snapshot['id'])
waiters.wait_for_volume_resource_status(
self.snapshots_extensions_client, snapshot['id'],
'available')
with self.override_role():
self.snapshots_extensions_client.delete_snapshot(snapshot['id'])
self.snapshots_extensions_client.wait_for_resource_deletion(
snapshot['id'])

View File

@ -1,280 +0,0 @@
# 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 oslo_log import log as logging
from tempest.api.identity import base
from tempest.lib.common.utils import data_utils
from tempest.lib.common.utils import test_utils
from patrole_tempest_plugin import rbac_utils
LOG = logging.getLogger(__name__)
class BaseIdentityRbacTest(rbac_utils.RbacUtilsMixin,
base.BaseIdentityTest):
@classmethod
def setup_test_role(cls):
"""Set up a test role."""
name = data_utils.rand_name(cls.__name__ + '-test_role')
role = cls.roles_client.create_role(name=name)['role']
cls.addClassResourceCleanup(
test_utils.call_and_ignore_notfound_exc,
cls.roles_client.delete_role, role['id'])
return role
@classmethod
def setup_test_service(cls):
"""Setup a test service."""
name = data_utils.rand_name(cls.__name__ + '-service')
serv_type = data_utils.rand_name('type')
desc = data_utils.rand_name(cls.__name__ + '-description')
service = cls.services_client.create_service(
name=name,
type=serv_type,
description=desc)['service']
cls.addClassResourceCleanup(
test_utils.call_and_ignore_notfound_exc,
cls.services_client.delete_service, service['id'])
return service
@classmethod
def setup_test_user(cls, password=None, **kwargs):
"""Set up a test user."""
username = data_utils.rand_name(cls.__name__ + '-test_user')
email = username + '@testmail.tm'
user = cls.users_client.create_user(
name=username,
email=email,
password=password,
**kwargs)['user']
cls.addClassResourceCleanup(
test_utils.call_and_ignore_notfound_exc,
cls.users_client.delete_user, user['id'])
return user
class BaseIdentityV3RbacTest(BaseIdentityRbacTest):
identity_version = 'v3'
@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
cls.domain_config_client = cls.os_primary.domain_config_client
cls.endpoints_client = cls.os_primary.endpoints_v3_client
cls.endpoint_filter_client = cls.os_primary.endpoint_filter_client
cls.endpoint_groups_client = cls.os_primary.endpoint_groups_client
cls.groups_client = cls.os_primary.groups_client
cls.identity_client = cls.os_primary.identity_v3_client
cls.oauth_token_client = cls.os_primary.oauth_token_client
cls.projects_client = cls.os_primary.projects_client
cls.project_tags_client = cls.os_primary.project_tags_client
cls.policies_client = cls.os_primary.policies_client
cls.regions_client = cls.os_primary.regions_client
cls.role_assignments_client = cls.os_primary.role_assignments_client
cls.roles_client = cls.os_primary.roles_v3_client
cls.services_client = cls.os_primary.identity_services_v3_client
cls.token_client = cls.os_primary.token_v3_client
cls.trusts_client = cls.os_primary.trusts_client
cls.users_client = cls.os_primary.users_v3_client
@classmethod
def resource_setup(cls):
super(BaseIdentityV3RbacTest, cls).resource_setup()
cls.credentials = []
cls.domains = []
cls.groups = []
cls.policies = []
cls.projects = []
cls.regions = []
cls.trusts = []
cls.tokens = []
@classmethod
def resource_cleanup(cls):
for credential in cls.credentials:
test_utils.call_and_ignore_notfound_exc(
cls.creds_client.delete_credential, credential['id'])
# Delete each domain at the end of the test, but each domain must be
# disabled first.
for domain in cls.domains:
test_utils.call_and_ignore_notfound_exc(
cls.domains_client.update_domain, domain['id'], enabled=False)
test_utils.call_and_ignore_notfound_exc(
cls.domains_client.delete_domain, domain['id'])
for group in cls.groups:
test_utils.call_and_ignore_notfound_exc(
cls.groups_client.delete_group, group['id'])
for policy in cls.policies:
test_utils.call_and_ignore_notfound_exc(
cls.policies_client.delete_policy, policy['id'])
for project in cls.projects:
test_utils.call_and_ignore_notfound_exc(
cls.projects_client.delete_project, project['id'])
for region in cls.regions:
test_utils.call_and_ignore_notfound_exc(
cls.regions_client.delete_region, region['id'])
for trust in cls.trusts:
test_utils.call_and_ignore_notfound_exc(
cls.trusts_client.delete_trust, trust['id'])
for token in cls.tokens:
test_utils.call_and_ignore_notfound_exc(
cls.identity_client.delete_token, token)
super(BaseIdentityV3RbacTest, cls).resource_cleanup()
@classmethod
def setup_test_endpoint(cls, service=None):
"""Creates a service and an endpoint for test."""
interface = 'public'
url = data_utils.rand_url()
region_name = data_utils.rand_name(
cls.__name__ + '-region')
# Endpoint creation requires a service
if service is None:
service = cls.setup_test_service()
params = {
'service_id': service['id'],
'region': region_name,
'interface': interface,
'url': url
}
endpoint = cls.endpoints_client.create_endpoint(**params)['endpoint']
cls.addClassResourceCleanup(
test_utils.call_and_ignore_notfound_exc,
cls.regions_client.delete_region, endpoint['region'])
cls.addClassResourceCleanup(
test_utils.call_and_ignore_notfound_exc,
cls.endpoints_client.delete_endpoint, endpoint['id'])
return endpoint
@classmethod
def setup_test_credential(cls, user=None):
"""Creates a credential for test."""
keys = [data_utils.rand_uuid_hex(),
data_utils.rand_uuid_hex()]
blob = '{"access": "%s", "secret": "%s"}' % (keys[0], keys[1])
credential = cls.creds_client.create_credential(
user_id=user['id'],
project_id=user['project_id'],
blob=blob,
type='ec2')['credential']
cls.credentials.append(credential)
return credential
@classmethod
def setup_test_domain(cls):
"""Set up a test domain."""
domain = cls.domains_client.create_domain(
name=data_utils.rand_name(cls.__name__),
description=data_utils.rand_name(
cls.__name__ + '-desc'))['domain']
cls.domains.append(domain)
return domain
@classmethod
def setup_test_group(cls):
"""Creates a group for test."""
name = data_utils.rand_name(cls.__name__ + '-test_group')
group = cls.groups_client.create_group(name=name)['group']
cls.groups.append(group)
return group
@classmethod
def setup_test_policy(cls):
"""Creates a policy for test."""
blob = data_utils.rand_name(cls.__name__ + '-test_blob')
policy_type = data_utils.rand_name(
cls.__name__ + '-policy_type')
policy = cls.policies_client.create_policy(
blob=blob,
policy=policy_type,
type="application/json")['policy']
cls.policies.append(policy)
return policy
@classmethod
def setup_test_project(cls):
"""Set up a test project."""
project = cls.projects_client.create_project(
name=data_utils.rand_name(
cls.__name__),
description=data_utils.rand_name(
cls.__name__ + '-desc'))['project']
cls.projects.append(project)
return project
@classmethod
def setup_test_region(cls):
"""Creates a region for test."""
description = data_utils.rand_name(
cls.__name__ + '-test_region_desc')
id = data_utils.rand_name(cls.__name__)
region = cls.regions_client.create_region(
id=id,
description=description)['region']
cls.regions.append(region)
return region
@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_token(cls, user_id, password):
"""Set up a test token."""
token = cls.token_client.auth(user_id=user_id,
password=password).response
token_id = token['x-subject-token']
cls.tokens.append(token_id)
return token_id

View File

@ -1,89 +0,0 @@
# 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",
rules=["identity:create_application_credential"])
def test_create_application_credential(self):
with self.override_role():
self._create_application_credential()
@decorators.idempotent_id('58b3c3a0-5ad0-44f7-8da7-0736f71f7168')
@rbac_rule_validation.action(
service="keystone",
rules=["identity:list_application_credentials"])
def test_list_application_credentials(self):
with self.override_role():
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",
rules=["identity:get_application_credential"])
def test_show_application_credential(self):
app_cred = self._create_application_credential()
with self.override_role():
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",
rules=["identity:delete_application_credential"])
def test_delete_application_credential(self):
app_cred = self._create_application_credential()
with self.override_role():
self.application_credentials_client.delete_application_credential(
user_id=self.user_id, application_credential_id=app_cred['id'])

View File

@ -1,45 +0,0 @@
# 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.lib import decorators
from patrole_tempest_plugin import rbac_rule_validation
from patrole_tempest_plugin.tests.api.identity import rbac_base
class IdentityAuthV3RbacTest(rbac_base.BaseIdentityV3RbacTest):
"""Tests the APIs that enforce the auth policy actions.
For more information about the auth policy actions, see:
https://git.openstack.org/cgit/openstack/keystone/tree/keystone/common/policies/auth.py
"""
# TODO(felipemonteiro): Add tests for identity:get_auth_catalog
# once the endpoints are implemented in Tempest's
# identity v3 client.
@decorators.idempotent_id('2a9fbf7f-6feb-4161-ae4b-faf7d6421b1a')
@rbac_rule_validation.action(service="keystone",
rules=["identity:get_auth_projects"])
def test_list_auth_projects(self):
with self.override_role():
self.identity_client.list_auth_projects()
@decorators.idempotent_id('6a40af0d-7265-4657-b6b2-87a2828e263e')
@rbac_rule_validation.action(service="keystone",
rules=["identity:get_auth_domains"])
def test_list_auth_domain(self):
with self.override_role():
self.identity_client.list_auth_domains()

View File

@ -1,96 +0,0 @@
# 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.
import testtools
from tempest import config
from tempest.lib.common.utils import data_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
@testtools.skipIf(
CONF.policy_feature_enabled.removed_keystone_policies_stein,
"This policy is unavailable in Stein so cannot be tested.")
class IdentityCredentialsV3RbacTest(rbac_base.BaseIdentityV3RbacTest):
def _create_user_project_and_credential(self):
project = self.setup_test_project()
user = self.setup_test_user(project_id=project['id'])
credential = self.setup_test_credential(user=user)
return credential
@rbac_rule_validation.action(service="keystone",
rules=["identity:create_credential"])
@decorators.idempotent_id('c1ab6d34-c59f-4ae1-bae9-bb3c1089b48e')
def test_create_credential(self):
project = self.setup_test_project()
user = self.setup_test_user(project_id=project['id'])
with self.override_role():
self.setup_test_credential(user=user)
@testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source,
'Skipped because environment has an immutable user '
'source and solely provides read-only access to users.')
@rbac_rule_validation.action(service="keystone",
rules=["identity:update_credential"])
@decorators.idempotent_id('cfb05ce3-bffb-496e-a3c2-9515d730da63')
def test_update_credential(self):
credential = self._create_user_project_and_credential()
new_keys = [data_utils.rand_uuid_hex(),
data_utils.rand_uuid_hex()]
with self.override_role():
self.creds_client.update_credential(
credential['id'],
credential=credential,
access_key=new_keys[0],
secret_key=new_keys[1],
project_id=credential['project_id'])
@testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source,
'Skipped because environment has an immutable user '
'source and solely provides read-only access to users.')
@rbac_rule_validation.action(service="keystone",
rules=["identity:delete_credential"])
@decorators.idempotent_id('87ab42af-8d41-401b-90df-21e72919fcde')
def test_delete_credential(self):
credential = self._create_user_project_and_credential()
with self.override_role():
self.creds_client.delete_credential(credential['id'])
@testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source,
'Skipped because environment has an immutable user '
'source and solely provides read-only access to users.')
@rbac_rule_validation.action(service="keystone",
rules=["identity:get_credential"])
@decorators.idempotent_id('1b6eeae6-f1e8-4cdf-8903-1c002b1fc271')
def test_show_credential(self):
credential = self._create_user_project_and_credential()
with self.override_role():
self.creds_client.show_credential(credential['id'])
@rbac_rule_validation.action(service="keystone",
rules=["identity:list_credentials"])
@decorators.idempotent_id('3de303e2-12a7-4811-805a-f18906472038')
def test_list_credentials(self):
with self.override_role():
self.creds_client.list_credentials()

View File

@ -1,164 +0,0 @@
# 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.identity import rbac_base
CONF = config.CONF
class DomainConfigurationV3RbacTest(rbac_base.BaseIdentityV3RbacTest):
"""RBAC tests for domain configuration client.
Provides coverage for the following policy actions:
https://git.openstack.org/cgit/openstack/keystone/tree/keystone/common/policies/domain_config.py
"""
identity = {"driver": "ldap"}
ldap = {"url": "ldap://myldap.com:389/",
"user_tree_dn": "ou=Users,dc=my_new_root,dc=org"}
@classmethod
def resource_setup(cls):
super(DomainConfigurationV3RbacTest, cls).resource_setup()
cls.domain_id = cls.setup_test_domain()['id']
def setUp(self):
super(DomainConfigurationV3RbacTest, self).setUp()
self._create_domain_config(self.domain_id)
def _create_domain_config(self, domain_id):
domain_config = self.domain_config_client.create_domain_config(
domain_id, identity=self.identity, ldap=self.ldap)['config']
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.domain_config_client.delete_domain_config,
domain_id)
return domain_config
@rbac_rule_validation.action(service="keystone",
rules=["identity:create_domain_config"])
@decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd115')
def test_create_domain_config(self):
with self.override_role():
self._create_domain_config(self.domain_id)
@rbac_rule_validation.action(service="keystone",
rules=["identity:get_domain_config"])
@decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd118')
def test_show_domain_config(self):
with self.override_role():
self.domain_config_client.show_domain_config(self.domain_id)
@decorators.idempotent_id('1b539f95-4991-4e09-960f-fa771e1007d7')
@rbac_rule_validation.action(service="keystone",
rules=["identity:get_domain_config"])
def test_show_domain_group_config(self):
with self.override_role():
self.domain_config_client.show_domain_group_config(
self.domain_id, 'identity')
@decorators.idempotent_id('590c774d-a294-44f8-866e-aac9f4ab3809')
@rbac_rule_validation.action(service="keystone",
rules=["identity:get_domain_config"])
def test_show_domain_group_option_config(self):
with self.override_role():
self.domain_config_client.show_domain_group_option_config(
self.domain_id, 'identity', 'driver')
@decorators.idempotent_id('21053885-1ce3-4167-b5e3-e470253481da')
@rbac_rule_validation.action(
service="keystone",
rules=["identity:get_security_compliance_domain_config"])
def test_show_security_compliance_domain_config(self):
# The "security_compliance" group can only be shown for the default
# domain.
with self.override_role():
self.domain_config_client.show_domain_group_config(
CONF.identity.default_domain_id, 'security_compliance')
@decorators.idempotent_id('d1addd10-9ae4-4360-9961-47324fd22f23')
@rbac_rule_validation.action(service="keystone",
rules=["identity:get_domain_config_default"])
def test_show_default_config_settings(self):
with self.override_role():
self.domain_config_client.show_default_config_settings()
@decorators.idempotent_id('63183377-251f-4622-81f0-6b58a8a285c9')
@rbac_rule_validation.action(service="keystone",
rules=["identity:get_domain_config_default"])
def test_show_default_group_config(self):
with self.override_role():
self.domain_config_client.show_default_group_config('identity')
@decorators.idempotent_id('6440e9c1-e8da-474d-9118-89996fffe5f8')
@rbac_rule_validation.action(service="keystone",
rules=["identity:get_domain_config_default"])
def test_show_default_group_option(self):
with self.override_role():
self.domain_config_client.show_default_group_option('identity',
'driver')
@rbac_rule_validation.action(service="keystone",
rules=["identity:update_domain_config"])
@decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd116')
def test_update_domain_config(self):
updated_config = {'ldap': {'url': data_utils.rand_url()}}
with self.override_role():
self.domain_config_client.update_domain_config(
self.domain_id, **updated_config)
@decorators.idempotent_id('6e32bf96-dbe9-4ac8-b814-0e79fa948285')
@rbac_rule_validation.action(service="keystone",
rules=["identity:update_domain_config"])
def test_update_domain_group_config(self):
with self.override_role():
self.domain_config_client.update_domain_group_config(
self.domain_id, 'identity', identity=self.identity)
@decorators.idempotent_id('d2c510da-a077-4c67-9522-27745ef2812b')
@rbac_rule_validation.action(service="keystone",
rules=["identity:update_domain_config"])
def test_update_domain_group_option_config(self):
with self.override_role():
self.domain_config_client.update_domain_group_option_config(
self.domain_id, 'identity', 'driver', driver='ldap')
@rbac_rule_validation.action(service="keystone",
rules=["identity:delete_domain_config"])
@decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd117')
def test_delete_domain_config(self):
with self.override_role():
self.domain_config_client.delete_domain_config(self.domain_id)
@decorators.idempotent_id('f479694b-df02-4d5a-88b6-c8b52f9341eb')
@rbac_rule_validation.action(service="keystone",
rules=["identity:delete_domain_config"])
def test_delete_domain_group_config(self):
with self.override_role():
self.domain_config_client.delete_domain_group_config(
self.domain_id, 'identity')
@decorators.idempotent_id('f594bde3-31c9-414f-922d-0ddafdc0ca40')
@rbac_rule_validation.action(service="keystone",
rules=["identity:delete_domain_config"])
def test_delete_domain_group_option_config(self):
with self.override_role():
self.domain_config_client.delete_domain_group_option_config(
self.domain_id, 'identity', 'driver')

View File

@ -1,69 +0,0 @@
# 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.lib.common.utils import data_utils
from tempest.lib import decorators
from patrole_tempest_plugin import rbac_rule_validation
from patrole_tempest_plugin.tests.api.identity import rbac_base
class IdentityDomainsV3RbacTest(rbac_base.BaseIdentityV3RbacTest):
@rbac_rule_validation.action(service="keystone",
rules=["identity:create_domain"])
@decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd110')
def test_create_domain(self):
with self.override_role():
self.setup_test_domain()
@rbac_rule_validation.action(service="keystone",
rules=["identity:update_domain"])
@decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd111')
def test_update_domain(self):
domain = self.setup_test_domain()
new_domain_name = data_utils.rand_name(
self.__class__.__name__)
with self.override_role():
self.domains_client.update_domain(domain['id'],
domain=domain,
name=new_domain_name)
@rbac_rule_validation.action(service="keystone",
rules=["identity:delete_domain"])
@decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd112')
def test_delete_domain(self):
domain = self.setup_test_domain()
# A domain must be deactivated to be deleted
self.domains_client.update_domain(domain['id'],
domain=domain,
enabled=False)
with self.override_role():
self.domains_client.delete_domain(domain['id'])
@rbac_rule_validation.action(service="keystone",
rules=["identity:get_domain"])
@decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd113')
def test_show_domain(self):
domain = self.setup_test_domain()
with self.override_role():
self.domains_client.show_domain(domain['id'])
@rbac_rule_validation.action(service="keystone",
rules=["identity:list_domains"])
@decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd114')
def test_list_domains(self):
with self.override_role():
self.domains_client.list_domains()

View File

@ -1,68 +0,0 @@
# 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.lib.common.utils import data_utils
from tempest.lib import decorators
from patrole_tempest_plugin import rbac_rule_validation
from patrole_tempest_plugin.tests.api.identity import rbac_base
class IdentityEndpointsV3RbacTest(rbac_base.BaseIdentityV3RbacTest):
@rbac_rule_validation.action(service="keystone",
rules=["identity:create_endpoint"])
@decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd127')
def test_create_endpoint(self):
service = self.setup_test_service()
with self.override_role():
self.setup_test_endpoint(service=service)
@rbac_rule_validation.action(service="keystone",
rules=["identity:update_endpoint"])
@decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd128')
def test_update_endpoint(self):
endpoint = self.setup_test_endpoint()
new_url = data_utils.rand_url()
with self.override_role():
self.endpoints_client.update_endpoint(
endpoint["id"],
url=new_url)
@rbac_rule_validation.action(service="keystone",
rules=["identity:delete_endpoint"])
@decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd129')
def test_delete_endpoint(self):
endpoint = self.setup_test_endpoint()
with self.override_role():
self.endpoints_client.delete_endpoint(endpoint['id'])
@rbac_rule_validation.action(service="keystone",
rules=["identity:get_endpoint"])
@decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd130')
def test_show_endpoint(self):
endpoint = self.setup_test_endpoint()
with self.override_role():
self.endpoints_client.show_endpoint(endpoint['id'])
@rbac_rule_validation.action(service="keystone",
rules=["identity:list_endpoints"])
@decorators.idempotent_id('6bdaecd4-0843-4ed6-ab64-3a57ab0cd131')
def test_list_endpoints(self):
with self.override_role():
self.endpoints_client.list_endpoints()

View File

@ -1,109 +0,0 @@
# 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.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
class EndpointFilterGroupsV3RbacTest(rbac_base.BaseIdentityV3RbacTest):
interface = 'public'
@classmethod
def resource_setup(cls):
super(EndpointFilterGroupsV3RbacTest, cls).resource_setup()
cls.service_id = cls.setup_test_service()['id']
def setUp(self):
super(EndpointFilterGroupsV3RbacTest, self).setUp()
self.endpoint_group_id = self._create_endpoint_group()
def _create_endpoint_group(self, ignore_not_found=False):
# Create an endpoint group
ep_group_name = data_utils.rand_name(
self.__class__.__name__ + '-EPFilterGroup')
filters = {
'filters': {
'interface': self.interface,
'service_id': self.service_id
}
}
endpoint_group = self.endpoint_groups_client.create_endpoint_group(
name=ep_group_name, **filters)['endpoint_group']
if ignore_not_found:
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.endpoint_groups_client.delete_endpoint_group,
endpoint_group['id'])
else:
self.addCleanup(self.endpoint_groups_client.delete_endpoint_group,
endpoint_group['id'])
return endpoint_group['id']
@rbac_rule_validation.action(service="keystone",
rules=["identity:create_endpoint_group"])
@decorators.idempotent_id('b4765906-52ec-477b-b441-a8508ced68e3')
def test_create_endpoint_group(self):
with self.override_role():
self._create_endpoint_group(ignore_not_found=True)
@rbac_rule_validation.action(service="keystone",
rules=["identity:list_endpoint_groups"])
@decorators.idempotent_id('089aa3a7-ba1f-4f70-a1cf-f298a845058a')
def test_list_endpoint_groups(self):
with self.override_role():
self.endpoint_groups_client.list_endpoint_groups()
@decorators.idempotent_id('5c16368d-1485-4c28-9803-db3fa3510623')
@rbac_rule_validation.action(service="keystone",
rules=["identity:get_endpoint_group"])
def test_check_endpoint_group(self):
with self.override_role():
self.endpoint_groups_client.check_endpoint_group(
self.endpoint_group_id)
@rbac_rule_validation.action(service="keystone",
rules=["identity:get_endpoint_group"])
@decorators.idempotent_id('bd2b6fb8-661f-4255-84b2-50fea4a1dc61')
def test_show_endpoint_group(self):
with self.override_role():
self.endpoint_groups_client.show_endpoint_group(
self.endpoint_group_id)
@rbac_rule_validation.action(service="keystone",
rules=["identity:update_endpoint_group"])
@decorators.idempotent_id('028b9198-ec35-4bd5-8f72-e23dfb7a0c8e')
def test_update_endpoint_group(self):
updated_name = data_utils.rand_name(
self.__class__.__name__ + '-EPFilterGroup')
with self.override_role():
self.endpoint_groups_client.update_endpoint_group(
self.endpoint_group_id, name=updated_name)
@rbac_rule_validation.action(service="keystone",
rules=["identity:delete_endpoint_group"])
@decorators.idempotent_id('88cc105e-70d9-48ac-927e-200ef41e070c')
def test_delete_endpoint_group(self):
endpoint_group_id = self._create_endpoint_group(ignore_not_found=True)
with self.override_role():
self.endpoint_groups_client.delete_endpoint_group(
endpoint_group_id)

View File

@ -1,90 +0,0 @@
# 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.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
class EndpointFilterProjectsV3RbacTest(rbac_base.BaseIdentityV3RbacTest):
@classmethod
def resource_setup(cls):
super(EndpointFilterProjectsV3RbacTest, cls).resource_setup()
cls.project = cls.setup_test_project()
cls.endpoint = cls.setup_test_endpoint()
def _add_endpoint_to_project(self, ignore_not_found=False):
self.endpoint_filter_client.add_endpoint_to_project(
self.project['id'], self.endpoint['id'])
if ignore_not_found:
self.addCleanup(
test_utils.call_and_ignore_notfound_exc,
self.endpoint_filter_client.delete_endpoint_from_project,
self.project['id'], self.endpoint['id'])
else:
self.addCleanup(
self.endpoint_filter_client.delete_endpoint_from_project,
self.project['id'], self.endpoint['id'])
@rbac_rule_validation.action(
service="keystone",
rules=["identity:add_endpoint_to_project"])
@decorators.idempotent_id('9199ec13-816d-4efe-b8b1-e1cd026b9747')
def test_add_endpoint_to_project(self):
# Adding endpoints to projects
with self.override_role():
self._add_endpoint_to_project(ignore_not_found=True)
@rbac_rule_validation.action(
service="keystone",
rules=["identity:list_projects_for_endpoint"])
@decorators.idempotent_id('f53dca42-ec8a-48e9-924b-0bbe6c99727f')
def test_list_projects_for_endpoint(self):
with self.override_role():
self.endpoint_filter_client.list_projects_for_endpoint(
self.endpoint['id'])
@rbac_rule_validation.action(
service="keystone",
rules=["identity:check_endpoint_in_project"])
@decorators.idempotent_id('0c1425eb-833c-4aa1-a21d-52ffa41fdc6a')
def test_check_endpoint_in_project(self):
self._add_endpoint_to_project()
with self.override_role():
self.endpoint_filter_client.check_endpoint_in_project(
self.project['id'], self.endpoint['id'])
@rbac_rule_validation.action(
service="keystone",
rules=["identity:list_endpoints_for_project"])
@decorators.idempotent_id('5d86c659-c6ad-41e0-854e-3823e95c7cc2')
def test_list_endpoints_in_project(self):
with self.override_role():
self.endpoint_filter_client.list_endpoints_in_project(
self.project['id'])
@rbac_rule_validation.action(
service="keystone",
rules=["identity:remove_endpoint_from_project"])
@decorators.idempotent_id('b4e21c10-4f47-427b-9b8a-f5b5601adfda')
def test_remove_endpoint_from_project(self):
self._add_endpoint_to_project(ignore_not_found=True)
with self.override_role():
self.endpoint_filter_client.delete_endpoint_from_project(
self.project['id'], self.endpoint['id'])

View File

@ -1,121 +0,0 @@
# 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.
import testtools
from tempest import config
from tempest.lib.common.utils import data_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 IdentityGroupsV3RbacTest(rbac_base.BaseIdentityV3RbacTest):
def _create_user_and_add_to_new_group(self):
"""Creates a user and adds to a group for test."""
group = self.setup_test_group()
user = self.setup_test_user()
self.groups_client.add_group_user(group['id'], user['id'])
return (group['id'], user['id'])
@rbac_rule_validation.action(service="keystone",
rules=["identity:create_group"])
@decorators.idempotent_id('88377f51-9074-4d64-a22f-f8931d048c9a')
def test_create_group(self):
with self.override_role():
self.setup_test_group()
@rbac_rule_validation.action(service="keystone",
rules=["identity:update_group"])
@decorators.idempotent_id('790fb7be-a657-4a64-9b83-c43425cf180b')
def test_update_group(self):
group = self.setup_test_group()
new_group_name = data_utils.rand_name(
self.__class__.__name__ + '-group')
with self.override_role():
self.groups_client.update_group(group['id'], name=new_group_name)
@rbac_rule_validation.action(service="keystone",
rules=["identity:delete_group"])
@decorators.idempotent_id('646b52da-2a5f-486a-afb0-51fdc86a6c12')
def test_delete_group(self):
group = self.setup_test_group()
with self.override_role():
self.groups_client.delete_group(group['id'])
@rbac_rule_validation.action(service="keystone",
rules=["identity:get_group"])
@decorators.idempotent_id('d530f0ad-42b9-429b-ad05-e53ac95a040e')
def test_show_group(self):
group = self.setup_test_group()
with self.override_role():
self.groups_client.show_group(group['id'])
@rbac_rule_validation.action(service="keystone",
rules=["identity:list_groups"])
@decorators.idempotent_id('c4d0f76b-735f-4fd0-868b-0006bc420ff4')
def test_list_groups(self):
with self.override_role():
self.groups_client.list_groups()
@rbac_rule_validation.action(service="keystone",
rules=["identity:add_user_to_group"])
@decorators.idempotent_id('fdd49b74-3ed3-4736-9f0e-9027a32017ac')
def test_add_user_group(self):
group = self.setup_test_group()
user = self.setup_test_user()
with self.override_role():
self.groups_client.add_group_user(group['id'], user['id'])
@testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source,
'Skipped because environment has an immutable user '
'source and solely provides read-only access to users.')
@rbac_rule_validation.action(service="keystone",
rules=["identity:remove_user_from_group"])
@decorators.idempotent_id('8a60d11c-7d2b-47e5-a0f3-9ea900ca66fe')
def test_remove_user_group(self):
group_id, user_id = self._create_user_and_add_to_new_group()
with self.override_role():
self.groups_client.delete_group_user(group_id, user_id)
@rbac_rule_validation.action(service="keystone",
rules=["identity:list_users_in_group"])
@decorators.idempotent_id('b3e394a7-079e-4a0d-a4ff-9b266293d1ee')
def test_list_user_group(self):
group = self.setup_test_group()
with self.override_role():
self.groups_client.list_group_users(group['id'])
@testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source,
'Skipped because environment has an immutable user '
'source and solely provides read-only access to users.')
@rbac_rule_validation.action(service="keystone",
rules=["identity:check_user_in_group"])
@decorators.idempotent_id('d3603241-fd87-4a2d-94f9-f32469d1aaba')
def test_check_user_group(self):
group_id, user_id = self._create_user_and_add_to_new_group()
with self.override_role():
self.groups_client.check_group_user_existence(group_id, user_id)

View File

@ -1,78 +0,0 @@
# 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.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
class IdentityConsumersV3RbacTest(rbac_base.BaseIdentityV3RbacTest):
def _create_consumer(self):
description = data_utils.rand_name(
self.__class__.__name__)
consumer = self.consumers_client.create_consumer(
description)['consumer']
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.consumers_client.delete_consumer,
consumer['id'])
return consumer
@rbac_rule_validation.action(service="keystone",
rules=["identity:create_consumer"])
@decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d970')
def test_create_consumer(self):
with self.override_role():
self._create_consumer()
@rbac_rule_validation.action(service="keystone",
rules=["identity:delete_consumer"])
@decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d971')
def test_delete_consumer(self):
consumer = self._create_consumer()
with self.override_role():
self.consumers_client.delete_consumer(consumer['id'])
@rbac_rule_validation.action(service="keystone",
rules=["identity:update_consumer"])
@decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d972')
def test_update_consumer(self):
consumer = self._create_consumer()
updated_description = data_utils.rand_name(
self.__class__.__name__)
with self.override_role():
self.consumers_client.update_consumer(consumer['id'],
updated_description)
@rbac_rule_validation.action(service="keystone",
rules=["identity:get_consumer"])
@decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d973')
def test_show_consumer(self):
consumer = self._create_consumer()
with self.override_role():
self.consumers_client.show_consumer(consumer['id'])
@rbac_rule_validation.action(service="keystone",
rules=["identity:list_consumers"])
@decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d975')
def test_list_consumers(self):
with self.override_role():
self.consumers_client.list_consumers()

View File

@ -1,138 +0,0 @@
# 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.lib.common.utils import data_utils
from tempest.lib.common.utils import test_utils
from tempest import config
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 IdentityOAuthTokensV3RbacTest(rbac_base.BaseIdentityV3RbacTest):
@classmethod
def resource_setup(cls):
super(IdentityOAuthTokensV3RbacTest, cls).resource_setup()
# Authorize token on admin role since primary user has admin
# credentials before switching roles. Populate role_ids with admin
# role id.
cls.role_ids = [cls.get_role_by_name(CONF.identity.admin_role)['id']]
cls.project_id = cls.os_primary.credentials.project_id
cls.user_id = cls.os_primary.credentials.user_id
def _create_consumer(self):
description = data_utils.rand_name(
self.__class__.__name__ + '-Consumer')
consumer = self.consumers_client.create_consumer(
description)['consumer']
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.consumers_client.delete_consumer,
consumer['id'])
return consumer
def _create_consumer_and_request_token(self):
# Create consumer
consumer = self._create_consumer()
# Create request token
request_token = self.oauth_token_client.create_request_token(
consumer['id'], consumer['secret'], self.project_id)
return consumer, request_token
def _create_access_token(self):
consumer, request_token = self._create_consumer_and_request_token()
# Authorize request token
resp = self.oauth_token_client.authorize_request_token(
request_token['oauth_token'], self.role_ids)['token']
auth_verifier = resp['oauth_verifier']
# Create access token
body = self.oauth_token_client.create_access_token(
consumer['id'],
consumer['secret'],
request_token['oauth_token'],
request_token['oauth_token_secret'],
auth_verifier)
access_key = body['oauth_token']
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.oauth_token_client.revoke_access_token,
self.user_id, access_key)
return access_key
@rbac_rule_validation.action(service="keystone",
rules=["identity:authorize_request_token"])
@decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d976')
def test_authorize_request_token(self):
_, request_token = self._create_consumer_and_request_token()
with self.override_role():
self.oauth_token_client.authorize_request_token(
request_token['oauth_token'],
self.role_ids)
@rbac_rule_validation.action(service="keystone",
rules=["identity:get_access_token"])
@decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d977')
def test_get_access_token(self):
access_token = self._create_access_token()
with self.override_role():
self.oauth_token_client.get_access_token(self.user_id,
access_token)
@rbac_rule_validation.action(service="keystone",
rules=["identity:get_access_token_role"])
@decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d980')
def test_get_access_token_role(self):
access_token = self._create_access_token()
with self.override_role():
self.oauth_token_client.get_access_token_role(
self.user_id, access_token, self.role_ids[0])
@rbac_rule_validation.action(service="keystone",
rules=["identity:list_access_tokens"])
@decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d979')
def test_list_access_tokens(self):
with self.override_role():
self.oauth_token_client.list_access_tokens(self.user_id)
@rbac_rule_validation.action(service="keystone",
rules=["identity:list_access_token_roles"])
@decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d978')
def test_list_access_token_roles(self):
access_token = self._create_access_token()
with self.override_role():
self.oauth_token_client.list_access_token_roles(
self.user_id, access_token)
@rbac_rule_validation.action(service="keystone",
rules=["identity:delete_access_token"])
@decorators.idempotent_id('0f148510-63bf-11e6-4522-080044d0d981')
def test_revoke_access_token(self):
access_token = self._create_access_token()
with self.override_role():
self.oauth_token_client.revoke_access_token(
self.user_id, access_token)

View File

@ -1,67 +0,0 @@
# 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.lib.common.utils import data_utils
from tempest.lib import decorators
from patrole_tempest_plugin import rbac_rule_validation
from patrole_tempest_plugin.tests.api.identity import rbac_base
class IdentityPoliciesV3RbacTest(rbac_base.BaseIdentityV3RbacTest):
@rbac_rule_validation.action(service="keystone",
rules=["identity:create_policy"])
@decorators.idempotent_id('de2f7ecb-fbf0-41f3-abf4-b97b5e082fd5')
def test_create_policy(self):
with self.override_role():
self.setup_test_policy()
@rbac_rule_validation.action(service="keystone",
rules=["identity:update_policy"])
@decorators.idempotent_id('9cfed3c6-0b27-4d15-be67-e06e0cfb01b9')
def test_update_policy(self):
policy = self.setup_test_policy()
updated_policy_type = data_utils.rand_name(
self.__class__.__name__ + '-policy_type')
with self.override_role():
self.policies_client.update_policy(policy['id'],
type=updated_policy_type)
@rbac_rule_validation.action(service="keystone",
rules=["identity:delete_policy"])
@decorators.idempotent_id('dcd93f75-1e1b-4fbe-bee0-9c4c7b201735')
def test_delete_policy(self):
policy = self.setup_test_policy()
with self.override_role():
self.policies_client.delete_policy(policy['id'])
@rbac_rule_validation.action(service="keystone",
rules=["identity:get_policy"])
@decorators.idempotent_id('d7e415c2-945a-4504-9571-0e2d0dd8594b')
def test_show_policy(self):
policy = self.setup_test_policy()
with self.override_role():
self.policies_client.show_policy(policy['id'])
@rbac_rule_validation.action(service="keystone",
rules=["identity:list_policies"])
@decorators.idempotent_id('35a56161-4054-4237-8a78-7ce805dce202')
def test_list_policies(self):
with self.override_role():
self.policies_client.list_policies()

Some files were not shown because too many files have changed in this diff Show More