From 65b9dc622f00e4aeac0e4f9a71db690a377cd558 Mon Sep 17 00:00:00 2001 From: Tobias Urdin Date: Mon, 24 Mar 2025 16:16:25 +0100 Subject: [PATCH] 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 --- .../conf/policies/network_ip_availability.py | 2 +- neutron/conf/policies/port.py | 18 ++++--- neutron/conf/policies/subnet.py | 1 + .../policies/test_network_ip_availability.py | 8 +-- neutron/tests/unit/conf/policies/test_port.py | 54 +++++++++---------- .../tests/unit/conf/policies/test_subnet.py | 6 +-- ...octavia-service-role-b8ee74e5cb52ea30.yaml | 33 ++++++++++++ 7 files changed, 77 insertions(+), 45 deletions(-) create mode 100644 releasenotes/notes/octavia-service-role-b8ee74e5cb52ea30.yaml diff --git a/neutron/conf/policies/network_ip_availability.py b/neutron/conf/policies/network_ip_availability.py index 1cdedfb8338..6b84b006a68 100644 --- a/neutron/conf/policies/network_ip_availability.py +++ b/neutron/conf/policies/network_ip_availability.py @@ -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=[ diff --git a/neutron/conf/policies/port.py b/neutron/conf/policies/port.py index 7122a421623..2c2e7cb181a 100644 --- a/neutron/conf/policies/port.py +++ b/neutron/conf/policies/port.py @@ -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`` ' diff --git a/neutron/conf/policies/subnet.py b/neutron/conf/policies/subnet.py index 6182b4e4ff8..05291319467 100644 --- a/neutron/conf/policies/subnet.py +++ b/neutron/conf/policies/subnet.py @@ -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', diff --git a/neutron/tests/unit/conf/policies/test_network_ip_availability.py b/neutron/tests/unit/conf/policies/test_network_ip_availability.py index fa1affd687b..b036a44cae5 100644 --- a/neutron/tests/unit/conf/policies/test_network_ip_availability.py +++ b/neutron/tests/unit/conf/policies/test_network_ip_availability.py @@ -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)) diff --git a/neutron/tests/unit/conf/policies/test_port.py b/neutron/tests/unit/conf/policies/test_port.py index 48c63a36e8d..804e35fa91b 100644 --- a/neutron/tests/unit/conf/policies/test_port.py +++ b/neutron/tests/unit/conf/policies/test_port.py @@ -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( diff --git a/neutron/tests/unit/conf/policies/test_subnet.py b/neutron/tests/unit/conf/policies/test_subnet.py index d59747fafe4..9d0b1a22443 100644 --- a/neutron/tests/unit/conf/policies/test_subnet.py +++ b/neutron/tests/unit/conf/policies/test_subnet.py @@ -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( diff --git a/releasenotes/notes/octavia-service-role-b8ee74e5cb52ea30.yaml b/releasenotes/notes/octavia-service-role-b8ee74e5cb52ea30.yaml new file mode 100644 index 00000000000..e60ebc82346 --- /dev/null +++ b/releasenotes/notes/octavia-service-role-b8ee74e5cb52ea30.yaml @@ -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.