diff --git a/CVE-2024-53916-Fix-the-tagging-policy-engine.patch b/CVE-2024-53916-Fix-the-tagging-policy-engine.patch new file mode 100644 index 0000000000000000000000000000000000000000..62a29420e27e317b338d3173c551ea0e55cef02d --- /dev/null +++ b/CVE-2024-53916-Fix-the-tagging-policy-engine.patch @@ -0,0 +1,451 @@ +diff --git a/neutron/extensions/tagging.py b/neutron/extensions/tagging.py +index b65978e..8ffd8a6 100644 +--- a/neutron/extensions/tagging.py ++++ b/neutron/extensions/tagging.py +@@ -12,6 +12,9 @@ + # under the License. + + import abc ++import collections ++import functools ++import itertools + + from neutron_lib.api import extensions as api_extensions + from neutron_lib.api import faults +@@ -28,6 +31,16 @@ from neutron.api import extensions + from neutron.api.v2 import resource as api_resource + from neutron.db import standard_attr + ++from neutron.objects import network as network_obj ++from neutron.objects import network_segment_range as network_segment_range_obj ++from neutron.objects import ports as ports_obj ++from neutron.objects.qos import policy as policy_obj ++from neutron.objects import router as router_obj ++from neutron.objects import securitygroup as securitygroup_obj ++from neutron.objects import subnet as subnet_obj ++from neutron.objects import subnetpool as subnetpool_obj ++from neutron.objects import trunk as trunk_obj ++from neutron import policy + + TAG = 'tag' + TAGS = TAG + 's' +@@ -49,6 +62,33 @@ TAG_ATTRIBUTE_MAP = { + 'is_visible': False, 'is_filter': True}, + } + ++PARENTS = { ++ 'floatingips': router_obj.FloatingIP, ++ 'network_segment_ranges': network_segment_range_obj.NetworkSegmentRange, ++ 'networks': network_obj.Network, ++ 'policies': policy_obj.QosPolicy, ++ 'ports': ports_obj.Port, ++ 'routers': router_obj.Router, ++ 'security_groups': securitygroup_obj.SecurityGroup, ++ 'subnets': ('networks', subnet_obj.Subnet), ++ 'subnetpools': subnetpool_obj.SubnetPool, ++ 'trunks': trunk_obj.Trunk, ++} ++ResourceInfo = collections.namedtuple( ++ 'ResourceInfo', ['project_id', ++ 'parent_type', ++ 'parent_id', ++ 'upper_parent_type', ++ 'upper_parent_id', ++ ]) ++EMPTY_RESOURCE_INFO = ResourceInfo(None, None, None, None, None) ++ ++def _policy_init(f): ++ @functools.wraps(f) ++ def func(self, *args, **kwargs): ++ policy.init() ++ return f(self, *args, **kwargs) ++ return func + + class TagResourceNotFound(exceptions.NotFound): + message = _("Resource %(resource)s %(resource_id)s could not be found.") +@@ -88,75 +128,161 @@ class TaggingController(object): + self.plugin = directory.get_plugin(TAG_PLUGIN_TYPE) + self.supported_resources = TAG_SUPPORTED_RESOURCES + ++ def _get_target(self, res_info): ++ target = {'id': res_info.parent_id, ++ 'tenant_id': res_info.project_id, ++ 'project_id': res_info.project_id} ++ if res_info.upper_parent_type: ++ res_id = (self.supported_resources[res_info.upper_parent_type] + ++ '_id') ++ target[res_id] = res_info.upper_parent_id ++ return target ++ ++ def _get_resource_info(self, context, kwargs): ++ """Return the tag parent resource information ++ ++ Some parent resources, like the subnets, depend on other upper parent ++ resources (networks). In that case, it is needed to provide the upper ++ parent resource information. ++ ++ :param kwargs: dictionary with the parent resource ID, along with other ++ information not needed. It is formated as ++ {"resource_id": "id", ...} ++ :return: ``ResourceInfo`` named tuple with the parent and upper parent ++ information and the project ID (of the parent or upper ++ parent). ++ """ ++ for key, parent_type in itertools.product( ++ kwargs.keys(), self.supported_resources.keys()): ++ if key != self.supported_resources[parent_type] + '_id': ++ continue ++ ++ parent_id = kwargs[key] ++ parent_obj = PARENTS[parent_type] ++ if isinstance(parent_obj, tuple): ++ upper_parent_type = parent_obj[0] ++ parent_obj = parent_obj[1] ++ res_id = (self.supported_resources[upper_parent_type] + ++ '_id') ++ upper_parent_id = parent_obj.get_values( ++ context.elevated(), res_id, id=parent_id)[0] ++ else: ++ upper_parent_type = upper_parent_id = None ++ ++ try: ++ project_id = parent_obj.get_values( ++ context.elevated(), 'project_id', id=parent_id)[0] ++ except IndexError: ++ return EMPTY_RESOURCE_INFO ++ ++ return ResourceInfo(project_id, parent_type, parent_id, ++ upper_parent_type, upper_parent_id) ++ ++ # This should never be returned. ++ return EMPTY_RESOURCE_INFO ++ + def _get_parent_resource_and_id(self, kwargs): + for key in kwargs: + for resource in self.supported_resources: + if key == self.supported_resources[resource] + '_id': + return resource, kwargs[key] + return None, None +- ++ @_policy_init + def index(self, request, **kwargs): +- # GET /v2.0/networks/{network_id}/tags +- parent, parent_id = self._get_parent_resource_and_id(kwargs) +- return self.plugin.get_tags(request.context, parent, parent_id) +- ++ # GET /v2.0/{parent_resource}/{parent_resource_id}/tags ++ ctx = request.context ++ rinfo = self._get_resource_info(ctx, kwargs) ++ target = self._get_target(rinfo) ++ policy.enforce(ctx, 'get_{}_{}'.format(rinfo.parent_type, TAGS), ++ target) ++ return self.plugin.get_tags(ctx, rinfo.parent_type, rinfo.parent_id) ++ ++ @_policy_init + def show(self, request, id, **kwargs): +- # GET /v2.0/networks/{network_id}/tags/{tag} ++ # GET /v2.0/{parent_resource}/{parent_resource_id}/tags/{tag} + # id == tag + validate_tag(id) +- parent, parent_id = self._get_parent_resource_and_id(kwargs) +- return self.plugin.get_tag(request.context, parent, parent_id, id) +- ++ ctx = request.context ++ rinfo = self._get_resource_info(ctx, kwargs) ++ target = self._get_target(rinfo) ++ policy.enforce(ctx, 'get_{}_{}'.format(rinfo.parent_type, TAGS), ++ target) ++ return self.plugin.get_tag(ctx, rinfo.parent_type, rinfo.parent_id, id) ++ ++ @_policy_init + def create(self, request, **kwargs): + # not supported + # POST /v2.0/networks/{network_id}/tags + raise webob.exc.HTTPNotFound("not supported") + ++ @_policy_init + def update(self, request, id, **kwargs): +- # PUT /v2.0/networks/{network_id}/tags/{tag} ++ # PUT /v2.0/{parent_resource}/{parent_resource_id}/tags/{tag} + # id == tag + validate_tag(id) +- parent, parent_id = self._get_parent_resource_and_id(kwargs) +- notify_tag_action(request.context, 'create.start', +- parent, parent_id, [id]) +- result = self.plugin.update_tag(request.context, parent, parent_id, id) +- notify_tag_action(request.context, 'create.end', +- parent, parent_id, [id]) ++ ctx = request.context ++ rinfo = self._get_resource_info(ctx, kwargs) ++ target = self._get_target(rinfo) ++ policy.enforce(ctx, 'update_{}_{}'.format(rinfo.parent_type, TAGS), ++ target) ++ notify_tag_action(ctx, 'create.start', rinfo.parent_type, ++ rinfo.parent_id, [id]) ++ result = self.plugin.update_tag(ctx, rinfo.parent_type, ++ rinfo.parent_id, id) ++ notify_tag_action(ctx, 'create.end', rinfo.parent_type, ++ rinfo.parent_id, [id]) + return result + ++ @_policy_init + def update_all(self, request, body, **kwargs): +- # PUT /v2.0/networks/{network_id}/tags ++ # PUT /v2.0/{parent_resource}/{parent_resource_id}/tags + # body: {"tags": ["aaa", "bbb"]} + validate_tags(body) +- parent, parent_id = self._get_parent_resource_and_id(kwargs) +- notify_tag_action(request.context, 'update.start', +- parent, parent_id, body['tags']) +- result = self.plugin.update_tags(request.context, parent, +- parent_id, body) +- notify_tag_action(request.context, 'update.end', +- parent, parent_id, body['tags']) ++ ctx = request.context ++ rinfo = self._get_resource_info(ctx, kwargs) ++ target = self._get_target(rinfo) ++ policy.enforce(ctx, 'update_{}_{}'.format(rinfo.parent_type, TAGS), ++ target) ++ notify_tag_action(ctx, 'update.start', rinfo.parent_type, ++ rinfo.parent_id, body['tags']) ++ result = self.plugin.update_tags(ctx, rinfo.parent_type, ++ rinfo.parent_id, body) ++ notify_tag_action(ctx, 'update.end', rinfo.parent_type, ++ rinfo.parent_id, body['tags']) + return result + ++ @_policy_init + def delete(self, request, id, **kwargs): +- # DELETE /v2.0/networks/{network_id}/tags/{tag} ++ # DELETE /v2.0/{parent_resource}/{parent_resource_id}/tags/{tag} + # id == tag + validate_tag(id) +- parent, parent_id = self._get_parent_resource_and_id(kwargs) +- notify_tag_action(request.context, 'delete.start', +- parent, parent_id, [id]) +- result = self.plugin.delete_tag(request.context, parent, parent_id, id) +- notify_tag_action(request.context, 'delete.end', +- parent, parent_id, [id]) ++ ctx = request.context ++ rinfo = self._get_resource_info(ctx, kwargs) ++ target = self._get_target(rinfo) ++ policy.enforce(ctx, 'delete_{}_{}'.format(rinfo.parent_type, TAGS), ++ target) ++ notify_tag_action(ctx, 'delete.start', rinfo.parent_type, ++ rinfo.parent_id, [id]) ++ result = self.plugin.delete_tag(ctx, rinfo.parent_type, ++ rinfo.parent_id, id) ++ notify_tag_action(ctx, 'delete.end', rinfo.parent_type, ++ rinfo.parent_id, [id]) + return result + ++ @_policy_init + def delete_all(self, request, **kwargs): +- # DELETE /v2.0/networks/{network_id}/tags +- parent, parent_id = self._get_parent_resource_and_id(kwargs) +- notify_tag_action(request.context, 'delete_all.start', +- parent, parent_id) +- result = self.plugin.delete_tags(request.context, parent, parent_id) +- notify_tag_action(request.context, 'delete_all.end', +- parent, parent_id) ++ # DELETE /v2.0/{parent_resource}/{parent_resource_id}/tags ++ ctx = request.context ++ rinfo = self._get_resource_info(ctx, kwargs) ++ target = self._get_target(rinfo) ++ policy.enforce(ctx, 'delete_{}_{}'.format(rinfo.parent_type, TAGS), ++ target) ++ notify_tag_action(ctx, 'delete_all.start', rinfo.parent_type, ++ rinfo.parent_id) ++ result = self.plugin.delete_tags(ctx, rinfo.parent_type, ++ rinfo.parent_id) ++ notify_tag_action(ctx, 'delete_all.end', rinfo.parent_type, ++ rinfo.parent_id) + return result + + +diff --git a/neutron/tests/unit/extensions/test_tagging.py b/neutron/tests/unit/extensions/test_tagging.py +new file mode 100644 +index 0000000..d9eea32 +--- /dev/null ++++ b/neutron/tests/unit/extensions/test_tagging.py +@@ -0,0 +1,179 @@ ++# Copyright 2024 Red Hat, Inc. ++# All rights reserved. ++# ++# Licensed under the Apache License, Version 2.0 (the "License"); you may ++# not use this file except in compliance with the License. You may obtain ++# a copy of the License at ++# ++# http://www.apache.org/licenses/LICENSE-2.0 ++# ++# Unless required by applicable law or agreed to in writing, software ++# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT ++# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the ++# License for the specific language governing permissions and limitations ++# under the License. ++# ++ ++import netaddr ++from neutron_lib import constants as n_const ++from neutron_lib import context ++from neutron_lib.utils import net as net_utils ++from oslo_utils import uuidutils ++ ++from neutron.extensions import tagging ++from neutron.objects import network as network_obj ++from neutron.objects import network_segment_range as network_segment_range_obj ++from neutron.objects import ports as ports_obj ++from neutron.objects.qos import policy as policy_obj ++from neutron.objects import router as router_obj ++from neutron.objects import securitygroup as securitygroup_obj ++from neutron.objects import subnet as subnet_obj ++from neutron.objects import subnetpool as subnetpool_obj ++from neutron.objects import trunk as trunk_obj ++from neutron.tests.unit import testlib_api ++ ++ ++class TaggingControllerDbTestCase(testlib_api.WebTestCase): ++ def setUp(self): ++ super().setUp() ++ self.user_id = uuidutils.generate_uuid() ++ self.project_id = uuidutils.generate_uuid() ++ self.ctx = context.Context(user_id=self.user_id, ++ tenant_id=self.project_id, ++ is_admin=False) ++ self.tc = tagging.TaggingController() ++ ++ def test_all_parents_have_a_reference(self): ++ tc_supported_resources = set(self.tc.supported_resources.keys()) ++ parent_resources = set(tagging.PARENTS.keys()) ++ self.assertEqual(tc_supported_resources, parent_resources) ++ ++ def _check_resource_info(self, parent_id, parent_type, ++ upper_parent_id=None, upper_parent_type=None): ++ p_id = self.tc.supported_resources[parent_type] + '_id' ++ res = self.tc._get_resource_info(self.ctx, {p_id: parent_id}) ++ reference = tagging.ResourceInfo( ++ self.project_id, parent_type, parent_id, ++ upper_parent_type, upper_parent_id) ++ self.assertEqual(reference, res) ++ ++ def test__get_resource_info_floatingips(self): ++ ext_net_id = uuidutils.generate_uuid() ++ fip_port_id = uuidutils.generate_uuid() ++ fip_id = uuidutils.generate_uuid() ++ network_obj.Network( ++ self.ctx, id=ext_net_id, project_id=self.project_id).create() ++ network_obj.ExternalNetwork( ++ self.ctx, project_id=self.project_id, ++ network_id=ext_net_id).create() ++ mac_str = next(net_utils.random_mac_generator( ++ ['ca', 'fe', 'ca', 'fe'])) ++ mac = netaddr.EUI(mac_str) ++ ports_obj.Port( ++ self.ctx, id=fip_port_id, project_id=self.project_id, ++ mac_address=mac, network_id=ext_net_id, admin_state_up=True, ++ status='UP', device_id='', device_owner='').create() ++ ip_address = netaddr.IPAddress('1.2.3.4') ++ router_obj.FloatingIP( ++ self.ctx, id=fip_id, project_id=self.project_id, ++ floating_network_id=ext_net_id, floating_port_id=fip_port_id, ++ floating_ip_address=ip_address).create() ++ self._check_resource_info(fip_id, 'floatingips') ++ ++ def test__get_resource_info_network_segment_ranges(self): ++ srange_id = uuidutils.generate_uuid() ++ network_segment_range_obj.NetworkSegmentRange( ++ self.ctx, id=srange_id, project_id=self.project_id, ++ shared=False, network_type=n_const.TYPE_GENEVE).create() ++ self._check_resource_info(srange_id, 'network_segment_ranges') ++ ++ def test__get_resource_info_networks(self): ++ net_id = uuidutils.generate_uuid() ++ network_obj.Network( ++ self.ctx, id=net_id, project_id=self.project_id).create() ++ self._check_resource_info(net_id, 'networks') ++ ++ def test__get_resource_info_policies(self): ++ qos_id = uuidutils.generate_uuid() ++ policy_obj.QosPolicy( ++ self.ctx, id=qos_id, project_id=self.project_id).create() ++ self._check_resource_info(qos_id, 'policies') ++ ++ def test__get_resource_info_ports(self): ++ net_id = uuidutils.generate_uuid() ++ port_id = uuidutils.generate_uuid() ++ network_obj.Network( ++ self.ctx, id=net_id, project_id=self.project_id).create() ++ mac_str = next(net_utils.random_mac_generator( ++ ['ca', 'fe', 'ca', 'fe'])) ++ mac = netaddr.EUI(mac_str) ++ ports_obj.Port( ++ self.ctx, id=port_id, project_id=self.project_id, ++ mac_address=mac, network_id=net_id, admin_state_up=True, ++ status='UP', device_id='', device_owner='').create() ++ self._check_resource_info(port_id, 'ports') ++ ++ def test__get_resource_info_routers(self): ++ router_id = uuidutils.generate_uuid() ++ router_obj.Router( ++ self.ctx, id=router_id, project_id=self.project_id).create() ++ self._check_resource_info(router_id, 'routers') ++ ++ def test__get_resource_info_security_groups(self): ++ sg_id = uuidutils.generate_uuid() ++ securitygroup_obj.SecurityGroup( ++ self.ctx, id=sg_id, project_id=self.project_id, ++ is_default=True).create() ++ self._check_resource_info(sg_id, 'security_groups') ++ ++ def test__get_resource_info_subnets(self): ++ net_id = uuidutils.generate_uuid() ++ subnet_id = uuidutils.generate_uuid() ++ network_obj.Network( ++ self.ctx, id=net_id, project_id=self.project_id).create() ++ cidr = netaddr.IPNetwork('1.2.3.0/24') ++ subnet_obj.Subnet( ++ self.ctx, id=subnet_id, project_id=self.project_id, ++ ip_version=n_const.IP_VERSION_4, cidr=cidr, ++ network_id=net_id).create() ++ self._check_resource_info(subnet_id, 'subnets', ++ upper_parent_id=net_id, ++ upper_parent_type='networks') ++ ++ def test__get_resource_info_subnetpools(self): ++ sp_id = uuidutils.generate_uuid() ++ subnetpool_obj.SubnetPool( ++ self.ctx, id=sp_id, project_id=self.project_id, ++ ip_version=n_const.IP_VERSION_4, default_prefixlen=26, ++ min_prefixlen=28, max_prefixlen=26).create() ++ self._check_resource_info(sp_id, 'subnetpools') ++ ++ def test__get_resource_info_trunks(self): ++ trunk_id = uuidutils.generate_uuid() ++ net_id = uuidutils.generate_uuid() ++ port_id = uuidutils.generate_uuid() ++ network_obj.Network( ++ self.ctx, id=net_id, project_id=self.project_id).create() ++ mac_str = next(net_utils.random_mac_generator( ++ ['ca', 'fe', 'ca', 'fe'])) ++ mac = netaddr.EUI(mac_str) ++ ports_obj.Port( ++ self.ctx, id=port_id, project_id=self.project_id, ++ mac_address=mac, network_id=net_id, admin_state_up=True, ++ status='UP', device_id='', device_owner='').create() ++ trunk_obj.Trunk( ++ self.ctx, id=trunk_id, project_id=self.project_id, ++ port_id=port_id).create() ++ self._check_resource_info(trunk_id, 'trunks') ++ ++ def test__get_resource_info_parent_not_present(self): ++ missing_id = uuidutils.generate_uuid() ++ p_id = self.tc.supported_resources['trunks'] + '_id' ++ res = self.tc._get_resource_info(self.ctx, {p_id: missing_id}) ++ self.assertEqual(tagging.EMPTY_RESOURCE_INFO, res) ++ ++ def test__get_resource_info_wrong_resource(self): ++ missing_id = uuidutils.generate_uuid() ++ res = self.tc._get_resource_info(self.ctx, ++ {'wrong_resource_id': missing_id}) ++ self.assertEqual(tagging.EMPTY_RESOURCE_INFO, res) +\ No newline at end of file diff --git a/openstack-neutron.spec b/openstack-neutron.spec index f4e1df14c4eae1df4bdaffb1cc7e1edee2c7b4b1..9db39486392793dea8220d42ed3707eef45adefe 100644 --- a/openstack-neutron.spec +++ b/openstack-neutron.spec @@ -30,7 +30,7 @@ capabilities (e.g., QoS, ACLs, network monitoring, etc.) Name: openstack-%{service} Version: 15.3.4 -Release: 3 +Release: 4 Summary: OpenStack Networking Service License: ASL 2.0 @@ -64,6 +64,7 @@ Source34: neutron-l2-agent-sysctl.conf Source35: neutron-l2-agent.modules Source36: neutron-destroy-patch-ports.service Source37: 0001-add-distributed-traffic-feature-support.patch +Patch01: CVE-2024-53916-Fix-the-tagging-policy-engine.patch # Required for tarball sources verification BuildArch: noarch @@ -817,6 +818,9 @@ crudini --del %{python3_sitelib}/neutron-*.egg-info/entry_points.txt neutron.age %{python3_sitelib}/openstack-plugin/neutron/%{basename %{SOURCE37}} %changelog +* Fri Nov 29 2024 wangjing - 15.3.4-4 +- add patch CVE-2024-53916-Fix-the-tagging-policy-engine.patch + * Tue Oct 17 2023 wangkuntian - 15.3.4-3 - Add distributed traffic feature package @@ -824,4 +828,4 @@ crudini --del %{python3_sitelib}/neutron-*.egg-info/entry_points.txt neutron.age - Fix install issue * Fri Nov 05 2021 wangxiyuan 15.3.4-1 -- Support OpenStack Train release \ No newline at end of file +- Support OpenStack Train release