From f914cb185c40b587ae8ce579eaf2f295c273e43b Mon Sep 17 00:00:00 2001 From: Ghanshyam Maan Date: Sat, 16 Aug 2025 04:11:31 +0000 Subject: [PATCH] Add service role in Nova policy RBAC community wide goal phase-2[1] is to add 'service' role for the service APIs policy rule. This commit defaults the service APIs to 'service' role. This way service APIs will be allowed for service user only. Tempest tests also modified to simulate the service-to-service communication. Tempest tests send the user with service role to nova API. - https://review.opendev.org/c/openstack/tempest/+/892639> Partial implement blueprint policy-service-role-default [1] https://governance.openstack.org/tc/goals/selected/consistent-and-secure-rbac.html#phase-2 Change-Id: I1565ea163fa2c8212f71c9ba375654d2aab28330 Signed-off-by: Ghanshyam Maan --- api-ref/source/index.rst | 13 ++++ api-ref/source/os-volume-attachments-swap.inc | 60 +++++++++++++++++++ api-ref/source/os-volume-attachments.inc | 33 ++-------- api-ref/source/parameters.yaml | 6 ++ doc/source/configuration/policy-concepts.rst | 23 +++++++ .../compute/assisted_volume_snapshots.py | 10 ---- .../compute/server_external_events.py | 5 -- .../openstack/compute/volume_attachments.py | 5 -- nova/policies/assisted_volume_snapshots.py | 18 +----- nova/policies/base.py | 19 +++++- nova/policies/server_external_events.py | 10 +--- nova/policies/volumes_attachments.py | 9 +-- nova/tests/unit/policies/base.py | 10 +++- .../test_assisted_volume_snapshots.py | 8 +-- .../unit/policies/test_availability_zone.py | 3 +- nova/tests/unit/policies/test_extensions.py | 15 +---- .../tests/unit/policies/test_flavor_access.py | 3 +- .../unit/policies/test_flavor_extra_specs.py | 17 ++++-- .../unit/policies/test_floating_ip_pools.py | 14 +---- nova/tests/unit/policies/test_floating_ips.py | 12 ++-- nova/tests/unit/policies/test_keypairs.py | 15 +---- nova/tests/unit/policies/test_limits.py | 6 +- nova/tests/unit/policies/test_networks.py | 6 +- nova/tests/unit/policies/test_quota_sets.py | 11 ++-- .../unit/policies/test_security_groups.py | 12 ++-- .../policies/test_server_external_events.py | 9 +-- .../tests/unit/policies/test_server_groups.py | 17 ++---- nova/tests/unit/policies/test_servers.py | 9 ++- nova/tests/unit/policies/test_snapshots.py | 12 ++-- .../unit/policies/test_tenant_networks.py | 5 +- .../unit/policies/test_volume_attachments.py | 9 +-- nova/tests/unit/policies/test_volumes.py | 10 ++-- nova/tests/unit/test_policy.py | 34 +++++++++-- ...-policy-service-role-eaa391e30431a9d6.yaml | 43 +++++++++++++ 34 files changed, 308 insertions(+), 183 deletions(-) create mode 100644 api-ref/source/os-volume-attachments-swap.inc create mode 100644 releasenotes/notes/add-policy-service-role-eaa391e30431a9d6.yaml diff --git a/api-ref/source/index.rst b/api-ref/source/index.rst index 765c21a339..9a1d9ee714 100644 --- a/api-ref/source/index.rst +++ b/api-ref/source/index.rst @@ -54,6 +54,19 @@ the `API guide `_. .. include:: os-server-external-events.inc .. include:: server-topology.inc +===================== +Internal Service APIs +===================== + +.. warning:: + The below Nova APIs are meant to communicate to OpenStack services. Those + APIs are not supposed to be used by any users because they can make + deployment or resources in unwanted state. + +.. include:: os-assisted-volume-snapshots.inc +.. include:: os-volume-attachments-swap.inc +.. include:: os-server-external-events.inc + =============== Deprecated APIs =============== diff --git a/api-ref/source/os-volume-attachments-swap.inc b/api-ref/source/os-volume-attachments-swap.inc new file mode 100644 index 0000000000..5cca5c9a2a --- /dev/null +++ b/api-ref/source/os-volume-attachments-swap.inc @@ -0,0 +1,60 @@ +.. -*- rst -*- + +.. _os-volume-attachments-swap: + +=============================================================================== +Update ("swapping") Server volume attachments (servers, os-volume\_attachments) +=============================================================================== + +Update ("swapping") the server volume attachments which means swapping +the volume attached to the server. + +Update(swapping) a volume attachment +==================================== + +.. rest_method:: PUT /servers/{server_id}/os-volume_attachments/{volume_id} + +Update a volume attachment. + +.. note:: This action only valid when the server is in ACTIVE, PAUSED and RESIZED state, + or a conflict(409) error will be returned. + +.. Important:: + + When updating volumeId, this API **MUST** only be used + as part of a larger orchestrated volume + migration operation initiated in the block storage + service via the ``os-retype`` or ``os-migrate_volume`` + volume actions. Direct usage of this API is not supported + and will be blocked by nova with a 409 conflict. + Furthermore, updating ``volumeId`` via this API is only + implemented by `certain compute drivers`_. + +.. _certain compute drivers: https://docs.openstack.org/nova/latest/user/support-matrix.html#operation_swap_volume + +Updating, or what is commonly referred to as "swapping", volume attachments +with volumes that have more than one read/write attachment, is not supported. + +Normal response codes: 202 + +Error response codes: badRequest(400), unauthorized(401), forbidden(403), itemNotFound(404), conflict(409) + +Request +------- + +.. rest_parameters:: parameters.yaml + + - server_id: server_id_path + - volume_id: volume_id_swap_src + - volumeAttachment: volumeAttachment_put + - volumeId: volumeId_swap + +**Example Update a volume attachment: JSON request** + +.. literalinclude:: ../../doc/api_samples/os-volume_attachments/update-volume-req.json + :language: javascript + +Response +-------- + +No body is returned on successful request. diff --git a/api-ref/source/os-volume-attachments.inc b/api-ref/source/os-volume-attachments.inc index c20e8121f4..359d4d16e8 100644 --- a/api-ref/source/os-volume-attachments.inc +++ b/api-ref/source/os-volume-attachments.inc @@ -182,31 +182,10 @@ Update a volume attachment Update a volume attachment. -.. note:: This action only valid when the server is in ACTIVE, PAUSED and RESIZED state, - or a conflict(409) error will be returned. - -.. Important:: - - When updating volumeId, this API **MUST** only be used - as part of a larger orchestrated volume - migration operation initiated in the block storage - service via the ``os-retype`` or ``os-migrate_volume`` - volume actions. Direct usage of this API is not supported - and will be blocked by nova with a 409 conflict. - Furthermore, updating ``volumeId`` via this API is only - implemented by `certain compute drivers`_. - -.. _certain compute drivers: https://docs.openstack.org/nova/latest/user/support-matrix.html#operation_swap_volume - -Policy default role is 'rule:system_admin_or_owner', its scope is -[system, project], which allow project members or system admins to -change the fields of an attached volume of a server. Policy defaults -enable only users with the administrative role to change ``volumeId`` -via this operation. Cloud providers can change these permissions -through the ``policy.json`` file. - -Updating, or what is commonly referred to as "swapping", volume attachments -with volumes that have more than one read/write attachment, is not supported. +Policy default role is 'rule:admin_or_owner', its scope is [project], which +allow project members or admins to change the fields of an attached volume of +a server. Cloud providers can change these permissions through the +``policy.yaml`` file. Normal response codes: 202 @@ -218,9 +197,9 @@ Request .. rest_parameters:: parameters.yaml - server_id: server_id_path - - volume_id: volume_id_swap_src + - volume_id: volume_id_path - volumeAttachment: volumeAttachment_put - - volumeId: volumeId_swap + - volumeId: volumeId_update - delete_on_termination: delete_on_termination_put_req - device: attachment_device_put_req - serverId: attachment_server_id_put_req diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml index d9db54680e..c1cf611fa7 100644 --- a/api-ref/source/parameters.yaml +++ b/api-ref/source/parameters.yaml @@ -7628,6 +7628,12 @@ volumeId_swap: in: body required: true type: string +volumeId_update: + description: | + The UUID of the attached volume. + in: body + required: true + type: string volumes: description: | The list of ``volume`` objects. diff --git a/doc/source/configuration/policy-concepts.rst b/doc/source/configuration/policy-concepts.rst index f7cbce4f6e..eeb8cc6a6f 100644 --- a/doc/source/configuration/policy-concepts.rst +++ b/doc/source/configuration/policy-concepts.rst @@ -276,6 +276,25 @@ With these new defaults, you can solve the problem of: to provide access to project level user to perform operations within their project only. +.. rubric:: ``service`` + +The ``service`` role is a special role in Keystone, which is used for the +internal service-to-service communication. It is assigned to service users +i.e. nova or neutron which model the OpenStack services. Nova defaults its +service-to-service APIs to require the ``service`` role so that they cannot +be used by any non-service users. Allowing access to service-to-service APIs +to non-service users can be destructive to resources and leave the deployment +in an invalid state. It's advisable to audit the ``policy.yaml`` files and +keystone users to make sure those APIs are not allowed to any non-service +users and the service role is not granted to human admin accounts. + +.. note:: + + Make sure the configured nova service user in other services has the + ``service`` role otherwise communication from the other services to + Nova will fail. For example, user configured as ``username`` option in + ``neutron.conf`` file under ``[nova]`` section has the ``service`` role. + Nova supported scope & Roles ----------------------------- @@ -308,6 +327,10 @@ overridden in the policy.yaml file but scope is not override-able. Such policy rules are default to most of the read only APIs so that legacy admin can continue to access those APIs. +#. SERVICE_ROLE (Internal): ``service`` role on service users with ``project`` + scope. Such policy rules are default to the service-to-service APIs (The + APIs only meant to be called by the OpenStack services). + Backward Compatibility ---------------------- diff --git a/nova/api/openstack/compute/assisted_volume_snapshots.py b/nova/api/openstack/compute/assisted_volume_snapshots.py index d60fd10ee0..9d57220c3d 100644 --- a/nova/api/openstack/compute/assisted_volume_snapshots.py +++ b/nova/api/openstack/compute/assisted_volume_snapshots.py @@ -41,11 +41,6 @@ class AssistedVolumeSnapshotsController(wsgi.Controller): def create(self, req, body): """Creates a new snapshot.""" context = req.environ['nova.context'] - # NOTE(gmann) We pass empty target to policy enforcement. This API - # is called by cinder which does not have correct project_id. - # By passing the empty target, we make sure that we do not check - # the requester project_id and allow users with - # allowed role to create snapshot. context.can(avs_policies.POLICY_ROOT % 'create', target={}) snapshot = body['snapshot'] @@ -75,11 +70,6 @@ class AssistedVolumeSnapshotsController(wsgi.Controller): def delete(self, req, id): """Delete a snapshot.""" context = req.environ['nova.context'] - # NOTE(gmann) We pass empty target to policy enforcement. This API - # is called by cinder which does not have correct project_id. - # By passing the empty target, we make sure that we do not check - # the requester project_id and allow users with allowed role to - # delete snapshot. context.can(avs_policies.POLICY_ROOT % 'delete', target={}) delete_metadata = {} diff --git a/nova/api/openstack/compute/server_external_events.py b/nova/api/openstack/compute/server_external_events.py index 53d5e1d907..0328ec08fe 100644 --- a/nova/api/openstack/compute/server_external_events.py +++ b/nova/api/openstack/compute/server_external_events.py @@ -80,11 +80,6 @@ class ServerExternalEventsController(wsgi.Controller): def create(self, req, body): """Creates a new instance event.""" context = req.environ['nova.context'] - # NOTE(gmann) We pass empty target to policy enforcement. This API - # is called by neutron which does not have correct project_id where - # server belongs to. By passing the empty target, we make sure that - # we do not check the requester project_id and allow users with - # allowed role to create external event. context.can(see_policies.POLICY_ROOT % 'create', target={}) response_events = [] diff --git a/nova/api/openstack/compute/volume_attachments.py b/nova/api/openstack/compute/volume_attachments.py index 212ecc052b..aa72ea046f 100644 --- a/nova/api/openstack/compute/volume_attachments.py +++ b/nova/api/openstack/compute/volume_attachments.py @@ -329,11 +329,6 @@ class VolumeAttachmentController(wsgi.Controller): # different from the 'id' in the url path, or only swap is allowed by # the microversion, we should check the swap volume policy. # otherwise, check the volume update policy. - # NOTE(gmann) We pass empty target to policy enforcement. This API - # is called by cinder which does not have correct project_id where - # server belongs to. By passing the empty target, we make sure that - # we do not check the requester project_id and allow users with - # allowed role to perform the swap volume. if only_swap or id != volume_id: context.can(va_policies.POLICY_ROOT % 'swap', target={}) else: diff --git a/nova/policies/assisted_volume_snapshots.py b/nova/policies/assisted_volume_snapshots.py index 98a67a8e37..f3e112b75f 100644 --- a/nova/policies/assisted_volume_snapshots.py +++ b/nova/policies/assisted_volume_snapshots.py @@ -24,14 +24,7 @@ POLICY_ROOT = 'os_compute_api:os-assisted-volume-snapshots:%s' assisted_volume_snapshots_policies = [ policy.DocumentedRuleDefault( name=POLICY_ROOT % 'create', - # TODO(gmann): This is internal API policy and called by - # cinder. Add 'service' role in this policy so that cinder - # can call it with user having 'service' role (not having - # correct project_id). That is for phase-2 of RBAC goal and until - # then, we keep it open for all admin in any project. We cannot - # default it to ADMIN which has the project_id in - # check_str and will fail if cinder call it with other project_id. - check_str=base.ADMIN, + check_str=base.SERVICE_ROLE, description="Create an assisted volume snapshot", operations=[ { @@ -42,14 +35,7 @@ assisted_volume_snapshots_policies = [ scope_types=['project']), policy.DocumentedRuleDefault( name=POLICY_ROOT % 'delete', - # TODO(gmann): This is internal API policy and called by - # cinder. Add 'service' role in this policy so that cinder - # can call it with user having 'service' role (not having - # correct project_id). That is for phase-2 of RBAC goal and until - # then, we keep it open for all admin in any project. We cannot - # default it to ADMIN which has the project_id in - # check_str and will fail if cinder call it with other project_id. - check_str=base.ADMIN, + check_str=base.SERVICE_ROLE, description="Delete an assisted volume snapshot", operations=[ { diff --git a/nova/policies/base.py b/nova/policies/base.py index 0d4c3ac658..70673e555b 100644 --- a/nova/policies/base.py +++ b/nova/policies/base.py @@ -41,6 +41,12 @@ ADMIN = 'rule:context_is_admin' PROJECT_MEMBER = 'rule:project_manager_api' PROJECT_MEMBER = 'rule:project_member_api' PROJECT_READER = 'rule:project_reader_api' +# TODO(gmaan): Remove the admin role from the service rule in 2026.2. We are +# continue allowing admin to access the service APIs, otherwise it will break +# deployment where nova service users in other services are not assigned +# 'service' role. After one SLURP (2026.1), we can make service APIs only +# allowed for the 'service' role. +SERVICE_ROLE = 'rule:service_or_admin' PROJECT_MANAGER_OR_ADMIN = 'rule:project_manager_or_admin' PROJECT_MEMBER_OR_ADMIN = 'rule:project_member_or_admin' PROJECT_READER_OR_ADMIN = 'rule:project_reader_or_admin' @@ -106,6 +112,11 @@ rules = [ "role:reader and project_id:%(project_id)s", "Default rule for Project level read only APIs.", deprecated_rule=DEPRECATED_ADMIN_OR_OWNER_POLICY), + policy.RuleDefault( + "service_api", + "role:service", + "Default rule for service-to-service APIs.", + deprecated_rule=DEPRECATED_ADMIN_POLICY), policy.RuleDefault( "project_manager_or_admin", "rule:project_manager_api or rule:context_is_admin", @@ -120,7 +131,13 @@ rules = [ "project_reader_or_admin", "rule:project_reader_api or rule:context_is_admin", "Default rule for Project reader or admin APIs.", - deprecated_rule=DEPRECATED_ADMIN_OR_OWNER_POLICY) + deprecated_rule=DEPRECATED_ADMIN_OR_OWNER_POLICY), + policy.RuleDefault( + "service_or_admin", + "rule:service_api or rule:context_is_admin", + "Default rule for service or admin APIs.", + deprecated_rule=DEPRECATED_ADMIN_POLICY), + ] diff --git a/nova/policies/server_external_events.py b/nova/policies/server_external_events.py index 56034d0186..29d5371abd 100644 --- a/nova/policies/server_external_events.py +++ b/nova/policies/server_external_events.py @@ -24,15 +24,7 @@ POLICY_ROOT = 'os_compute_api:os-server-external-events:%s' server_external_events_policies = [ policy.DocumentedRuleDefault( name=POLICY_ROOT % 'create', - # TODO(gmann): This is internal API policy and supposed to be called - # by neutron, cinder, ironic, and cyborg (may be other openstack - # services in future). Add 'service' role in this policy so that - # neutron can call it with user having 'service' role (not having - # server's project_id). That is for phase-2 of RBAC goal and until - # then, we keep it open for all admin in any project. We cannot - # default it to ADMIN which has the project_id in - # check_str and will fail if neutron call it with other project_id. - check_str=base.ADMIN, + check_str=base.SERVICE_ROLE, description="Create one or more external events", operations=[ { diff --git a/nova/policies/volumes_attachments.py b/nova/policies/volumes_attachments.py index 68a1694c59..071a5d6eb1 100644 --- a/nova/policies/volumes_attachments.py +++ b/nova/policies/volumes_attachments.py @@ -73,14 +73,7 @@ always superset of this policy permission. scope_types=['project']), policy.DocumentedRuleDefault( name=POLICY_ROOT % 'swap', - # TODO(gmann): This is internal API policy and supposed to be called - # only by cinder. Add 'service' role in this policy so that cinder - # can call it with user having 'service' role (not having server's - # project_id). That is for phase-2 of RBAC goal and until then, - # we keep it open for all admin in any project. We cannot default it to - # ADMIN which has the project_id in check_str and will fail - # if cinder call it with other project_id. - check_str=base.ADMIN, + check_str=base.SERVICE_ROLE, description="Update a volume attachment with a different volumeId", operations=[ { diff --git a/nova/tests/unit/policies/base.py b/nova/tests/unit/policies/base.py index 2d1c0d68e7..8d4aa03142 100644 --- a/nova/tests/unit/policies/base.py +++ b/nova/tests/unit/policies/base.py @@ -132,6 +132,12 @@ class BasePolicyTest(test.TestCase): project_id=self.project_id_other, roles=['reader']) + # service user + self.service_context = nova_context.RequestContext( + user_id="service_user", + project_id="service_user_project_id", + roles=['service']) + self.all_contexts = set([ self.legacy_admin_context, self.system_admin_context, self.system_member_context, self.system_reader_context, @@ -140,7 +146,8 @@ class BasePolicyTest(test.TestCase): self.project_member_context, self.project_reader_context, self.other_project_manager_context, self.other_project_member_context, - self.project_foo_context, self.other_project_reader_context + self.project_foo_context, self.other_project_reader_context, + self.service_context ]) # All the project contexts for easy access. @@ -238,6 +245,7 @@ class BasePolicyTest(test.TestCase): "rule:project_member_api or rule:context_is_admin", "project_reader_or_admin": "rule:project_reader_api or rule:context_is_admin", + "service_api": "role:service", }) self.policy.set_rules(self.rules_without_deprecation, overwrite=False) diff --git a/nova/tests/unit/policies/test_assisted_volume_snapshots.py b/nova/tests/unit/policies/test_assisted_volume_snapshots.py index 1474702e7b..7afd69f644 100644 --- a/nova/tests/unit/policies/test_assisted_volume_snapshots.py +++ b/nova/tests/unit/policies/test_assisted_volume_snapshots.py @@ -34,11 +34,10 @@ class AssistedVolumeSnapshotPolicyTest(base.BasePolicyTest): self.controller = snapshots.AssistedVolumeSnapshotsController() self.req = fakes.HTTPRequest.blank('') # By default, legacy rule are enable and scope check is disabled. - # system admin, legacy admin, and project admin is able to - # take volume snapshot. + # admin and service user is able to manage volume snapshot. self.project_admin_authorized_contexts = [ self.legacy_admin_context, self.system_admin_context, - self.project_admin_context] + self.project_admin_context, self.service_context] @mock.patch('nova.compute.api.API.volume_snapshot_create') def test_assisted_create_policy(self, mock_create): @@ -98,7 +97,8 @@ class AssistedSnapshotScopeTypePolicyTest(AssistedVolumeSnapshotPolicyTest): # With scope check enabled, system admin is not able to # take volume snapshot. self.project_admin_authorized_contexts = [ - self.legacy_admin_context, self.project_admin_context] + self.legacy_admin_context, self.project_admin_context, + self.service_context] class AssistedSnapshotScopeTypeNoLegacyPolicyTest( diff --git a/nova/tests/unit/policies/test_availability_zone.py b/nova/tests/unit/policies/test_availability_zone.py index 1852f8444c..4a24f5cea9 100644 --- a/nova/tests/unit/policies/test_availability_zone.py +++ b/nova/tests/unit/policies/test_availability_zone.py @@ -84,7 +84,8 @@ class AvailabilityZoneScopeTypePolicyTest(AvailabilityZonePolicyTest): # able to get AZ with host information. self.project_admin_authorized_contexts = [self.legacy_admin_context, self.project_admin_context] - self.project_authorized_contexts = self.all_project_contexts + self.project_authorized_contexts = (self.all_project_contexts | set([ + self.service_context])) class AZScopeTypeNoLegacyPolicyTest(AvailabilityZoneScopeTypePolicyTest): diff --git a/nova/tests/unit/policies/test_extensions.py b/nova/tests/unit/policies/test_extensions.py index 565e410acf..c345edc3fb 100644 --- a/nova/tests/unit/policies/test_extensions.py +++ b/nova/tests/unit/policies/test_extensions.py @@ -30,17 +30,7 @@ class ExtensionsPolicyTest(base.BasePolicyTest): self.req = fakes.HTTPRequest.blank('') # Check that everyone is able to get extension info. - self.everyone_authorized_contexts = [ - self.legacy_admin_context, self.system_admin_context, - self.project_admin_context, self.project_manager_context, - self.project_member_context, self.project_reader_context, - self.project_foo_context, - self.other_project_reader_context, - self.system_member_context, self.system_reader_context, - self.system_foo_context, - self.other_project_manager_context, - self.other_project_member_context - ] + self.everyone_authorized_contexts = self.all_contexts self.everyone_unauthorized_contexts = [] def test_list_extensions_policy(self): @@ -80,7 +70,8 @@ class ExtensionsScopeTypePolicyTest(ExtensionsPolicyTest): self.project_foo_context, self.other_project_manager_context, self.other_project_reader_context, - self.other_project_member_context + self.other_project_member_context, + self.service_context ] self.everyone_unauthorized_contexts = [ self.system_admin_context, self.system_member_context, diff --git a/nova/tests/unit/policies/test_flavor_access.py b/nova/tests/unit/policies/test_flavor_access.py index cfdbbd2470..e7f967f0fc 100644 --- a/nova/tests/unit/policies/test_flavor_access.py +++ b/nova/tests/unit/policies/test_flavor_access.py @@ -126,7 +126,8 @@ class FlavorAccessScopeTypePolicyTest(FlavorAccessPolicyTest): self.admin_authorized_contexts = [ self.legacy_admin_context, self.project_admin_context] - self.admin_index_authorized_contexts = self.all_project_contexts + self.admin_index_authorized_contexts = (self.all_project_contexts | + set([self.service_context])) class FlavorAccessScopeTypeNoLegacyPolicyTest(FlavorAccessScopeTypePolicyTest): diff --git a/nova/tests/unit/policies/test_flavor_extra_specs.py b/nova/tests/unit/policies/test_flavor_extra_specs.py index 7c3efccdc3..514629bc92 100644 --- a/nova/tests/unit/policies/test_flavor_extra_specs.py +++ b/nova/tests/unit/policies/test_flavor_extra_specs.py @@ -51,13 +51,15 @@ class FlavorExtraSpecsPolicyTest(base.BasePolicyTest): # In the base/legacy case, all project and system contexts are # authorized in the "anyone" case. self.all_authorized_contexts = (self.all_project_contexts | - self.all_system_contexts) + self.all_system_contexts | + set([self.service_context])) # In the base/legacy case, all project and system contexts are # authorized in the case of things that distinguish between # scopes, since scope checking is disabled. self.all_project_authorized_contexts = (self.all_project_contexts | - self.all_system_contexts) + self.all_system_contexts | + set([self.service_context])) # In the base/legacy case, any admin is an admin. self.admin_authorized_contexts = set([self.project_admin_context, @@ -211,8 +213,10 @@ class FlavorExtraSpecsScopeTypePolicyTest(FlavorExtraSpecsPolicyTest): self.flags(enforce_scope=True, group="oslo_policy") # Only project users are authorized - self.reduce_set('all_project_authorized', self.all_project_contexts) - self.reduce_set('all_authorized', self.all_project_contexts) + self.reduce_set('all_project_authorized', + self.all_project_contexts | set([self.service_context])) + self.reduce_set('all_authorized', + self.all_project_contexts | set([self.service_context])) # Only admins can do admin things self.admin_authorized_contexts = [self.legacy_admin_context, @@ -254,9 +258,10 @@ class FlavorExtraSpecsNoLegacyPolicyTest(FlavorExtraSpecsScopeTypePolicyTest): # contexts stay separate. self.reduce_set( 'all_project_authorized', - self.all_project_contexts - set([self.project_foo_context])) + self.all_project_contexts - set([self.project_foo_context, + self.service_context])) everything_but_foo_and_system = ( self.all_contexts - set([ - self.project_foo_context, + self.project_foo_context, self.service_context, ]) - self.all_system_contexts) self.reduce_set('all_authorized', everything_but_foo_and_system) diff --git a/nova/tests/unit/policies/test_floating_ip_pools.py b/nova/tests/unit/policies/test_floating_ip_pools.py index 5360a86eaf..e6c8083ed4 100644 --- a/nova/tests/unit/policies/test_floating_ip_pools.py +++ b/nova/tests/unit/policies/test_floating_ip_pools.py @@ -32,16 +32,7 @@ class FloatingIPPoolsPolicyTest(base.BasePolicyTest): self.req = fakes.HTTPRequest.blank('') # Check that everyone is able to list FIP pools. - self.everyone_authorized_contexts = set([ - self.legacy_admin_context, self.system_admin_context, - self.project_admin_context, self.project_manager_context, - self.project_member_context, self.project_reader_context, - self.project_foo_context, - self.other_project_manager_context, - self.other_project_reader_context, - self.other_project_member_context, - self.system_member_context, self.system_reader_context, - self.system_foo_context]) + self.everyone_authorized_contexts = self.all_contexts self.everyone_unauthorized_contexts = set([]) @mock.patch('nova.network.neutron.API.get_floating_ip_pools') @@ -68,7 +59,8 @@ class FloatingIPPoolsScopeTypePolicyTest(FloatingIPPoolsPolicyTest): super(FloatingIPPoolsScopeTypePolicyTest, self).setUp() self.flags(enforce_scope=True, group="oslo_policy") - self.reduce_set('everyone_authorized', self.all_project_contexts) + self.reduce_set('everyone_authorized', self.all_project_contexts | + set([self.service_context])) self.everyone_unauthorized_contexts = ( self.all_contexts - self.everyone_authorized_contexts) diff --git a/nova/tests/unit/policies/test_floating_ips.py b/nova/tests/unit/policies/test_floating_ips.py index b90fa37d84..1ed4ceaa18 100644 --- a/nova/tests/unit/policies/test_floating_ips.py +++ b/nova/tests/unit/policies/test_floating_ips.py @@ -64,7 +64,8 @@ class FloatingIPPolicyTest(base.BasePolicyTest): self.system_member_context, self.system_reader_context, self.system_foo_context, self.other_project_manager_context, - self.other_project_member_context + self.other_project_member_context, + self.service_context ] self.project_reader_authorized_contexts = [ self.legacy_admin_context, self.system_admin_context, @@ -75,7 +76,8 @@ class FloatingIPPolicyTest(base.BasePolicyTest): self.system_member_context, self.system_reader_context, self.system_foo_context, self.other_project_manager_context, - self.other_project_member_context + self.other_project_member_context, + self.service_context ] # With legacy rule and no scope checks, all admin, project members # project reader or other project role(because legacy rule allow server @@ -218,7 +220,8 @@ class FloatingIPScopeTypePolicyTest(FloatingIPPolicyTest): self.project_reader_context, self.project_foo_context, self.other_project_manager_context, self.other_project_reader_context, - self.other_project_member_context + self.other_project_member_context, + self.service_context ] self.project_reader_authorized_contexts = [ self.legacy_admin_context, self.project_admin_context, @@ -226,7 +229,8 @@ class FloatingIPScopeTypePolicyTest(FloatingIPPolicyTest): self.project_reader_context, self.project_foo_context, self.other_project_manager_context, self.other_project_reader_context, - self.other_project_member_context + self.other_project_member_context, + self.service_context ] diff --git a/nova/tests/unit/policies/test_keypairs.py b/nova/tests/unit/policies/test_keypairs.py index c3fb201768..48d054bbb5 100644 --- a/nova/tests/unit/policies/test_keypairs.py +++ b/nova/tests/unit/policies/test_keypairs.py @@ -53,17 +53,7 @@ class KeypairsPolicyTest(base.BasePolicyTest): # Check that everyone is able to create, delete and get # their keypairs. - self.everyone_authorized_contexts = set([ - self.legacy_admin_context, self.system_admin_context, - self.project_admin_context, - self.system_member_context, self.system_reader_context, - self.system_foo_context, self.project_manager_context, - self.project_member_context, self.project_reader_context, - self.project_foo_context, - self.other_project_manager_context, - self.other_project_member_context, - self.other_project_reader_context, - ]) + self.everyone_authorized_contexts = self.all_contexts # Check that admin is able to create, delete and get # other users keypairs. @@ -177,7 +167,8 @@ class KeypairsScopeTypePolicyTest(KeypairsPolicyTest): self.flags(enforce_scope=True, group="oslo_policy") # With scope checking, only project-scoped users are allowed - self.reduce_set('everyone_authorized', self.all_project_contexts) + self.reduce_set('everyone_authorized', self.all_project_contexts | + set([self.service_context])) self.admin_authorized_contexts = [ self.legacy_admin_context, self.project_admin_context] diff --git a/nova/tests/unit/policies/test_limits.py b/nova/tests/unit/policies/test_limits.py index 3905dc03d6..c7951a552a 100644 --- a/nova/tests/unit/policies/test_limits.py +++ b/nova/tests/unit/policies/test_limits.py @@ -131,7 +131,8 @@ class LimitsScopeTypePolicyTest(LimitsPolicyTest): self.project_reader_context, self.other_project_manager_context, self.other_project_member_context, - self.project_foo_context, self.other_project_reader_context + self.project_foo_context, self.other_project_reader_context, + self.service_context, ] @@ -157,5 +158,6 @@ class LimitsScopeTypeNoLegacyPolicyTest(LimitsScopeTypePolicyTest): self.project_reader_context, self.other_project_manager_context, self.other_project_member_context, - self.project_foo_context, self.other_project_reader_context + self.project_foo_context, self.other_project_reader_context, + self.service_context, ] diff --git a/nova/tests/unit/policies/test_networks.py b/nova/tests/unit/policies/test_networks.py index cf3181dc8a..c64d46f9ff 100644 --- a/nova/tests/unit/policies/test_networks.py +++ b/nova/tests/unit/policies/test_networks.py @@ -48,7 +48,8 @@ class NetworksPolicyTest(base.BasePolicyTest): self.system_member_context, self.system_reader_context, self.system_foo_context, self.other_project_manager_context, - self.other_project_member_context + self.other_project_member_context, + self.service_context ] @mock.patch('nova.network.neutron.API.get_all') @@ -116,7 +117,8 @@ class NetworksScopeTypePolicyTest(NetworksPolicyTest): self.project_reader_context, self.project_foo_context, self.other_project_manager_context, self.other_project_reader_context, - self.other_project_member_context + self.other_project_member_context, + self.service_context ] diff --git a/nova/tests/unit/policies/test_quota_sets.py b/nova/tests/unit/policies/test_quota_sets.py index d950f523f8..84a3cf0b1b 100644 --- a/nova/tests/unit/policies/test_quota_sets.py +++ b/nova/tests/unit/policies/test_quota_sets.py @@ -49,7 +49,8 @@ class QuotaSetsPolicyTest(base.BasePolicyTest): self.project_foo_context, self.other_project_manager_context, self.other_project_member_context, - self.other_project_reader_context]) + self.other_project_reader_context, + self.service_context]) # Everyone is able to get the default quota self.everyone_authorized_contexts = set([ self.legacy_admin_context, self.system_admin_context, @@ -60,7 +61,7 @@ class QuotaSetsPolicyTest(base.BasePolicyTest): self.project_foo_context, self.other_project_manager_context, self.other_project_member_context, - self.other_project_reader_context]) + self.other_project_reader_context, self.service_context]) @mock.patch('nova.quota.QUOTAS.get_project_quotas') @mock.patch('nova.quota.QUOTAS.get_settable_quotas') @@ -185,8 +186,10 @@ class QuotaSetsScopeTypePolicyTest(QuotaSetsPolicyTest): self.legacy_admin_context, self.project_admin_context])) self.reduce_set('project_reader_authorized', - self.all_project_contexts) - self.everyone_authorized_contexts = self.all_project_contexts + self.all_project_contexts | set([ + self.service_context])) + self.everyone_authorized_contexts = (self.all_project_contexts | set([ + self.service_context])) class QuotaSetsScopeTypeNoLegacyPolicyTest(QuotaSetsScopeTypePolicyTest): diff --git a/nova/tests/unit/policies/test_security_groups.py b/nova/tests/unit/policies/test_security_groups.py index 086daf4f69..990794f2fd 100644 --- a/nova/tests/unit/policies/test_security_groups.py +++ b/nova/tests/unit/policies/test_security_groups.py @@ -152,7 +152,8 @@ class SecurityGroupsPolicyTest(base.BasePolicyTest): self.system_member_context, self.system_reader_context, self.system_foo_context, self.other_project_manager_context, - self.other_project_member_context + self.other_project_member_context, + self.service_context ] self.project_reader_authorized_contexts = [ self.legacy_admin_context, self.system_admin_context, @@ -163,7 +164,8 @@ class SecurityGroupsPolicyTest(base.BasePolicyTest): self.system_member_context, self.system_reader_context, self.system_foo_context, self.other_project_manager_context, - self.other_project_member_context + self.other_project_member_context, + self.service_context ] @mock.patch('nova.network.security_group_api.list') @@ -304,7 +306,8 @@ class SecurityGroupsScopeTypePolicyTest(SecurityGroupsPolicyTest): self.project_reader_context, self.project_foo_context, self.other_project_reader_context, self.other_project_manager_context, - self.other_project_member_context + self.other_project_member_context, + self.service_context ] self.project_reader_authorized_contexts = [ self.legacy_admin_context, self.project_admin_context, @@ -312,7 +315,8 @@ class SecurityGroupsScopeTypePolicyTest(SecurityGroupsPolicyTest): self.project_reader_context, self.project_foo_context, self.other_project_reader_context, self.other_project_manager_context, - self.other_project_member_context + self.other_project_member_context, + self.service_context ] diff --git a/nova/tests/unit/policies/test_server_external_events.py b/nova/tests/unit/policies/test_server_external_events.py index 401b55325f..a2441d2fe7 100644 --- a/nova/tests/unit/policies/test_server_external_events.py +++ b/nova/tests/unit/policies/test_server_external_events.py @@ -34,11 +34,11 @@ class ServerExternalEventsPolicyTest(base.BasePolicyTest): self.controller = ev.ServerExternalEventsController() self.req = fakes.HTTPRequest.blank('') - # With legacy rule and no scope checks, all admin can - # create the server external events. + # With legacy rule and no scope checks, all admin and service user + # can create the server external events. self.project_admin_authorized_contexts = [ self.legacy_admin_context, self.system_admin_context, - self.project_admin_context + self.project_admin_context, self.service_context ] @mock.patch('nova.compute.api.API.external_instance_event') @@ -82,7 +82,8 @@ class ServerExternalEventsScopeTypePolicyTest(ServerExternalEventsPolicyTest): # With scope checks, system admin is not allowed. self.project_admin_authorized_contexts = [ - self.legacy_admin_context, self.project_admin_context] + self.legacy_admin_context, self.project_admin_context, + self.service_context] class ServerExternalEventsScopeTypeNoLegacyPolicyTest( diff --git a/nova/tests/unit/policies/test_server_groups.py b/nova/tests/unit/policies/test_server_groups.py index b712dd6d4d..0c55f3336b 100644 --- a/nova/tests/unit/policies/test_server_groups.py +++ b/nova/tests/unit/policies/test_server_groups.py @@ -83,7 +83,8 @@ class ServerGroupPolicyTest(base.BasePolicyTest): self.system_member_context, self.system_reader_context, self.system_foo_context, self.other_project_manager_context, - self.other_project_member_context + self.other_project_member_context, + self.service_context ] # With legacy rule, anyone can create SG. @@ -222,19 +223,13 @@ class ServerGroupScopeTypePolicyTest(ServerGroupPolicyTest): self.project_reader_context, self.project_foo_context, self.other_project_reader_context, self.other_project_member_context, - self.other_project_manager_context] + self.other_project_manager_context, + self.service_context] self.project_admin_authorized_contexts = [ self.legacy_admin_context, self.project_admin_context] - - self.everyone_authorized_contexts = [ - self.legacy_admin_context, self.project_admin_context, - self.project_manager_context, self.project_member_context, - self.project_reader_context, self.project_foo_context, - self.other_project_manager_context, - self.other_project_reader_context, - self.other_project_member_context - ] + self.everyone_authorized_contexts = ( + self.project_create_authorized_contexts) class ServerGroupScopeTypeNoLegacyPolicyTest(ServerGroupScopeTypePolicyTest): diff --git a/nova/tests/unit/policies/test_servers.py b/nova/tests/unit/policies/test_servers.py index 1167a60935..eb1bc8d847 100644 --- a/nova/tests/unit/policies/test_servers.py +++ b/nova/tests/unit/policies/test_servers.py @@ -1367,7 +1367,8 @@ class ServersNoLegacyNoScopeTest(ServersPolicyTest): # see everything in their project. self.reduce_set('everyone_authorized', self.all_contexts - set([self.project_foo_context, - self.system_foo_context])) + self.system_foo_context, + self.service_context])) # Disabling legacy support means readers and random roles lose # power to create things on their own projects. Note that @@ -1381,7 +1382,8 @@ class ServersNoLegacyNoScopeTest(ServersPolicyTest): self.system_foo_context, self.project_reader_context, self.project_foo_context, - self.other_project_reader_context])) + self.other_project_reader_context, + self.service_context])) class ServersScopeTypePolicyTest(ServersPolicyTest): @@ -1428,7 +1430,8 @@ class ServersScopeTypePolicyTest(ServersPolicyTest): # With scope checking enabled, system users no longer have # project access, even to create their own resources. - self.reduce_set('project_member_authorized', self.all_project_contexts) + self.reduce_set('project_member_authorized', + self.all_project_contexts | set([self.service_context])) # With scope checking enabled, system admin is no longer an # admin of project resources. diff --git a/nova/tests/unit/policies/test_snapshots.py b/nova/tests/unit/policies/test_snapshots.py index bf6469455d..9e61b0f696 100644 --- a/nova/tests/unit/policies/test_snapshots.py +++ b/nova/tests/unit/policies/test_snapshots.py @@ -61,7 +61,8 @@ class SnapshotsPolicyTest(base.BasePolicyTest): self.system_member_context, self.system_reader_context, self.system_foo_context, self.other_project_manager_context, - self.other_project_member_context + self.other_project_member_context, + self.service_context ] self.project_reader_authorized_contexts = [ self.legacy_admin_context, self.system_admin_context, @@ -72,7 +73,8 @@ class SnapshotsPolicyTest(base.BasePolicyTest): self.system_member_context, self.system_reader_context, self.system_foo_context, self.other_project_manager_context, - self.other_project_member_context + self.other_project_member_context, + self.service_context ] @mock.patch('nova.volume.cinder.API.get_all_snapshots') @@ -191,7 +193,8 @@ class SnapshotsScopeTypePolicyTest(SnapshotsPolicyTest): self.project_reader_context, self.project_foo_context, self.other_project_reader_context, self.other_project_manager_context, - self.other_project_member_context + self.other_project_member_context, + self.service_context ] self.project_reader_authorized_contexts = [ self.legacy_admin_context, self.project_admin_context, @@ -200,7 +203,8 @@ class SnapshotsScopeTypePolicyTest(SnapshotsPolicyTest): self.project_reader_context, self.project_foo_context, self.other_project_reader_context, self.other_project_manager_context, - self.other_project_member_context + self.other_project_member_context, + self.service_context ] diff --git a/nova/tests/unit/policies/test_tenant_networks.py b/nova/tests/unit/policies/test_tenant_networks.py index c98b7a39b1..56ce126b1a 100644 --- a/nova/tests/unit/policies/test_tenant_networks.py +++ b/nova/tests/unit/policies/test_tenant_networks.py @@ -48,7 +48,8 @@ class TenantNetworksPolicyTest(base.BasePolicyTest): self.system_member_context, self.system_reader_context, self.system_foo_context, self.other_project_manager_context, - self.other_project_member_context + self.other_project_member_context, + self.service_context ] @mock.patch('nova.network.neutron.API.get_all') @@ -113,7 +114,7 @@ class TenantNetworksScopeTypePolicyTest(TenantNetworksPolicyTest): self.project_reader_context, self.project_foo_context, self.other_project_reader_context, self.other_project_manager_context, - self.other_project_member_context + self.other_project_member_context, self.service_context ] diff --git a/nova/tests/unit/policies/test_volume_attachments.py b/nova/tests/unit/policies/test_volume_attachments.py index 38d69e4558..589d7aa9ba 100644 --- a/nova/tests/unit/policies/test_volume_attachments.py +++ b/nova/tests/unit/policies/test_volume_attachments.py @@ -119,11 +119,11 @@ class VolumeAttachPolicyTest(base.BasePolicyTest): self.project_member_authorized_contexts) # By default, legacy rule are enable and scope check is disabled. - # system admin, legacy admin, and project admin is able to update - # volume attachment with a different volumeId. + # system admin, legacy admin, project admin, and service user is able + # to update volume attachment with a different volumeId. self.project_admin_authorized_contexts = [ self.legacy_admin_context, self.system_admin_context, - self.project_admin_context] + self.project_admin_context, self.service_context] @mock.patch.object(objects.BlockDeviceMappingList, 'get_by_instance_uuid') def test_index_volume_attach_policy(self, mock_get_instance): @@ -256,7 +256,8 @@ class VolumeAttachScopeTypePolicyTest(VolumeAttachPolicyTest): self.project_m_r_or_admin_with_scope_and_legacy) self.project_admin_authorized_contexts = [ - self.legacy_admin_context, self.project_admin_context] + self.legacy_admin_context, self.project_admin_context, + self.service_context] class VolumeAttachScopeTypeNoLegacyPolicyTest(VolumeAttachScopeTypePolicyTest): diff --git a/nova/tests/unit/policies/test_volumes.py b/nova/tests/unit/policies/test_volumes.py index fccf3032e5..32d9c7ad1b 100644 --- a/nova/tests/unit/policies/test_volumes.py +++ b/nova/tests/unit/policies/test_volumes.py @@ -51,7 +51,8 @@ class VolumesPolicyTest(base.BasePolicyTest): self.system_member_context, self.system_reader_context, self.system_foo_context, self.other_project_manager_context, - self.other_project_member_context + self.other_project_member_context, + self.service_context ] self.project_reader_authorized_contexts = [ self.legacy_admin_context, self.system_admin_context, @@ -62,7 +63,8 @@ class VolumesPolicyTest(base.BasePolicyTest): self.system_member_context, self.system_reader_context, self.system_foo_context, self.other_project_manager_context, - self.other_project_member_context + self.other_project_member_context, + self.service_context ] @mock.patch('nova.volume.cinder.API.get_all') @@ -204,7 +206,7 @@ class VolumesScopeTypePolicyTest(VolumesPolicyTest): self.project_reader_context, self.project_foo_context, self.other_project_reader_context, self.other_project_manager_context, - self.other_project_member_context + self.other_project_member_context, self.service_context ] self.project_reader_authorized_contexts = [ self.legacy_admin_context, self.project_admin_context, @@ -213,7 +215,7 @@ class VolumesScopeTypePolicyTest(VolumesPolicyTest): self.project_reader_context, self.project_foo_context, self.other_project_reader_context, self.other_project_manager_context, - self.other_project_member_context + self.other_project_member_context, self.service_context ] diff --git a/nova/tests/unit/test_policy.py b/nova/tests/unit/test_policy.py index b74b969403..03873bfce0 100644 --- a/nova/tests/unit/test_policy.py +++ b/nova/tests/unit/test_policy.py @@ -308,6 +308,11 @@ class RealRolePolicyTestCase(test.NoDBTestCase): self.admin_context = context.RequestContext( 'fake', 'fake', True, roles=[ 'admin', 'manager', 'member', 'reader']) + self.service_context = context.RequestContext( + user_id="service_user", + project_id="service_user_project_id", + roles=['service']) + self.target = {} self.fake_policy = jsonutils.loads(fake_policy.policy_data) @@ -359,12 +364,8 @@ class RealRolePolicyTestCase(test.NoDBTestCase): "os_compute_api:os-shelve:shelve_offload", "os_compute_api:os-shelve:unshelve_to_host", "os_compute_api:os-availability-zone:detail", - "os_compute_api:os-assisted-volume-snapshots:create", - "os_compute_api:os-assisted-volume-snapshots:delete", "os_compute_api:os-console-auth-tokens", "os_compute_api:os-quota-class-sets:update", - "os_compute_api:os-server-external-events:create", - "os_compute_api:os-volumes-attachments:swap", "os_compute_api:servers:create:zero_disk_flavor", "os_compute_api:os-baremetal-nodes:list", "os_compute_api:os-baremetal-nodes:show", @@ -525,6 +526,13 @@ class RealRolePolicyTestCase(test.NoDBTestCase): servers_policy.CROSS_CELL_RESIZE, ) + self.service_rules = ( + "os_compute_api:os-assisted-volume-snapshots:create", + "os_compute_api:os-assisted-volume-snapshots:delete", + "os_compute_api:os-server-external-events:create", + "os_compute_api:os-volumes-attachments:swap", + ) + def test_all_rules_in_sample_file(self): special_rules = ["context_is_admin", "admin_or_owner", "default"] for (name, rule) in self.fake_policy.items(): @@ -556,6 +564,18 @@ class RealRolePolicyTestCase(test.NoDBTestCase): self.assertRaises(exception.PolicyNotAuthorized, policy.authorize, self.admin_context, rule, self.target) + def test_service_only_rules(self): + for rule in self.service_rules: + self.assertRaises(exception.PolicyNotAuthorized, policy.authorize, + self.non_admin_context, rule, + {'project_id': 'fake', 'user_id': 'fake'}) + # TODO(gmaan): For backward compatibility, we are allowing admin + # user to access service only rules, but once we remove that + # access, we need to assert here that the admin cannot access the + # service only rules. + policy.authorize(self.admin_context, rule) + policy.authorize(self.service_context, rule) + def test_rule_missing(self): rules = policy.get_rules() # eliqiao os_compute_api:os-quota-class-sets:show requires @@ -567,9 +587,11 @@ class RealRolePolicyTestCase(test.NoDBTestCase): 'project_member_api', 'project_reader_api', 'project_manager_or_admin', 'project_member_or_admin', - 'project_reader_or_admin') + 'project_reader_or_admin', 'service_api', + 'service_or_admin') result = set(rules.keys()) - set(self.admin_only_rules + self.admin_or_owner_rules + self.allow_all_rules + - self.allow_nobody_rules + special_rules) + self.allow_nobody_rules + special_rules + + self.service_rules) self.assertEqual(set([]), result) diff --git a/releasenotes/notes/add-policy-service-role-eaa391e30431a9d6.yaml b/releasenotes/notes/add-policy-service-role-eaa391e30431a9d6.yaml new file mode 100644 index 0000000000..fc6ea3893a --- /dev/null +++ b/releasenotes/notes/add-policy-service-role-eaa391e30431a9d6.yaml @@ -0,0 +1,43 @@ +--- +features: + - | + A few of the Nova APIs are meant only for use by other Openstack services. + Those APIs are not supposed to be used by any non-service users (even + admins) because they can make deployment or resources in unwanted state. + To restrict the usage of those APIs by users, Nova now defaults those APIs + to a policy rule of the ``service`` role. This will make sure they are + allowed to be used by the OpenStack services only. +upgrade: + - | + Nova changed the default access for the service-to-service APIs which are + meant to be used by the OpenStack services only and not by any users. + The below service-to-service APIs access default to the ``service`` role: + + * os_compute_api:os-assisted-volume-snapshots:create + * os_compute_api:os-assisted-volume-snapshots:delete + * os_compute_api:os-server-external-events:create + * os_compute_api:os-volumes-attachments:swap + + Make sure the configured nova service user in other services has the + ``service`` role otherwise communication from the other services to + Nova will fail. For example, user configured as ``username`` option in + ``neutron.conf`` file under ``[nova]`` section has the ``service`` + role. + + If you are allowing these APIs to be accessed by admin or non-admin users + then it is highly recommended to remove that permission and make sure + those APIs are not accessible by any non-service users. + + For backward compatibility, Nova continue allow ``admin`` role token to + access service APIs but in future release, ``admin`` access will be + removed. +deprecations: + - | + The below service-to-service APIs policy rule default value + ``role:admin or role:service`` is deprecated and will be changed to + ``role:service`` in future release: + + * os_compute_api:os-assisted-volume-snapshots:create + * os_compute_api:os-assisted-volume-snapshots:delete + * os_compute_api:os-server-external-events:create + * os_compute_api:os-volumes-attachments:swap