Allow service role more RBAC access for Octavia

This updates the default RBAC rules for multiple
resources to allow for a seamless integration with
Octavia without having to give Octavia system scope
admin in the entire cloud.

The current use of the service role in the RBAC
rules allows for pretty much all of the permissions
that Octavia needs today except for a few.

It needs get_subnet to be able to retrieve a subnet
and check the details, this is low impact as we
already allow get_network.

It also needs get_network_ip_availability because it
supports to automatically select a subnet (if none
is given) on a network based on the amount of
available IP addresses.

The default Amphora compute driver for Octavia uses
a keepalived and HAProxy implementation that uses
unicast VRRP for the VIP address, this VIP address
is added as an allowed address pair on the ports
for the amphora compute instances so the VIP port
itself is not bound.

Octavia also depends on being able to populate the
``device_id`` field on a port which means it also
needs this patch [1] together with this one.

[1] https://review.opendev.org/c/openstack/neutron/+/947003

Closes-Bug: #2105502
Change-Id: I089999cece698af1a3b54d1341d9004d4108ae44
This commit is contained in:
Tobias Urdin 2025-03-24 16:16:25 +01:00 committed by Brian Haley
parent c981cfd658
commit 65b9dc622f
7 changed files with 77 additions and 45 deletions

View File

@ -24,7 +24,7 @@ The network IP availability API now support project scope and default roles.
rules = [
policy.DocumentedRuleDefault(
name='get_network_ip_availability',
check_str=base.ADMIN,
check_str=base.ADMIN_OR_SERVICE,
scope_types=['project'],
description='Get network IP availability',
operations=[

View File

@ -258,7 +258,8 @@ rules = [
name='create_port:allowed_address_pairs',
check_str=neutron_policy.policy_or(
base.ADMIN_OR_NET_OWNER_MEMBER,
base.PROJECT_MANAGER),
base.PROJECT_MANAGER,
base.SERVICE),
scope_types=['project'],
description=(
'Specify ``allowed_address_pairs`` '
@ -275,7 +276,8 @@ rules = [
name='create_port:allowed_address_pairs:mac_address',
check_str=neutron_policy.policy_or(
base.ADMIN_OR_NET_OWNER_MEMBER,
base.PROJECT_MANAGER),
base.PROJECT_MANAGER,
base.SERVICE),
scope_types=['project'],
description=(
'Specify ``mac_address` of `allowed_address_pairs`` '
@ -292,7 +294,8 @@ rules = [
name='create_port:allowed_address_pairs:ip_address',
check_str=neutron_policy.policy_or(
base.ADMIN_OR_NET_OWNER_MEMBER,
base.PROJECT_MANAGER),
base.PROJECT_MANAGER,
base.SERVICE),
scope_types=['project'],
description=(
'Specify ``ip_address`` of ``allowed_address_pairs`` '
@ -650,7 +653,8 @@ rules = [
name='update_port:allowed_address_pairs',
check_str=neutron_policy.policy_or(
base.ADMIN_OR_NET_OWNER_MEMBER,
base.PROJECT_MANAGER),
base.PROJECT_MANAGER,
base.SERVICE),
scope_types=['project'],
description='Update ``allowed_address_pairs`` attribute of a port',
operations=ACTION_PUT,
@ -664,7 +668,8 @@ rules = [
name='update_port:allowed_address_pairs:mac_address',
check_str=neutron_policy.policy_or(
base.ADMIN_OR_NET_OWNER_MEMBER,
base.PROJECT_MANAGER),
base.PROJECT_MANAGER,
base.SERVICE),
scope_types=['project'],
description=(
'Update ``mac_address`` of ``allowed_address_pairs`` '
@ -681,7 +686,8 @@ rules = [
name='update_port:allowed_address_pairs:ip_address',
check_str=neutron_policy.policy_or(
base.ADMIN_OR_NET_OWNER_MEMBER,
base.PROJECT_MANAGER),
base.PROJECT_MANAGER,
base.SERVICE),
scope_types=['project'],
description=(
'Update ``ip_address`` of ``allowed_address_pairs`` '

View File

@ -126,6 +126,7 @@ rules = [
'rule:shared',
'rule:external_network',
base.ADMIN_OR_NET_OWNER_READER,
base.SERVICE,
),
scope_types=['project'],
description='Get a subnet',

View File

@ -99,7 +99,7 @@ class ServiceRoleTests(NetworkIPAvailabilityAPITestCase):
self.context = self.service_ctx
def test_get_network_ip_availability(self):
self.assertRaises(
base_policy.PolicyNotAuthorized,
policy.enforce,
self.context, 'get_network_ip_availability', self.target)
self.assertTrue(
policy.enforce(
self.context, 'get_network_ip_availability',
self.target))

View File

@ -1628,25 +1628,22 @@ class ServiceRoleTests(PortAPITestCase):
'create_port:binding:vnic_type', self.target))
def test_create_port_with_allowed_address_pairs(self):
self.assertRaises(
base_policy.PolicyNotAuthorized,
policy.enforce,
self.context, 'create_port:allowed_address_pairs',
self.target)
self.assertTrue(
policy.enforce(
self.context, 'create_port:allowed_address_pairs',
self.target))
def test_create_port_with_allowed_address_pairs_and_mac_address(self):
self.assertRaises(
base_policy.PolicyNotAuthorized,
policy.enforce,
self.context, 'create_port:allowed_address_pairs:mac_address',
self.alt_target)
self.assertTrue(
policy.enforce(
self.context, 'create_port:allowed_address_pairs:mac_address',
self.alt_target))
def test_create_port_with_allowed_address_pairs_and_ip_address(self):
self.assertRaises(
base_policy.PolicyNotAuthorized,
policy.enforce,
self.context, 'create_port:allowed_address_pairs:ip_address',
self.target)
self.assertTrue(
policy.enforce(
self.context, 'create_port:allowed_address_pairs:ip_address',
self.target))
def test_create_port_tags(self):
self.assertRaises(
@ -1744,25 +1741,22 @@ class ServiceRoleTests(PortAPITestCase):
self.context, 'update_port:binding:vnic_type', self.target))
def test_update_port_with_allowed_address_pairs(self):
self.assertRaises(
base_policy.PolicyNotAuthorized,
policy.enforce,
self.context, 'update_port:allowed_address_pairs',
self.target)
self.assertTrue(
policy.enforce(
self.context, 'update_port:allowed_address_pairs',
self.target))
def test_update_port_with_allowed_address_pairs_and_mac_address(self):
self.assertRaises(
base_policy.PolicyNotAuthorized,
policy.enforce,
self.context, 'update_port:allowed_address_pairs:mac_address',
self.target)
self.assertTrue(
policy.enforce(
self.context, 'update_port:allowed_address_pairs:mac_address',
self.target))
def test_update_port_with_allowed_address_pairs_and_ip_address(self):
self.assertRaises(
base_policy.PolicyNotAuthorized,
policy.enforce,
self.context, 'update_port:allowed_address_pairs:ip_address',
self.target)
self.assertTrue(
policy.enforce(
self.context, 'update_port:allowed_address_pairs:ip_address',
self.target))
def test_update_port_data_plane_status(self):
self.assertRaises(

View File

@ -927,10 +927,8 @@ class ServiceRoleTests(SubnetAPITestCase):
self.context, 'create_subnet:tags', self.target)
def test_get_subnet(self):
self.assertRaises(
base_policy.PolicyNotAuthorized,
policy.enforce,
self.context, 'get_subnet', self.target)
self.assertTrue(
policy.enforce(self.context, 'get_subnet', self.target))
def test_get_subnet_segment_id(self):
self.assertRaises(

View File

@ -0,0 +1,33 @@
---
features:
- |
Updated RBAC rules so that they allow the ``service`` role to pass the
following policies by default:
- ``get_subnet``
- ``get_network_ip_availability``
- ``create_port:allowed_address_pairs``
- ``create_port:allowed_address_pairs:mac_address``
- ``create_port:allowed_address_pairs:ip_address``
- ``update_port:allowed_address_pairs``
- ``update_port:allowed_address_pairs:mac_address``
- ``update_port:allowed_address_pairs:ip_address``
This allows for integration with the Octavia project using the
``service`` role instead of the ``admin`` role for integration
with Neutron.
upgrade:
- |
Default RBAC policies for ``get_subnet``, ``get_network_ip_availability``,
``create_port:allowed_address_pairs``, ``create_port:allowed_address_pairs:mac_address``,
``create_port:allowed_address_pairs:ip_address``, ``update_port:allowed_address_pairs``,
``update_port:allowed_address_pairs:mac_address`` and
``update_port:allowed_address_pairs:ip_address`` have been updated to allow the
``service`` role.