Project Scoping Node endpoint
* Adds additional policies: * baremetal:node_get:last_error * baremetal:node:get:reservation * baremetal:node:get:driver_internal_info * baremetal:node:get:driver_info * baremetal:node:update:driver_info * baremetal:node:update:properties * baremetal:node:update:chassis_uuid * baremetal:node:update:instance_uuid * baremetal:node:update:lessee * baremetal:node:update:driver_interfaces * baremetal:node:update:network_data * baremetal:node:update:conductor_group * baremetal:node:update:name * With new policies, responses of filtering and posted data is performed. Testing has been added to the RBAC testing files to align with this and the defaults where pertinant. * Adds another variation of the common policy check method which may be useful in the long term. This is too soon to tell, but the overall purpose is to allow similar logic patterns to the authorize behavior. This is because the standard policies are, at present, also used to control behavior of response, and node response sanitization needs to be carefully navigated. This change excludes linked resources such as /nodes/<uuid>/ports, portgroups, volumes/[targets|connectors]. Those will be in later changes, as the node itself is quite a bit. Special note: * The indicator endpoint code in the API appears to be broken and given that should be fixed in a separate patch. Change-Id: I2869bf21f761cfc543798cf1f7d97c5500cd3681
This commit is contained in:
parent
5857fa802d
commit
f1641468bb
@ -54,6 +54,7 @@ Advanced Topics
|
||||
Deploying without BMC Credentials <agent-power>
|
||||
Layer 3 or DHCP-less Ramdisk Booting <dhcp-less>
|
||||
Tuning Ironic <tuning>
|
||||
Role Based Access Control <secure-rbac>
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
119
doc/source/admin/secure-rbac.rst
Normal file
119
doc/source/admin/secure-rbac.rst
Normal file
@ -0,0 +1,119 @@
|
||||
===========
|
||||
Secure RBAC
|
||||
===========
|
||||
|
||||
System Scoped
|
||||
=============
|
||||
|
||||
.. todo: Need to be filled out in an earlier patch most likely.
|
||||
|
||||
Project Scoped
|
||||
==============
|
||||
|
||||
Project scoped authentication is when a request token and associated records
|
||||
indicate an associated ``project_id`` value.
|
||||
|
||||
Legacy Behavior
|
||||
---------------
|
||||
|
||||
The legacy behavior of API service is that all requests are treated as
|
||||
project scoped requests where access is governed using an "admin project".
|
||||
This behavior is *deprecated*. The new behavior is a delineation of
|
||||
access through ``system`` scoped and ``project`` scoped requests.
|
||||
|
||||
In essence, what would have served as an "admin project", is now ``system``
|
||||
scoped usage.
|
||||
|
||||
Previously, Ironic API, by default, responded with access denied or permitted
|
||||
based upon the admin project and associated role. These responses would
|
||||
generate an HTTP 403 if the project was incorrect or if a user role.
|
||||
|
||||
.. NOTE:: While Ironic has had the concept of an ``owner`` and a
|
||||
``lessee``, they are *NOT* used by default. They require
|
||||
custom policy configuration files to be used in the legacy
|
||||
operating mode.
|
||||
|
||||
Supported Endpoints
|
||||
-------------------
|
||||
|
||||
* /nodes
|
||||
|
||||
How Project Scoped Works
|
||||
------------------------
|
||||
|
||||
Ironic has two project use models where access is generally more delagative
|
||||
to an ``owner`` where access to a ``lessee`` is generally more utilitarian.
|
||||
|
||||
The purpose of an owner, is more to enable the System Operator to delegate
|
||||
much of the administrative activity of a Node to the owner.
|
||||
This may be because they physically own the hardware, or they are in charge
|
||||
of the node. Regardless of the use model that the fields and mechanics
|
||||
support, these fields are to support humans, and possibly services where
|
||||
applicable.
|
||||
|
||||
.. todo: Add link to owner spec.
|
||||
|
||||
The purpose of a lessee is more for a *tenant* in their *project* to
|
||||
be able to have access to perform basic actions with the API. In some cases
|
||||
that may be to reprovision or rebuild a node. Ultimately that is the lessee's
|
||||
progative, but by default there are actions and field updates that cannot
|
||||
be performed by default. This is also governed by access level within
|
||||
a project.
|
||||
|
||||
.. todo: Add link to lessee spec.
|
||||
|
||||
|
||||
These policies are applied in the way data is viewed and how data can be
|
||||
updated. Generally, an inability to view a node is an access permission issue
|
||||
in term of the project ID being correct for owner/lessee.
|
||||
|
||||
The ironic project has attempted to generally codify what we believe is
|
||||
reasonable, however operators may wish to override these policy settings.
|
||||
For details general policy setting details, please see
|
||||
:doc:`/configuration/policy`.
|
||||
|
||||
|
||||
Field value visibility restrictions
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Ironic's API, by default has a concept of filtering node values to prevent
|
||||
sensitive data from being leaked. System scoped users are subjected to basic
|
||||
restrictions, where as project scoped users are, by default, examined further
|
||||
and against additional policies. This threshold is controlled with the
|
||||
``baremetal:node:get:filter_threshold``.
|
||||
|
||||
By default, the following fields are masked on Nodes and are controlled by the
|
||||
associated policies. By default, owner's are able to see insight into the
|
||||
infrastucture, where as lessee users *CANNOT* view these fields by default.
|
||||
|
||||
* ``last_error`` - ``baremetal:node:get:last_error``
|
||||
* ``reservation`` - ``baremetal:node:get:reservation``
|
||||
* ``driver_internal_info`` - ``baremetal:node:get:driver_internal_info``
|
||||
* ``driver_info`` - ``baremetal:node:get:driver_info``
|
||||
|
||||
Field update restrictions
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Some of the fields in this list are restricted to System scoped users,
|
||||
or even only System Administrators. Some of these default restrictions
|
||||
are likely obvious. Owners can't change the owner. Lessee's can't
|
||||
change the owner.
|
||||
|
||||
* ``driver_info`` - ``baremetal:node:update:driver_info``
|
||||
* ``properties`` - ``baremetal:node:update:properties``
|
||||
* ``chassis_uuid`` - ``baremetal:node:update:chassis_uuid``
|
||||
* ``instance_uuid`` - ``baremetal:node:update:instance_uuid``
|
||||
* ``lessee`` - ``baremetal:node:update:lessee``
|
||||
* ``owner`` - ``baremetal:node:update:owner``
|
||||
* ``driver`` - ``baremetal:node:update:driver_interfaces``
|
||||
* ``*_interface`` - ``baremetal:node:update:driver_interfaces``
|
||||
* ``network_data`` - ``baremetal:node:update:network_data``
|
||||
* ``conductor_group`` - ``baremetal:node:update:conductor_group``
|
||||
* ``name`` - ``baremetal:node:update:name``
|
||||
* ``retired`` - ``baremetal:node:update:driver_info``
|
||||
* ``retired_reason`` - ``baremetal:node:update:retired``
|
||||
|
||||
.. WARNING:: The ``chassis_uuid`` field is a write-once-only field. As such
|
||||
it is restricted to system scoped administrators.
|
||||
|
||||
More information is available on these fields in :doc:`/configuration/policy`.
|
@ -23,6 +23,13 @@ OpenStack deployment.
|
||||
REST API: user roles and policy settings
|
||||
========================================
|
||||
|
||||
.. WARNING::
|
||||
This information is presently in flux as of the Wallaby release with the
|
||||
implementation of ``Secure RBAC`` where ``system`` and ``project``
|
||||
scoped requests are able to be parsed and default access controls support
|
||||
a delineation of roles and responsibilities through the roles.
|
||||
Please see :doc:`/admin/secure-rbac`.
|
||||
|
||||
Beginning with the Newton (6.1.0) release, the Bare Metal service allows
|
||||
operators significant control over API access:
|
||||
|
||||
|
@ -56,9 +56,9 @@ class NodeBiosController(rest.RestController):
|
||||
@method.expose()
|
||||
def get_all(self):
|
||||
"""List node bios settings."""
|
||||
api_utils.check_policy('baremetal:node:bios:get')
|
||||
node = api_utils.check_node_policy_and_retrieve(
|
||||
'baremetal:node:bios:get', self.node_ident)
|
||||
|
||||
node = api_utils.get_rpc_node(self.node_ident)
|
||||
settings = objects.BIOSSettingList.get_by_node_id(
|
||||
api.request.context, node.id)
|
||||
return collection_from_list(self.node_ident, settings)
|
||||
@ -71,9 +71,9 @@ class NodeBiosController(rest.RestController):
|
||||
|
||||
:param setting_name: Logical name of the setting to retrieve.
|
||||
"""
|
||||
api_utils.check_policy('baremetal:node:bios:get')
|
||||
node = api_utils.check_node_policy_and_retrieve(
|
||||
'baremetal:node:bios:get', self.node_ident)
|
||||
|
||||
node = api_utils.get_rpc_node(self.node_ident)
|
||||
try:
|
||||
setting = objects.BIOSSetting.get(api.request.context, node.id,
|
||||
setting_name)
|
||||
|
@ -47,6 +47,7 @@ from ironic.common import policy
|
||||
from ironic.common import states as ir_states
|
||||
from ironic.conductor import steps as conductor_steps
|
||||
import ironic.conf
|
||||
from ironic.drivers import base as driver_base
|
||||
from ironic import objects
|
||||
|
||||
|
||||
@ -513,9 +514,9 @@ class IndicatorController(rest.RestController):
|
||||
mod:`ironic.common.indicator_states`.
|
||||
|
||||
"""
|
||||
api_utils.check_policy('baremetal:node:set_indicator_state')
|
||||
|
||||
rpc_node = api_utils.get_rpc_node(node_ident)
|
||||
rpc_node = api_utils.check_node_policy_and_retrieve(
|
||||
'baremetal:node:set_indicator_state',
|
||||
node_ident)
|
||||
topic = pecan.request.rpcapi.get_topic_for(rpc_node)
|
||||
indicator_at_component = IndicatorAtComponent(unique_name=indicator)
|
||||
pecan.request.rpcapi.set_indicator_state(
|
||||
@ -535,9 +536,9 @@ class IndicatorController(rest.RestController):
|
||||
:returns: a dict with the "state" key and one of
|
||||
mod:`ironic.common.indicator_states` as a value.
|
||||
"""
|
||||
api_utils.check_policy('baremetal:node:get_indicator_state')
|
||||
|
||||
rpc_node = api_utils.get_rpc_node(node_ident)
|
||||
rpc_node = api_utils.check_node_policy_and_retrieve(
|
||||
'baremetal:node:get_indicator_state',
|
||||
node_ident)
|
||||
topic = pecan.request.rpcapi.get_topic_for(rpc_node)
|
||||
indicator_at_component = IndicatorAtComponent(unique_name=indicator)
|
||||
state = pecan.request.rpcapi.get_indicator_state(
|
||||
@ -558,9 +559,9 @@ class IndicatorController(rest.RestController):
|
||||
(from `get_supported_indicators`) as values.
|
||||
|
||||
"""
|
||||
api_utils.check_policy('baremetal:node:get_indicator_state')
|
||||
|
||||
rpc_node = api_utils.get_rpc_node(node_ident)
|
||||
rpc_node = api_utils.check_node_policy_and_retrieve(
|
||||
'baremetal:node:get_indicator_state',
|
||||
node_ident)
|
||||
topic = pecan.request.rpcapi.get_topic_for(rpc_node)
|
||||
indicators = pecan.request.rpcapi.get_supported_indicators(
|
||||
pecan.request.context, rpc_node.uuid, topic=topic)
|
||||
@ -1324,13 +1325,64 @@ def node_sanitize(node, fields):
|
||||
:type fields: list of str
|
||||
"""
|
||||
cdict = api.request.context.to_policy_values()
|
||||
target_dict = dict(cdict)
|
||||
owner = node.get('owner')
|
||||
lessee = node.get('lessee')
|
||||
|
||||
if owner:
|
||||
target_dict['node.owner'] = owner
|
||||
if lessee:
|
||||
target_dict['node.lessee'] = lessee
|
||||
|
||||
# NOTE(tenbrae): the 'show_password' policy setting name exists for
|
||||
# legacy purposes and can not be changed. Changing it will
|
||||
# cause upgrade problems for any operators who have
|
||||
# customized the value of this field
|
||||
show_driver_secrets = policy.check("show_password", cdict, cdict)
|
||||
# NOTE(TheJulia): These methods use policy.check and normally return
|
||||
# False in a noauth or password auth based situation, because the
|
||||
# effective caller doesn't match the policy check rule.
|
||||
show_driver_secrets = policy.check("show_password", cdict, target_dict)
|
||||
show_instance_secrets = policy.check("show_instance_secrets",
|
||||
cdict, cdict)
|
||||
cdict, target_dict)
|
||||
# TODO(TheJulia): The above checks need to be migrated in some direction,
|
||||
# but until we have auditing clarity, it might not be a big deal.
|
||||
|
||||
# Determine if we need to do the additional checks. Keep in mind
|
||||
# nova integrated with ironic is API read heavy, so it is ideal
|
||||
# to keep the policy checks for say system-member based roles to
|
||||
# a minimum as they are likely the regular API users as well.
|
||||
# Also, the default for the filter_threshold is system-member.
|
||||
evaluate_additional_policies = not policy.check_policy(
|
||||
"baremetal:node:get:filter_threshold",
|
||||
target_dict, cdict)
|
||||
if evaluate_additional_policies:
|
||||
# NOTE(TheJulia): The net effect of this is that by default,
|
||||
# at least matching common/policy.py defaults. is these should
|
||||
# be stripped out.
|
||||
if not policy.check("baremetal:node:get:last_error",
|
||||
target_dict, cdict):
|
||||
# Guard the last error from being visible as it can contain
|
||||
# hostnames revealing infrastucture internal details.
|
||||
node['last_error'] = ('** Value Redacted - Requires '
|
||||
'baremetal:node:get:last_error '
|
||||
'permission. **')
|
||||
if not policy.check("baremetal:node:get:reservation",
|
||||
target_dict, cdict):
|
||||
# Guard conductor names from being visible.
|
||||
node['reservation'] = ('** Redacted - requires baremetal:'
|
||||
'node:get:reservation permission. **')
|
||||
if not policy.check("baremetal:node:get:driver_internal_info",
|
||||
target_dict, cdict):
|
||||
# Guard conductor names from being visible.
|
||||
node['driver_internal_info'] = {
|
||||
'content': '** Redacted - Requires baremetal:node:get:'
|
||||
'driver_internal_info permission. **'}
|
||||
if not policy.check("baremetal:node:get:driver_info",
|
||||
target_dict, cdict):
|
||||
# Guard infrastructure intenral details from being visible.
|
||||
node['driver_info'] = {
|
||||
'content': '** Redacted - requires baremetal:node:get:'
|
||||
'driver_info permission. **'}
|
||||
|
||||
if not show_driver_secrets and node.get('driver_info'):
|
||||
node['driver_info'] = strutils.mask_dict_password(
|
||||
@ -2148,6 +2200,36 @@ class NodesController(rest.RestController):
|
||||
and strutils.bool_from_string(p['value'], default=None)
|
||||
is False):
|
||||
policy_checks.append('baremetal:node:disable_cleaning')
|
||||
elif p['path'].startswith('/driver_info'):
|
||||
policy_checks.append('baremetal:node:update:driver_info')
|
||||
elif p['path'].startswith('/properties'):
|
||||
policy_checks.append('baremetal:node:update:properties')
|
||||
elif p['path'].startswith('/chassis_uuid'):
|
||||
policy_checks.append('baremetal:node:update:chassis_uuid')
|
||||
elif p['path'].startswith('/instance_uuid'):
|
||||
policy_checks.append('baremetal:node:update:instance_uuid')
|
||||
elif p['path'].startswith('/lessee'):
|
||||
policy_checks.append('baremetal:node:update:lessee')
|
||||
elif p['path'].startswith('/owner'):
|
||||
policy_checks.append('baremetal:node:update:owner')
|
||||
elif p['path'].startswith('/driver'):
|
||||
policy_checks.append('baremetal:node:update:driver_interfaces')
|
||||
elif ((p['path'].lstrip('/').rsplit(sep="_", maxsplit=1)[0]
|
||||
in driver_base.ALL_INTERFACES)
|
||||
and (p['path'].lstrip('/').rsplit(sep="_", maxsplit=1)[-1]
|
||||
== "interface")):
|
||||
# TODO(TheJulia): Replace the above check with something like
|
||||
# elif (p['path'].lstrip('/').removesuffix('_interface')
|
||||
# when the minimum supported version is Python 3.9.
|
||||
policy_checks.append('baremetal:node:update:driver_interfaces')
|
||||
elif p['path'].startswith('/network_data'):
|
||||
policy_checks.append('baremetal:node:update:network_data')
|
||||
elif p['path'].startswith('/conductor_group'):
|
||||
policy_checks.append('baremetal:node:update:conductor_group')
|
||||
elif p['path'].startswith('/name'):
|
||||
policy_checks.append('baremetal:node:update:name')
|
||||
elif p['path'].startswith('/retired'):
|
||||
policy_checks.append('baremetal:node:update:retired')
|
||||
else:
|
||||
generic_update = True
|
||||
|
||||
|
@ -25,6 +25,7 @@ import jsonschema
|
||||
from jsonschema import exceptions as json_schema_exc
|
||||
import os_traits
|
||||
from oslo_config import cfg
|
||||
from oslo_policy import policy as oslo_policy
|
||||
from oslo_utils import uuidutils
|
||||
from pecan import rest
|
||||
|
||||
@ -1502,23 +1503,34 @@ def check_policy(policy_name):
|
||||
policy.authorize(policy_name, cdict, api.request.context)
|
||||
|
||||
|
||||
def check_owner_policy(object_type, policy_name, owner, lessee=None):
|
||||
def check_owner_policy(object_type, policy_name, owner, lessee=None,
|
||||
conceal_node=False):
|
||||
"""Check if the policy authorizes this request on an object.
|
||||
|
||||
:param: object_type: type of object being checked
|
||||
:param: policy_name: Name of the policy to check.
|
||||
:param: owner: the owner
|
||||
:param: lessee: the lessee
|
||||
:param: conceal_node: the UUID of the node IF we should
|
||||
conceal the existence of the node with a
|
||||
404 Error instead of a 403 Error.
|
||||
|
||||
:raises: HTTPForbidden if the policy forbids access.
|
||||
"""
|
||||
cdict = api.request.context.to_policy_values()
|
||||
|
||||
target_dict = dict(cdict)
|
||||
target_dict[object_type + '.owner'] = owner
|
||||
if lessee:
|
||||
target_dict[object_type + '.lessee'] = lessee
|
||||
policy.authorize(policy_name, target_dict, api.request.context)
|
||||
try:
|
||||
policy.authorize(policy_name, target_dict, api.request.context)
|
||||
except exception.HTTPForbidden:
|
||||
if conceal_node:
|
||||
# The caller does NOT have access to the node and we've been told
|
||||
# we should return a 404 instead of HTTPForbidden.
|
||||
raise exception.NodeNotFound(node=conceal_node)
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
def check_node_policy_and_retrieve(policy_name, node_ident,
|
||||
@ -1533,20 +1545,30 @@ def check_node_policy_and_retrieve(policy_name, node_ident,
|
||||
:raises: NodeNotFound if the node is not found.
|
||||
:return: RPC node identified by node_ident
|
||||
"""
|
||||
conceal_node = False
|
||||
try:
|
||||
if with_suffix:
|
||||
rpc_node = get_rpc_node_with_suffix(node_ident)
|
||||
else:
|
||||
rpc_node = get_rpc_node(node_ident)
|
||||
except exception.NodeNotFound:
|
||||
# don't expose non-existence of node unless requester
|
||||
# has generic access to policy
|
||||
cdict = api.request.context.to_policy_values()
|
||||
policy.authorize(policy_name, cdict, api.request.context)
|
||||
raise
|
||||
|
||||
# Project scoped users will get a 404 where as system
|
||||
# scoped should get a 403
|
||||
cdict = api.request.context.to_policy_values()
|
||||
if cdict.get('project_id', False):
|
||||
conceal_node = node_ident
|
||||
try:
|
||||
# Always check the ability to see the node BEFORE anything else.
|
||||
check_owner_policy('node', 'baremetal:node:get', rpc_node['owner'],
|
||||
rpc_node['lessee'], conceal_node=conceal_node)
|
||||
except exception.NotAuthorized:
|
||||
raise exception.NodeNotFound(node=node_ident)
|
||||
# If we've reached here, we can see the node and we have
|
||||
# access to view it.
|
||||
check_owner_policy('node', policy_name,
|
||||
rpc_node['owner'], rpc_node['lessee'])
|
||||
rpc_node['owner'], rpc_node['lessee'],
|
||||
conceal_node=False)
|
||||
return rpc_node
|
||||
|
||||
|
||||
@ -1612,7 +1634,9 @@ def check_list_policy(object_type, owner=None):
|
||||
try:
|
||||
policy.authorize('baremetal:%s:list_all' % object_type,
|
||||
cdict, api.request.context)
|
||||
except exception.HTTPForbidden:
|
||||
except (exception.HTTPForbidden, oslo_policy.InvalidScope):
|
||||
# In the event the scoped policy fails, falling back to the
|
||||
# policy governing a filtered view.
|
||||
project_owner = cdict.get('project_id')
|
||||
if (not project_owner or (owner and owner != project_owner)):
|
||||
raise
|
||||
|
@ -80,13 +80,36 @@ PROJECT_READER = ('role:reader and '
|
||||
# protecting APIs designed to operate with multiple scopes (e.g., a system
|
||||
# administrator should be able to delete any baremetal host in the deployment,
|
||||
# a project member should only be able to delete hosts in their project).
|
||||
SYSTEM_ADMIN_OR_PROJECT_MEMBER = (
|
||||
'(' + SYSTEM_ADMIN + ') or (' + PROJECT_MEMBER + ')'
|
||||
SYSTEM_OR_PROJECT_MEMBER = (
|
||||
'(' + SYSTEM_MEMBER + ') or (' + PROJECT_MEMBER + ')'
|
||||
)
|
||||
SYSTEM_OR_PROJECT_READER = (
|
||||
'(' + SYSTEM_READER + ') or (' + PROJECT_READER + ')'
|
||||
)
|
||||
|
||||
PROJECT_OWNER_ADMIN = ('role:admin and project_id:%(node.owner)s')
|
||||
PROJECT_OWNER_MEMBER = ('role:member and project_id:%(node.owner)s')
|
||||
PROJECT_OWNER_READER = ('role:reader and project_id:%(node.owner)s')
|
||||
PROJECT_LESSEE_ADMIN = ('role:admin and project_id:%(node.lessee)s')
|
||||
|
||||
SYSTEM_OR_OWNER_MEMBER_AND_LESSEE_ADMIN = (
|
||||
'(' + SYSTEM_MEMBER + ') or (' + PROJECT_OWNER_MEMBER + ') or (' + PROJECT_LESSEE_ADMIN + ')' # noqa
|
||||
)
|
||||
|
||||
SYSTEM_MEMBER_OR_OWNER_ADMIN = (
|
||||
'(' + SYSTEM_MEMBER + ') or (' + PROJECT_OWNER_ADMIN + ')'
|
||||
)
|
||||
|
||||
SYSTEM_MEMBER_OR_OWNER_MEMBER = (
|
||||
'(' + SYSTEM_MEMBER + ') or (' + PROJECT_OWNER_MEMBER + ')'
|
||||
)
|
||||
|
||||
SYSTEM_OR_OWNER_READER = (
|
||||
'(' + SYSTEM_READER + ') or (' + PROJECT_OWNER_READER + ')'
|
||||
)
|
||||
|
||||
API_READER = ('role:reader')
|
||||
|
||||
default_policies = [
|
||||
# Legacy setting, don't remove. Likely to be overridden by operators who
|
||||
# forget to update their policy.json configuration file.
|
||||
@ -282,21 +305,12 @@ node_policies = [
|
||||
deprecated_reason=deprecated_node_reason,
|
||||
deprecated_since=versionutils.deprecated.WALLABY
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:get',
|
||||
check_str=SYSTEM_READER,
|
||||
scope_types=['system'],
|
||||
description='Retrieve a single Node record',
|
||||
operations=[{'path': '/nodes/{node_ident}', 'method': 'GET'}],
|
||||
deprecated_rule=deprecated_node_get,
|
||||
deprecated_reason=deprecated_node_reason,
|
||||
deprecated_since=versionutils.deprecated.WALLABY
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:list',
|
||||
check_str=SYSTEM_READER,
|
||||
scope_types=['system'],
|
||||
description='Retrieve multiple Node records, filtered by owner',
|
||||
check_str=API_READER,
|
||||
scope_types=['system', 'project'],
|
||||
description='Retrieve multiple Node records, filtered by '
|
||||
'an explicit owner or the client project_id',
|
||||
operations=[{'path': '/nodes', 'method': 'GET'},
|
||||
{'path': '/nodes/detail', 'method': 'GET'}],
|
||||
deprecated_rule=deprecated_node_list,
|
||||
@ -315,38 +329,246 @@ node_policies = [
|
||||
deprecated_since=versionutils.deprecated.WALLABY
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:update',
|
||||
check_str=SYSTEM_MEMBER,
|
||||
scope_types=['system'],
|
||||
description='Update Node records',
|
||||
name='baremetal:node:get',
|
||||
check_str=SYSTEM_OR_PROJECT_READER,
|
||||
scope_types=['system', 'project'],
|
||||
description='Retrieve a single Node record',
|
||||
operations=[{'path': '/nodes/{node_ident}', 'method': 'GET'}],
|
||||
deprecated_rule=deprecated_node_get,
|
||||
deprecated_reason=deprecated_node_reason,
|
||||
deprecated_since=versionutils.deprecated.WALLABY
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:get:filter_threshold',
|
||||
check_str=SYSTEM_READER,
|
||||
scope_types=['system', 'project'],
|
||||
description='Filter to allow operators to govern the threshold '
|
||||
'where information should be filtered. Non-authorized '
|
||||
'users will be subjected to additional API policy '
|
||||
'checks for API content response bodies.',
|
||||
operations=[{'path': '/nodes/{node_ident}', 'method': 'GET'}],
|
||||
# This rule fallsback to deprecated_node_get in order to provide a
|
||||
# mechanism so the additional policies only engage in an updated
|
||||
# operating context.
|
||||
deprecated_rule=deprecated_node_get,
|
||||
deprecated_reason=deprecated_node_reason,
|
||||
deprecated_since=versionutils.deprecated.WALLABY,
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:get:last_error',
|
||||
check_str=SYSTEM_OR_OWNER_READER,
|
||||
scope_types=['system', 'project'],
|
||||
description='Governs if the node last_error field is masked from API'
|
||||
'clients with insufficent privileges.',
|
||||
operations=[{'path': '/nodes/{node_ident}', 'method': 'GET'}],
|
||||
deprecated_rule=deprecated_node_get,
|
||||
deprecated_reason=deprecated_node_reason,
|
||||
deprecated_since=versionutils.deprecated.WALLABY
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:get:reservation',
|
||||
check_str=SYSTEM_OR_OWNER_READER,
|
||||
scope_types=['system', 'project'],
|
||||
description='Governs if the node reservation field is masked from API'
|
||||
'clients with insufficent privileges.',
|
||||
operations=[{'path': '/nodes/{node_ident}', 'method': 'GET'}],
|
||||
deprecated_rule=deprecated_node_get,
|
||||
deprecated_reason=deprecated_node_reason,
|
||||
deprecated_since=versionutils.deprecated.WALLABY
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:get:driver_internal_info',
|
||||
check_str=SYSTEM_OR_OWNER_READER,
|
||||
scope_types=['system', 'project'],
|
||||
description='Governs if the node driver_internal_info field is '
|
||||
'masked from API clients with insufficent privileges.',
|
||||
operations=[{'path': '/nodes/{node_ident}', 'method': 'GET'}],
|
||||
deprecated_rule=deprecated_node_get,
|
||||
deprecated_reason=deprecated_node_reason,
|
||||
deprecated_since=versionutils.deprecated.WALLABY
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:get:driver_info',
|
||||
check_str=SYSTEM_OR_OWNER_READER,
|
||||
scope_types=['system', 'project'],
|
||||
description='Governs if the driver_info field is masked from API'
|
||||
'clients with insufficent privileges.',
|
||||
operations=[{'path': '/nodes/{node_ident}', 'method': 'GET'}],
|
||||
deprecated_rule=deprecated_node_get,
|
||||
deprecated_reason=deprecated_node_reason,
|
||||
deprecated_since=versionutils.deprecated.WALLABY
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:update:driver_info',
|
||||
check_str=SYSTEM_MEMBER_OR_OWNER_MEMBER,
|
||||
scope_types=['system', 'project'],
|
||||
description='Governs if node driver_info field can be updated via '
|
||||
'the API clients.',
|
||||
operations=[{'path': '/nodes/{node_ident}', 'method': 'PATCH'}],
|
||||
deprecated_rule=deprecated_node_update,
|
||||
deprecated_reason=deprecated_node_reason,
|
||||
deprecated_since=versionutils.deprecated.WALLABY
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:update:properties',
|
||||
check_str=SYSTEM_MEMBER_OR_OWNER_MEMBER,
|
||||
scope_types=['system', 'project'],
|
||||
description='Governs if node properties field can be updated via '
|
||||
'the API clients.',
|
||||
operations=[{'path': '/nodes/{node_ident}', 'method': 'PATCH'}],
|
||||
deprecated_rule=deprecated_node_update,
|
||||
deprecated_reason=deprecated_node_reason,
|
||||
deprecated_since=versionutils.deprecated.WALLABY
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:update:chassis_uuid',
|
||||
check_str=SYSTEM_ADMIN,
|
||||
scope_types=['system', 'project'],
|
||||
description='Governs if node chassis_uuid field can be updated via '
|
||||
'the API clients.',
|
||||
operations=[{'path': '/nodes/{node_ident}', 'method': 'PATCH'}],
|
||||
deprecated_rule=deprecated_node_update,
|
||||
deprecated_reason=deprecated_node_reason,
|
||||
deprecated_since=versionutils.deprecated.WALLABY
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:update:instance_uuid',
|
||||
check_str=SYSTEM_MEMBER_OR_OWNER_MEMBER,
|
||||
scope_types=['system', 'project'],
|
||||
description='Governs if node instance_uuid field can be updated via '
|
||||
'the API clients.',
|
||||
operations=[{'path': '/nodes/{node_ident}', 'method': 'PATCH'}],
|
||||
deprecated_rule=deprecated_node_update,
|
||||
deprecated_reason=deprecated_node_reason,
|
||||
deprecated_since=versionutils.deprecated.WALLABY
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:update:lessee',
|
||||
check_str=SYSTEM_MEMBER_OR_OWNER_MEMBER,
|
||||
scope_types=['system', 'project'],
|
||||
description='Governs if node lessee field can be updated via '
|
||||
'the API clients.',
|
||||
operations=[{'path': '/nodes/{node_ident}', 'method': 'PATCH'}],
|
||||
deprecated_rule=deprecated_node_update,
|
||||
deprecated_reason=deprecated_node_reason,
|
||||
deprecated_since=versionutils.deprecated.WALLABY
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:update:owner',
|
||||
check_str=SYSTEM_MEMBER,
|
||||
scope_types=['system', 'project'],
|
||||
description='Governs if node owner field can be updated via '
|
||||
'the API clients.',
|
||||
operations=[{'path': '/nodes/{node_ident}', 'method': 'PATCH'}],
|
||||
deprecated_rule=deprecated_node_update,
|
||||
deprecated_reason=deprecated_node_reason,
|
||||
deprecated_since=versionutils.deprecated.WALLABY
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:update:driver_interfaces',
|
||||
check_str=SYSTEM_MEMBER_OR_OWNER_ADMIN,
|
||||
scope_types=['system', 'project'],
|
||||
description='Governs if node driver and driver interfaces field '
|
||||
'can be updated via the API clients.',
|
||||
operations=[{'path': '/nodes/{node_ident}', 'method': 'PATCH'}],
|
||||
deprecated_rule=deprecated_node_update,
|
||||
deprecated_reason=deprecated_node_reason,
|
||||
deprecated_since=versionutils.deprecated.WALLABY
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:update:network_data',
|
||||
check_str=SYSTEM_MEMBER_OR_OWNER_MEMBER,
|
||||
scope_types=['system', 'project'],
|
||||
description='Governs if node driver_info field can be updated via '
|
||||
'the API clients.',
|
||||
operations=[{'path': '/nodes/{node_ident}', 'method': 'PATCH'}],
|
||||
deprecated_rule=deprecated_node_update,
|
||||
deprecated_reason=deprecated_node_reason,
|
||||
deprecated_since=versionutils.deprecated.WALLABY
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:update:conductor_group',
|
||||
check_str=SYSTEM_MEMBER,
|
||||
scope_types=['system', 'project'],
|
||||
description='Governs if node conductor_group field can be updated '
|
||||
'via the API clients.',
|
||||
operations=[{'path': '/nodes/{node_ident}', 'method': 'PATCH'}],
|
||||
deprecated_rule=deprecated_node_update,
|
||||
deprecated_reason=deprecated_node_reason,
|
||||
deprecated_since=versionutils.deprecated.WALLABY
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:update:name',
|
||||
check_str=SYSTEM_MEMBER_OR_OWNER_MEMBER,
|
||||
scope_types=['system', 'project'],
|
||||
description='Governs if node name field can be updated via '
|
||||
'the API clients.',
|
||||
operations=[{'path': '/nodes/{node_ident}', 'method': 'PATCH'}],
|
||||
deprecated_rule=deprecated_node_update,
|
||||
deprecated_reason=deprecated_node_reason,
|
||||
deprecated_since=versionutils.deprecated.WALLABY
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:update:retired',
|
||||
check_str=SYSTEM_MEMBER_OR_OWNER_MEMBER,
|
||||
scope_types=['system', 'project'],
|
||||
description='Governs if node retired and retired reason '
|
||||
'can be updated by API clients.',
|
||||
operations=[{'path': '/nodes/{node_ident}', 'method': 'PATCH'}],
|
||||
deprecated_rule=deprecated_node_update,
|
||||
deprecated_reason=deprecated_node_reason,
|
||||
deprecated_since=versionutils.deprecated.WALLABY
|
||||
),
|
||||
|
||||
# If this role is denied we should likely roll into the other rules
|
||||
# Like, this rule could match "SYSTEM_MEMBER" by default and then drill
|
||||
# further into each field, which would maintain what we do today, and
|
||||
# enable further testing.
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:update',
|
||||
check_str=SYSTEM_OR_PROJECT_MEMBER,
|
||||
scope_types=['system', 'project'],
|
||||
description='Generalized update of node records',
|
||||
operations=[{'path': '/nodes/{node_ident}', 'method': 'PATCH'}],
|
||||
deprecated_rule=deprecated_node_update,
|
||||
deprecated_reason=deprecated_node_reason,
|
||||
deprecated_since=versionutils.deprecated.WALLABY
|
||||
),
|
||||
# TODO(TheJulia): Explicit RBAC testing needed for this.
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:update_extra',
|
||||
check_str=SYSTEM_MEMBER,
|
||||
scope_types=['system'],
|
||||
check_str=SYSTEM_OR_PROJECT_MEMBER,
|
||||
scope_types=['system', 'project'],
|
||||
description='Update Node extra field',
|
||||
operations=[{'path': '/nodes/{node_ident}', 'method': 'PATCH'}],
|
||||
deprecated_rule=deprecated_node_update_extra,
|
||||
deprecated_reason=deprecated_node_reason,
|
||||
deprecated_since=versionutils.deprecated.WALLABY
|
||||
),
|
||||
# TODO(TheJulia): Explicit RBAC testing needed for this.
|
||||
# TODO(TheJulia): So multiple additional fields need policies. This needs
|
||||
# to be reviewed/audited/addressed.
|
||||
# * Get ability on last_error - policy added
|
||||
# * Get ability on reservation (conductor names) - policy added
|
||||
# * get ability on driver_internal_info (internal addressing) added
|
||||
# * ability to get driver_info - policy added
|
||||
# * ability to set driver_info - policy added
|
||||
# * ability to set properties. - added
|
||||
# * ability to set chassis_uuid - added
|
||||
# * ability to set instance_uuid - added
|
||||
# * ability to set a lessee - default only to admin or owner. added
|
||||
# * ability to set driver/*_interface - added
|
||||
# * ability to set network_data - added
|
||||
# * ability to set conductor_group -added
|
||||
# * ability to set name -added
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:update_instance_info',
|
||||
check_str=SYSTEM_MEMBER,
|
||||
scope_types=['system'],
|
||||
check_str=SYSTEM_OR_OWNER_MEMBER_AND_LESSEE_ADMIN,
|
||||
scope_types=['system', 'project'],
|
||||
description='Update Node instance_info field',
|
||||
operations=[{'path': '/nodes/{node_ident}', 'method': 'PATCH'}],
|
||||
deprecated_rule=deprecated_node_update_instance_info,
|
||||
deprecated_reason=deprecated_node_reason,
|
||||
deprecated_since=versionutils.deprecated.WALLABY
|
||||
),
|
||||
# TODO(TheJulia): Explicit RBAC testing needed for this.
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:update_owner_provisioned',
|
||||
check_str=SYSTEM_MEMBER,
|
||||
@ -357,11 +579,10 @@ node_policies = [
|
||||
deprecated_reason=deprecated_node_reason,
|
||||
deprecated_since=versionutils.deprecated.WALLABY
|
||||
),
|
||||
# TODO(TheJulia): Explicit RBAC testing needed for this... Maybe?
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:delete',
|
||||
check_str=SYSTEM_ADMIN,
|
||||
scope_types=['system'],
|
||||
scope_types=['system', 'project'],
|
||||
description='Delete Node records',
|
||||
operations=[{'path': '/nodes/{node_ident}', 'method': 'DELETE'}],
|
||||
deprecated_rule=deprecated_node_delete,
|
||||
@ -371,8 +592,8 @@ node_policies = [
|
||||
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:validate',
|
||||
check_str=SYSTEM_MEMBER,
|
||||
scope_types=['system'],
|
||||
check_str=SYSTEM_OR_OWNER_MEMBER_AND_LESSEE_ADMIN,
|
||||
scope_types=['system', 'project'],
|
||||
description='Request active validation of Nodes',
|
||||
operations=[
|
||||
{'path': '/nodes/{node_ident}/validate', 'method': 'GET'}
|
||||
@ -384,8 +605,8 @@ node_policies = [
|
||||
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:set_maintenance',
|
||||
check_str=SYSTEM_MEMBER,
|
||||
scope_types=['system'],
|
||||
check_str=SYSTEM_OR_OWNER_MEMBER_AND_LESSEE_ADMIN,
|
||||
scope_types=['system', 'project'],
|
||||
description='Set maintenance flag, taking a Node out of service',
|
||||
operations=[
|
||||
{'path': '/nodes/{node_ident}/maintenance', 'method': 'PUT'}
|
||||
@ -396,8 +617,8 @@ node_policies = [
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:clear_maintenance',
|
||||
check_str=SYSTEM_MEMBER,
|
||||
scope_types=['system'],
|
||||
check_str=SYSTEM_OR_OWNER_MEMBER_AND_LESSEE_ADMIN,
|
||||
scope_types=['system', 'project'],
|
||||
description=(
|
||||
'Clear maintenance flag, placing the Node into service again'
|
||||
),
|
||||
@ -413,8 +634,8 @@ node_policies = [
|
||||
# a cached object.
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:get_boot_device',
|
||||
check_str=SYSTEM_MEMBER,
|
||||
scope_types=['system'],
|
||||
check_str=SYSTEM_MEMBER_OR_OWNER_ADMIN,
|
||||
scope_types=['system', 'project'],
|
||||
description='Retrieve Node boot device metadata',
|
||||
operations=[
|
||||
{'path': '/nodes/{node_ident}/management/boot_device',
|
||||
@ -428,8 +649,8 @@ node_policies = [
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:set_boot_device',
|
||||
check_str=SYSTEM_MEMBER,
|
||||
scope_types=['system'],
|
||||
check_str=SYSTEM_MEMBER_OR_OWNER_ADMIN,
|
||||
scope_types=['system', 'project'],
|
||||
description='Change Node boot device',
|
||||
operations=[
|
||||
{'path': '/nodes/{node_ident}/management/boot_device',
|
||||
@ -442,8 +663,8 @@ node_policies = [
|
||||
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:get_indicator_state',
|
||||
check_str=SYSTEM_READER,
|
||||
scope_types=['system'],
|
||||
check_str=SYSTEM_OR_PROJECT_READER,
|
||||
scope_types=['system', 'project'],
|
||||
description='Retrieve Node indicators and their states',
|
||||
operations=[
|
||||
{'path': '/nodes/{node_ident}/management/indicators/'
|
||||
@ -458,8 +679,8 @@ node_policies = [
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:set_indicator_state',
|
||||
check_str=SYSTEM_MEMBER,
|
||||
scope_types=['system'],
|
||||
check_str=SYSTEM_MEMBER_OR_OWNER_MEMBER,
|
||||
scope_types=['system', 'project'],
|
||||
description='Change Node indicator state',
|
||||
operations=[
|
||||
{'path': '/nodes/{node_ident}/management/indicators/'
|
||||
@ -473,8 +694,8 @@ node_policies = [
|
||||
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:inject_nmi',
|
||||
check_str=SYSTEM_MEMBER,
|
||||
scope_types=['system'],
|
||||
check_str=SYSTEM_MEMBER_OR_OWNER_ADMIN,
|
||||
scope_types=['system', 'project'],
|
||||
description='Inject NMI for a node',
|
||||
operations=[
|
||||
{'path': '/nodes/{node_ident}/management/inject_nmi',
|
||||
@ -487,8 +708,8 @@ node_policies = [
|
||||
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:get_states',
|
||||
check_str=SYSTEM_READER,
|
||||
scope_types=['system'],
|
||||
check_str=SYSTEM_OR_PROJECT_READER,
|
||||
scope_types=['system', 'project'],
|
||||
description='View Node power and provision state',
|
||||
operations=[{'path': '/nodes/{node_ident}/states', 'method': 'GET'}],
|
||||
deprecated_rule=deprecated_node_get_states,
|
||||
@ -497,8 +718,8 @@ node_policies = [
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:set_power_state',
|
||||
check_str=SYSTEM_MEMBER,
|
||||
scope_types=['system'],
|
||||
check_str=SYSTEM_OR_PROJECT_MEMBER,
|
||||
scope_types=['system', 'project'],
|
||||
description='Change Node power status',
|
||||
operations=[
|
||||
{'path': '/nodes/{node_ident}/states/power', 'method': 'PUT'}
|
||||
@ -509,8 +730,8 @@ node_policies = [
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:set_provision_state',
|
||||
check_str=SYSTEM_MEMBER,
|
||||
scope_types=['system'],
|
||||
check_str=SYSTEM_OR_OWNER_MEMBER_AND_LESSEE_ADMIN,
|
||||
scope_types=['system', 'project'],
|
||||
description='Change Node provision status',
|
||||
operations=[
|
||||
{'path': '/nodes/{node_ident}/states/provision', 'method': 'PUT'}
|
||||
@ -521,8 +742,8 @@ node_policies = [
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:set_raid_state',
|
||||
check_str=SYSTEM_MEMBER,
|
||||
scope_types=['system'],
|
||||
check_str=SYSTEM_MEMBER_OR_OWNER_MEMBER,
|
||||
scope_types=['system', 'project'],
|
||||
description='Change Node RAID status',
|
||||
operations=[
|
||||
{'path': '/nodes/{node_ident}/states/raid', 'method': 'PUT'}
|
||||
@ -533,8 +754,8 @@ node_policies = [
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:get_console',
|
||||
check_str=SYSTEM_MEMBER,
|
||||
scope_types=['system'],
|
||||
check_str=SYSTEM_MEMBER_OR_OWNER_MEMBER,
|
||||
scope_types=['system', 'project'],
|
||||
description='Get Node console connection information',
|
||||
operations=[
|
||||
{'path': '/nodes/{node_ident}/states/console', 'method': 'GET'}
|
||||
@ -545,8 +766,8 @@ node_policies = [
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:set_console_state',
|
||||
check_str=SYSTEM_MEMBER,
|
||||
scope_types=['system'],
|
||||
check_str=SYSTEM_MEMBER_OR_OWNER_MEMBER,
|
||||
scope_types=['system', 'project'],
|
||||
description='Change Node console status',
|
||||
operations=[
|
||||
{'path': '/nodes/{node_ident}/states/console', 'method': 'PUT'}
|
||||
@ -558,8 +779,8 @@ node_policies = [
|
||||
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:vif:list',
|
||||
check_str=SYSTEM_READER,
|
||||
scope_types=['system'],
|
||||
check_str=SYSTEM_OR_PROJECT_READER,
|
||||
scope_types=['system', 'project'],
|
||||
description='List VIFs attached to node',
|
||||
operations=[{'path': '/nodes/{node_ident}/vifs', 'method': 'GET'}],
|
||||
deprecated_rule=deprecated_node_vif_list,
|
||||
@ -568,8 +789,8 @@ node_policies = [
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:vif:attach',
|
||||
check_str=SYSTEM_MEMBER,
|
||||
scope_types=['system'],
|
||||
check_str=SYSTEM_OR_OWNER_MEMBER_AND_LESSEE_ADMIN,
|
||||
scope_types=['system', 'project'],
|
||||
description='Attach a VIF to a node',
|
||||
operations=[{'path': '/nodes/{node_ident}/vifs', 'method': 'POST'}],
|
||||
deprecated_rule=deprecated_node_vif_attach,
|
||||
@ -578,8 +799,8 @@ node_policies = [
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:vif:detach',
|
||||
check_str=SYSTEM_MEMBER,
|
||||
scope_types=['system'],
|
||||
check_str=SYSTEM_OR_OWNER_MEMBER_AND_LESSEE_ADMIN,
|
||||
scope_types=['system', 'project'],
|
||||
description='Detach a VIF from a node',
|
||||
operations=[
|
||||
{'path': '/nodes/{node_ident}/vifs/{node_vif_ident}',
|
||||
@ -589,11 +810,10 @@ node_policies = [
|
||||
deprecated_reason=deprecated_node_reason,
|
||||
deprecated_since=versionutils.deprecated.WALLABY
|
||||
),
|
||||
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:traits:list',
|
||||
check_str=SYSTEM_READER,
|
||||
scope_types=['system'],
|
||||
check_str=SYSTEM_OR_PROJECT_READER,
|
||||
scope_types=['system', 'project'],
|
||||
description='List node traits',
|
||||
operations=[{'path': '/nodes/{node_ident}/traits', 'method': 'GET'}],
|
||||
deprecated_rule=deprecated_node_traits_list,
|
||||
@ -602,8 +822,8 @@ node_policies = [
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:traits:set',
|
||||
check_str=SYSTEM_MEMBER,
|
||||
scope_types=['system'],
|
||||
check_str=SYSTEM_MEMBER_OR_OWNER_ADMIN,
|
||||
scope_types=['system', 'project'],
|
||||
description='Add a trait to, or replace all traits of, a node',
|
||||
operations=[
|
||||
{'path': '/nodes/{node_ident}/traits', 'method': 'PUT'},
|
||||
@ -615,8 +835,8 @@ node_policies = [
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:traits:delete',
|
||||
check_str=SYSTEM_MEMBER,
|
||||
scope_types=['system'],
|
||||
check_str=SYSTEM_MEMBER_OR_OWNER_ADMIN,
|
||||
scope_types=['system', 'project'],
|
||||
description='Remove one or all traits from a node',
|
||||
operations=[
|
||||
{'path': '/nodes/{node_ident}/traits', 'method': 'DELETE'},
|
||||
@ -630,8 +850,8 @@ node_policies = [
|
||||
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:bios:get',
|
||||
check_str=SYSTEM_READER,
|
||||
scope_types=['system'],
|
||||
check_str=SYSTEM_OR_PROJECT_READER,
|
||||
scope_types=['system', 'project'],
|
||||
description='Retrieve Node BIOS information',
|
||||
operations=[
|
||||
{'path': '/nodes/{node_ident}/bios', 'method': 'GET'},
|
||||
@ -975,7 +1195,10 @@ vendor_passthru_policies = [
|
||||
policy.DocumentedRuleDefault(
|
||||
name='baremetal:node:vendor_passthru',
|
||||
check_str=SYSTEM_ADMIN,
|
||||
scope_types=['system'],
|
||||
# NOTE(TheJulia): Project scope listed, but not a project scoped role
|
||||
# as some operators may find it useful to provide access to say owner
|
||||
# admins.
|
||||
scope_types=['system', 'project'],
|
||||
description='Access vendor-specific Node functions',
|
||||
operations=[
|
||||
{'path': 'nodes/{node_ident}/vendor_passthru/methods',
|
||||
@ -1503,3 +1726,16 @@ def check(rule, target, creds, *args, **kwargs):
|
||||
"""
|
||||
enforcer = get_enforcer()
|
||||
return enforcer.enforce(rule, target, creds, *args, **kwargs)
|
||||
|
||||
|
||||
def check_policy(rule, target, creds, *args, **kwargs):
|
||||
"""Configuration aware role policy check wrapper.
|
||||
|
||||
Checks authorization of a rule against the target and credentials
|
||||
and returns True or False.
|
||||
Always returns true if CONF.auth_strategy is not keystone.
|
||||
"""
|
||||
if CONF.auth_strategy != 'keystone':
|
||||
return True
|
||||
enforcer = get_enforcer()
|
||||
return enforcer.enforce(rule, target, creds, *args, **kwargs)
|
||||
|
@ -1136,10 +1136,13 @@ class TestCheckNodePolicyAndRetrieve(base.TestCase):
|
||||
rpc_node = utils.check_node_policy_and_retrieve(
|
||||
'fake_policy', self.valid_node_uuid
|
||||
)
|
||||
authorize_calls = [
|
||||
mock.call('baremetal:node:get', expected_target, fake_context),
|
||||
mock.call('fake_policy', expected_target, fake_context)]
|
||||
|
||||
mock_grn.assert_called_once_with(self.valid_node_uuid)
|
||||
mock_grnws.assert_not_called()
|
||||
mock_authorize.assert_called_once_with(
|
||||
'fake_policy', expected_target, fake_context)
|
||||
mock_authorize.assert_has_calls(authorize_calls)
|
||||
self.assertEqual(self.node, rpc_node)
|
||||
|
||||
@mock.patch.object(api, 'request', spec_set=["context", "version"])
|
||||
@ -1162,14 +1165,16 @@ class TestCheckNodePolicyAndRetrieve(base.TestCase):
|
||||
)
|
||||
mock_grn.assert_not_called()
|
||||
mock_grnws.assert_called_once_with(self.valid_node_uuid)
|
||||
mock_authorize.assert_called_once_with(
|
||||
'fake_policy', expected_target, fake_context)
|
||||
authorize_calls = [
|
||||
mock.call('baremetal:node:get', expected_target, fake_context),
|
||||
mock.call('fake_policy', expected_target, fake_context)]
|
||||
mock_authorize.assert_has_calls(authorize_calls)
|
||||
self.assertEqual(self.node, rpc_node)
|
||||
|
||||
@mock.patch.object(api, 'request', spec_set=["context"])
|
||||
@mock.patch.object(policy, 'authorize', spec=True)
|
||||
@mock.patch.object(utils, 'get_rpc_node', autospec=True)
|
||||
def test_check_node_policy_and_retrieve_no_node_policy_forbidden(
|
||||
def test_check_node_policy_and_retrieve_no_node_policy_notfound(
|
||||
self, mock_grn, mock_authorize, mock_pr
|
||||
):
|
||||
mock_pr.context.to_policy_values.return_value = {}
|
||||
@ -1178,7 +1183,7 @@ class TestCheckNodePolicyAndRetrieve(base.TestCase):
|
||||
node=self.valid_node_uuid)
|
||||
|
||||
self.assertRaises(
|
||||
exception.HTTPForbidden,
|
||||
exception.NodeNotFound,
|
||||
utils.check_node_policy_and_retrieve,
|
||||
'fake-policy',
|
||||
self.valid_node_uuid
|
||||
@ -1213,7 +1218,7 @@ class TestCheckNodePolicyAndRetrieve(base.TestCase):
|
||||
mock_grn.return_value = self.node
|
||||
|
||||
self.assertRaises(
|
||||
exception.HTTPForbidden,
|
||||
exception.NodeNotFound,
|
||||
utils.check_node_policy_and_retrieve,
|
||||
'fake-policy',
|
||||
self.valid_node_uuid
|
||||
|
@ -142,9 +142,22 @@ class TestACLBase(base.BaseApiTest):
|
||||
)
|
||||
else:
|
||||
assert False, 'Unimplemented test method: %s' % method
|
||||
|
||||
# Once miggrated:
|
||||
# Items will return:
|
||||
# 403 - Trying to access something that is generally denied.
|
||||
# Example: PATCH /v1/nodes/<uuid> as a reader.
|
||||
# 404 - Trying to access something where we don't have permissions
|
||||
# in a project scope. This is particularly true where implied
|
||||
# permissions or assocation exists. Ports are attempted to be
|
||||
# accessed when the underlying node is inaccessible as owner
|
||||
# nor node matches.
|
||||
# Example: GET /v1/portgroups or /v1/nodes/<uuid>/ports
|
||||
# 500 - Attempting to access something such an system scoped endpoint
|
||||
# with a project scoped request. Example: /v1/conductors.
|
||||
if not (bool(deprecated)
|
||||
and ('403' in response.status or '500' in response.status)
|
||||
and ('404' in response.status
|
||||
or '500' in response.status
|
||||
or '403' in response.status)
|
||||
and cfg.CONF.oslo_policy.enforce_scope
|
||||
and cfg.CONF.oslo_policy.enforce_new_defaults):
|
||||
# NOTE(TheJulia): Everything, once migrated, should
|
||||
@ -152,7 +165,9 @@ class TestACLBase(base.BaseApiTest):
|
||||
self.assertEqual(assert_status, response.status_int)
|
||||
else:
|
||||
self.assertTrue(
|
||||
'403' in response.status or '500' in response.status)
|
||||
'404' in response.status
|
||||
or '500' in response.status
|
||||
or '403' in response.status)
|
||||
# We can't check the contents of the response if there is no
|
||||
# response.
|
||||
return
|
||||
@ -163,8 +178,23 @@ class TestACLBase(base.BaseApiTest):
|
||||
if assert_dict_contains:
|
||||
for k, v in assert_dict_contains.items():
|
||||
self.assertIn(k, response)
|
||||
self.assertEqual(v.format(**self.format_data),
|
||||
response.json[k])
|
||||
print(k)
|
||||
print(v)
|
||||
if str(v) == "None":
|
||||
# Compare since the variable loaded from the
|
||||
# json ends up being null in json or None.
|
||||
self.assertIsNone(response.json[k])
|
||||
elif str(v) == "{}":
|
||||
# Special match for signifying a dictonary.
|
||||
self.assertEqual({}, response.json[k])
|
||||
elif isinstance(v, dict):
|
||||
# The value from the YAML can be a dictionary,
|
||||
# which cannot be formatted, so we're likely doing
|
||||
# direct matching.
|
||||
self.assertEqual(str(v), str(response.json[k]))
|
||||
else:
|
||||
self.assertEqual(v.format(**self.format_data),
|
||||
response.json[k])
|
||||
|
||||
if assert_list_length:
|
||||
for root, length in assert_list_length.items():
|
||||
@ -173,7 +203,14 @@ class TestACLBase(base.BaseApiTest):
|
||||
# important for owner/lessee testing.
|
||||
items = response.json[root]
|
||||
self.assertIsInstance(items, list)
|
||||
self.assertEqual(length, len(items))
|
||||
if not (bool(deprecated)
|
||||
and cfg.CONF.oslo_policy.enforce_scope):
|
||||
self.assertEqual(length, len(items))
|
||||
else:
|
||||
# If we have scope enforcement, we likely have different
|
||||
# views, such as "other" admins being subjected to
|
||||
# a filtered view in these cases.
|
||||
self.assertEqual(0, len(items))
|
||||
|
||||
# NOTE(TheJulia): API tests in Ironic tend to have a pattern
|
||||
# to print request and response data to aid in development
|
||||
@ -207,13 +244,15 @@ class TestRBACModelBeforeScopesBase(TestACLBase):
|
||||
resource_class="CUSTOM_TEST")
|
||||
fake_db_node = db_utils.create_test_node(
|
||||
chassis_id=None,
|
||||
driver='fake-driverz')
|
||||
driver='fake-driverz',
|
||||
owner='z')
|
||||
fake_db_node_alloced = db_utils.create_test_node(
|
||||
id=allocated_node_id,
|
||||
chassis_id=None,
|
||||
allocation_id=fake_db_allocation['id'],
|
||||
uuid='22e26c0b-03f2-4d2e-ae87-c02d7f33c000',
|
||||
driver='fake-driverz')
|
||||
driver='fake-driverz',
|
||||
owner='z')
|
||||
fake_vif_port_id = "ee21d58f-5de2-4956-85ff-33935ea1ca00"
|
||||
fake_db_port = db_utils.create_test_port(
|
||||
node_id=fake_db_node['id'],
|
||||
@ -242,7 +281,6 @@ class TestRBACModelBeforeScopesBase(TestACLBase):
|
||||
# false positives with test runners.
|
||||
db_utils.create_test_node(
|
||||
uuid='18a552fb-dcd2-43bf-9302-e4c93287be11')
|
||||
|
||||
self.format_data.update({
|
||||
'node_ident': fake_db_node['uuid'],
|
||||
'allocated_node_ident': fake_db_node_alloced['uuid'],
|
||||
@ -337,11 +375,15 @@ class TestRBACProjectScoped(TestACLBase):
|
||||
# owner/lesse checks
|
||||
db_utils.create_test_node(
|
||||
uuid=owner_node_ident,
|
||||
owner=owner_node_ident)
|
||||
owner=owner_project_id,
|
||||
last_error='meow',
|
||||
reservation='lolcats')
|
||||
leased_node = db_utils.create_test_node(
|
||||
uuid=lessee_node_ident,
|
||||
owner=owner_project_id,
|
||||
lessee=lessee_project_id)
|
||||
lessee=lessee_project_id,
|
||||
last_error='meow',
|
||||
reservation='lolcats')
|
||||
fake_db_volume_target = db_utils.create_test_volume_target(
|
||||
node_id=leased_node['id'])
|
||||
fake_db_volume_connector = db_utils.create_test_volume_connector(
|
||||
@ -350,6 +392,8 @@ class TestRBACProjectScoped(TestACLBase):
|
||||
node_id=leased_node['id'])
|
||||
fake_db_portgroup = db_utils.create_test_portgroup(
|
||||
node_id=leased_node['id'])
|
||||
fake_trait = 'CUSTOM_MEOW'
|
||||
fake_vif_port_id = "0e21d58f-5de2-4956-85ff-33935ea1ca01"
|
||||
|
||||
self.format_data.update({
|
||||
'node_ident': unowned_node['uuid'],
|
||||
@ -359,7 +403,11 @@ class TestRBACProjectScoped(TestACLBase):
|
||||
'volume_target_ident': fake_db_volume_target['uuid'],
|
||||
'volume_connector_ident': fake_db_volume_connector['uuid'],
|
||||
'port_ident': fake_db_port['uuid'],
|
||||
'portgroup_ident': fake_db_portgroup['uuid']})
|
||||
'portgroup_ident': fake_db_portgroup['uuid'],
|
||||
'trait': fake_trait,
|
||||
'vif_ident': fake_vif_port_id,
|
||||
'ind_component': 'component',
|
||||
'ind_ident': 'magic_light'})
|
||||
|
||||
@ddt.file_data('test_rbac_project_scoped.yaml')
|
||||
@ddt.unpack
|
||||
|
@ -11,7 +11,7 @@ values:
|
||||
|
||||
unauthenticated_user_cannot_get_node:
|
||||
path: &node_path '/v1/nodes/{node_uuid}'
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
|
||||
project_admin_can_get_node:
|
||||
path: *node_path
|
||||
@ -24,7 +24,7 @@ project_admin_can_get_node:
|
||||
project_member_cannot_get_node:
|
||||
path: *node_path
|
||||
headers: *project_member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
|
||||
public_api:
|
||||
path: /
|
||||
|
@ -78,7 +78,7 @@ nodes_get_node_member:
|
||||
path: '/v1/nodes/{node_ident}'
|
||||
method: get
|
||||
headers: *member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
nodes_get_node_observer:
|
||||
@ -152,7 +152,7 @@ nodes_node_ident_get_member:
|
||||
path: '/v1/nodes/{node_ident}'
|
||||
method: get
|
||||
headers: *member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
nodes_node_ident_get_observer:
|
||||
@ -178,7 +178,7 @@ nodes_node_ident_patch_member:
|
||||
method: patch
|
||||
headers: *member_headers
|
||||
body: *extra_patch
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
nodes_node_ident_patch_observer:
|
||||
@ -200,7 +200,7 @@ nodes_node_ident_delete_member:
|
||||
path: '/v1/nodes/{node_ident}'
|
||||
method: delete
|
||||
headers: *member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
nodes_node_ident_delete_observer:
|
||||
@ -223,7 +223,7 @@ nodes_validate_get_member:
|
||||
path: '/v1/nodes/{node_ident}/validate'
|
||||
method: get
|
||||
headers: *member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
nodes_validate_get_observer:
|
||||
@ -244,7 +244,7 @@ nodes_maintenance_put_member:
|
||||
path: '/v1/nodes/{node_ident}/maintenance'
|
||||
method: put
|
||||
headers: *member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
nodes_maintenance_put_observer:
|
||||
@ -265,7 +265,7 @@ nodes_maintenance_delete_member:
|
||||
path: '/v1/nodes/{node_ident}/maintenance'
|
||||
method: delete
|
||||
headers: *member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
nodes_maintenance_delete_observer:
|
||||
@ -289,7 +289,7 @@ nodes_management_boot_device_put_member:
|
||||
method: put
|
||||
headers: *member_headers
|
||||
body: *boot_device_body
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
nodes_management_boot_device_put_observer:
|
||||
@ -311,7 +311,7 @@ nodes_management_boot_device_get_member:
|
||||
path: '/v1/nodes/{node_ident}/management/boot_device'
|
||||
method: get
|
||||
headers: *member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
nodes_management_boot_device_get_observer:
|
||||
@ -332,7 +332,7 @@ nodes_management_boot_device_supported_get_member:
|
||||
path: '/v1/nodes/{node_ident}/management/boot_device/supported'
|
||||
method: get
|
||||
headers: *member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
nodes_management_boot_device_supported_get_observer:
|
||||
@ -355,7 +355,7 @@ nodes_management_inject_nmi_put_member:
|
||||
method: put
|
||||
headers: *member_headers
|
||||
body: {}
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
nodes_management_inject_nmi_put_observer:
|
||||
@ -377,7 +377,7 @@ nodes_states_get_member:
|
||||
path: '/v1/nodes/{node_ident}/states'
|
||||
method: get
|
||||
headers: *member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
nodes_states_get_observer:
|
||||
@ -401,7 +401,7 @@ nodes_states_power_put_member:
|
||||
method: put
|
||||
headers: *member_headers
|
||||
body: *power_body
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
nodes_states_power_put_observer:
|
||||
@ -426,7 +426,7 @@ nodes_states_provision_put_member:
|
||||
method: put
|
||||
headers: *member_headers
|
||||
body: *provision_body
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
nodes_states_provision_put_observer:
|
||||
@ -455,7 +455,7 @@ nodes_states_raid_put_member:
|
||||
method: put
|
||||
headers: *member_headers
|
||||
body: *raid_body
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
nodes_states_raid_put_observer:
|
||||
@ -477,7 +477,7 @@ nodes_states_console_get_member:
|
||||
path: '/v1/nodes/{node_ident}/states/console'
|
||||
method: get
|
||||
headers: *member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
nodes_states_console_get_admin:
|
||||
@ -501,7 +501,7 @@ nodes_states_console_put_member:
|
||||
method: put
|
||||
headers: *member_headers
|
||||
body: *console_body_put
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
nodes_states_console_put_observer:
|
||||
@ -526,7 +526,7 @@ nodes_vendor_passthru_methods_get_member:
|
||||
path: '/v1/nodes/{node_ident}/vendor_passthru/methods'
|
||||
method: get
|
||||
headers: *member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
nodes_vendor_passthru_methods_get_observer:
|
||||
@ -547,7 +547,7 @@ nodes_vendor_passthru_get_member:
|
||||
path: '/v1/nodes/{node_ident}/vendor_passthru?method=test'
|
||||
method: get
|
||||
headers: *member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
nodes_vendor_passthru_get_observer:
|
||||
@ -568,7 +568,7 @@ nodes_vendor_passthru_post_member:
|
||||
path: '/v1/nodes/{node_ident}/vendor_passthru?method=test'
|
||||
method: post
|
||||
headers: *member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
nodes_vendor_passthru_post_observer:
|
||||
@ -589,7 +589,7 @@ nodes_vendor_passthru_put_member:
|
||||
path: '/v1/nodes/{node_ident}/vendor_passthru?method=test'
|
||||
method: put
|
||||
headers: *member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
nodes_vendor_passthru_put_observer:
|
||||
@ -610,7 +610,7 @@ nodes_vendor_passthru_delete_member:
|
||||
path: '/v1/nodes/{node_ident}/vendor_passthru?method=test'
|
||||
method: delete
|
||||
headers: *member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
nodes_vendor_passthru_delete_observer:
|
||||
@ -633,7 +633,7 @@ nodes_traits_get_member:
|
||||
path: '/v1/nodes/{node_ident}/traits'
|
||||
method: get
|
||||
headers: *member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
nodes_traits_get_observer:
|
||||
@ -658,7 +658,7 @@ nodes_traits_put_member:
|
||||
path: '/v1/nodes/{node_ident}/traits'
|
||||
method: put
|
||||
headers: *member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
body: *traits_body
|
||||
deprecated: true
|
||||
|
||||
@ -681,7 +681,7 @@ nodes_traits_delete_member:
|
||||
path: '/v1/nodes/{node_ident}/traits/{trait}'
|
||||
method: delete
|
||||
headers: *member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
nodes_traits_delete_observer:
|
||||
@ -702,7 +702,7 @@ nodes_traits_trait_put_member:
|
||||
path: '/v1/nodes/{node_ident}/traits/CUSTOM_TRAIT2'
|
||||
method: put
|
||||
headers: *member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
nodes_traits_trait_put_observer:
|
||||
@ -723,7 +723,7 @@ nodes_traits_trait_delete_member:
|
||||
path: '/v1/nodes/{node_ident}/traits/{trait}'
|
||||
method: delete
|
||||
headers: *member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
nodes_traits_trait_delete_observer:
|
||||
@ -750,7 +750,7 @@ nodes_vifs_get_member:
|
||||
path: '/v1/nodes/{node_ident}/vifs'
|
||||
method: get
|
||||
headers: *member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
nodes_vifs_get_observer:
|
||||
@ -773,7 +773,7 @@ nodes_vifs_post_member:
|
||||
path: '/v1/nodes/{node_ident}/vifs'
|
||||
method: post
|
||||
headers: *member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
body: *vif_body
|
||||
deprecated: true
|
||||
|
||||
@ -797,7 +797,7 @@ nodes_vifs_node_vif_ident_delete_member:
|
||||
path: '/v1/nodes/{node_ident}/vifs/{vif_ident}'
|
||||
method: delete
|
||||
headers: *member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
nodes_vifs_node_vif_ident_delete_observer:
|
||||
@ -820,7 +820,7 @@ nodes_management_indicators_get_member:
|
||||
path: '/v1/nodes/{node_ident}/management/indicators'
|
||||
method: get
|
||||
headers: *member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
nodes_management_indicators_get_observer:
|
||||
@ -1804,7 +1804,7 @@ nodes_bios_get_member:
|
||||
path: '/v1/nodes/{node_ident}/bios'
|
||||
method: get
|
||||
headers: *member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
nodes_bios_get_observer:
|
||||
@ -1825,7 +1825,7 @@ nodes_bios_bios_setting_get_member:
|
||||
path: '/v1/nodes/{node_ident}/bios/{bios_setting}'
|
||||
method: get
|
||||
headers: *member_headers
|
||||
assert_status: 403
|
||||
assert_status: 404
|
||||
deprecated: true
|
||||
|
||||
nodes_bios_bios_setting_get_observer:
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -147,6 +147,26 @@ nodes_node_ident_patch_admin:
|
||||
value: {'test': 'testing'}
|
||||
assert_status: 503
|
||||
|
||||
system_admin_can_patch_chassis:
|
||||
path: '/v1/nodes/{node_ident}'
|
||||
method: patch
|
||||
headers: *admin_headers
|
||||
body:
|
||||
- op: replace
|
||||
path: /chassis_uuid
|
||||
value: 'e74c40e0-d825-11e2-a28f-0800200c9a66'
|
||||
assert_status: 503
|
||||
|
||||
system_member_can_patch_conductor_group:
|
||||
path: '/v1/nodes/{node_ident}'
|
||||
method: patch
|
||||
headers: *scoped_member_headers
|
||||
body:
|
||||
- op: replace
|
||||
path: /conductor_group
|
||||
value: "DC04-ROW39"
|
||||
assert_status: 503
|
||||
|
||||
nodes_node_ident_patch_member:
|
||||
path: '/v1/nodes/{node_ident}'
|
||||
method: patch
|
||||
|
Loading…
Reference in New Issue
Block a user