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:
parent
aa7bc0dd9a
commit
b540700061
.coveragerc.gitignore.mailmap.stestr.conf.zuul.yamlCONTRIBUTING.rstHACKING.rstLICENSEREADME.rstREVIEWING.rstbabel.cfg
devstack
doc
requirements.txt
source
etc
lower-constraints.txtpatrole_tempest_plugin
__init__.pyconfig.py
hacking
plugin.pypolicy_authority.pyrbac_authority.pyrbac_exceptions.pyrbac_rule_validation.pyrbac_utils.pyrequirements_authority.pytests
__init__.py
api
README.rst__init__.py
compute
__init__.pyrbac_base.pytest_agents_rbac.pytest_aggregates_rbac.pytest_availability_zone_rbac.pytest_flavor_access_rbac.pytest_flavor_extra_specs_rbac.pytest_flavor_manage_rbac.pytest_flavor_rxtx_rbac.pytest_floating_ip_pools_rbac.pytest_floating_ips_rbac.pytest_hosts_rbac.pytest_hypervisor_rbac.pytest_images_rbac.pytest_instance_usages_audit_log_rbac.pytest_keypairs_rbac.pytest_limits_rbac.pytest_migrations_rbac.pytest_quota_class_sets_rbac.pytest_quota_sets_rbac.pytest_security_groups_rbac.pytest_server_actions_rbac.pytest_server_consoles_rbac.pytest_server_groups_rbac.pytest_server_metadata_rbac.pytest_server_migrations_rbac.pytest_server_misc_policy_actions_rbac.pytest_server_rbac.pytest_server_tags_rbac.pytest_server_volume_attachments_rbac.pytest_services_rbac.pytest_tenant_networks_rbac.pytest_volume_rbac.py
identity
__init__.pyrbac_base.py
v3
__init__.pytest_application_credentials_rbac.pytest_auth_rbac.pytest_credentials_rbac.pytest_domain_configuration_rbac.pytest_domains_rbac.pytest_endpoints_rbac.pytest_ep_filter_groups_rbac.pytest_ep_filter_projects_rbac.pytest_groups_rbac.pytest_oauth_consumers_rbac.pytest_oauth_tokens_rbac.pytest_policies_rbac.py
@ -1,6 +0,0 @@
|
||||
[run]
|
||||
branch = True
|
||||
source = patrole
|
||||
|
||||
[report]
|
||||
ignore_errors = True
|
65
.gitignore
vendored
65
.gitignore
vendored
@ -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
|
5
.mailmap
5
.mailmap
@ -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>
|
@ -1,3 +0,0 @@
|
||||
[DEFAULT]
|
||||
test_path=./patrole_tempest_plugin/tests/unit
|
||||
group_regex=([^\.]*\.)*
|
246
.zuul.yaml
246
.zuul.yaml
@ -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
|
@ -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
|
170
HACKING.rst
170
HACKING.rst
@ -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
176
LICENSE
@ -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.
|
||||
|
257
README.rst
257
README.rst
@ -1,251 +1,10 @@
|
||||
Patrole - RBAC Integration Tempest Plugin
|
||||
=========================================
|
||||
This project is no longer maintained.
|
||||
|
||||
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.
|
||||
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".
|
||||
|
||||
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>`_.
|
||||
For any further questions, please email
|
||||
openstack-discuss@lists.openstack.org or join #openstack-dev on
|
||||
OFTC.
|
||||
|
189
REVIEWING.rst
189
REVIEWING.rst
@ -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.
|
@ -1,2 +0,0 @@
|
||||
[python: **.py]
|
||||
|
@ -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
|
@ -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
|
@ -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}
|
@ -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
|
@ -1,5 +0,0 @@
|
||||
====================
|
||||
Patrole Coding Guide
|
||||
====================
|
||||
|
||||
.. include:: ../../HACKING.rst
|
@ -1,5 +0,0 @@
|
||||
======================
|
||||
Reviewing Patrole Code
|
||||
======================
|
||||
|
||||
.. include:: ../../REVIEWING.rst
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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.
|
@ -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`.
|
@ -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>`
|
@ -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>`
|
@ -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
|
||||
|
@ -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>`
|
@ -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>`
|
@ -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.
|
@ -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
|
@ -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
|
@ -1 +0,0 @@
|
||||
../../README.rst
|
@ -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
|
@ -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.
|
@ -1,3 +0,0 @@
|
||||
[DEFAULT]
|
||||
output_file = etc/patrole.conf.sample
|
||||
namespace = patrole.config
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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)
|
||||
]
|
@ -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)
|
@ -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.
|
||||
"""
|
@ -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"
|
@ -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)
|
@ -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
|
@ -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
|
@ -1 +0,0 @@
|
||||
../../../doc/source/field_guide/rbac.rst
|
@ -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
|
@ -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'])
|
@ -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})
|
@ -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)
|
@ -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)
|
@ -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'])
|
@ -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)
|
@ -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')
|
@ -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()
|
@ -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'])
|
@ -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'])
|
@ -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'])
|
@ -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)
|
@ -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"))))
|
@ -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()
|
@ -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()
|
@ -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()
|
@ -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)
|
@ -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)
|
@ -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)
|
@ -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')
|
@ -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")
|
@ -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'])
|
@ -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')
|
@ -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")
|
@ -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)
|
@ -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')
|
@ -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'])
|
@ -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')
|
@ -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']
|
@ -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()
|
@ -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'])
|
@ -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
|
@ -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'])
|
@ -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()
|
@ -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()
|
@ -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')
|
@ -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()
|
@ -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()
|
@ -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)
|
@ -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'])
|
@ -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)
|
@ -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()
|
@ -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)
|
@ -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
Loading…
x
Reference in New Issue
Block a user