Browse Source

Disallow router interface out of subnet IP range

Currently, a non privileged tenant can add a router interface to a
shared / external network's subnet with an IP address outside the
subnet's allocation pool, creating a security risk. This patch prevents
tenants who are not the subnet's owner or admin from assigning a router
interface an IP address outside the subnet's allocation pool.

Change-Id: I32e76a83443dd8e7d79b396499747f29b4762e92
Closes-Bug: #1757482
tags/13.0.0.0b3
Miguel Lavalle 1 year ago
parent
commit
54aa6e81cb
2 changed files with 82 additions and 0 deletions
  1. 26
    0
      neutron/db/l3_db.py
  2. 56
    0
      neutron/tests/unit/extensions/test_l3.py

+ 26
- 0
neutron/db/l3_db.py View File

@@ -692,6 +692,27 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
692 692
             raise n_exc.BadRequest(resource='router', msg=msg)
693 693
         return port
694 694
 
695
+    def _validate_port_in_range_or_admin(self, context, subnets, port):
696
+        if context.is_admin:
697
+            return
698
+        subnets_by_id = {}
699
+        for s in subnets:
700
+            addr_set = netaddr.IPSet()
701
+            for range in s['allocation_pools']:
702
+                addr_set.add(netaddr.IPRange(netaddr.IPAddress(range['start']),
703
+                                             netaddr.IPAddress(range['end'])))
704
+            subnets_by_id[s['id']] = (addr_set, s['project_id'],)
705
+        for subnet_id, ip in [(fix_ip['subnet_id'], fix_ip['ip_address'],)
706
+                              for fix_ip in port['fixed_ips']]:
707
+            if (ip not in subnets_by_id[subnet_id][0] and
708
+                    context.project_id != subnets_by_id[subnet_id][1]):
709
+                msg = (_('Cannot add interface to router because specified '
710
+                         'port %(port)s has an IP address out of the '
711
+                         'allocation pool of subnet %(subnet)s, which is not '
712
+                         'owned by the project making the request') %
713
+                       {'port': port['id'], 'subnet': subnet_id})
714
+                raise n_exc.BadRequest(resource='router', msg=msg)
715
+
695 716
     def _validate_router_port_info(self, context, router, port_id):
696 717
         with db_api.autonested_transaction(context.session):
697 718
             # check again within transaction to mitigate race
@@ -727,6 +748,7 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
727 748
                 msg = _("Cannot have multiple "
728 749
                         "IPv4 subnets on router port")
729 750
                 raise n_exc.BadRequest(resource='router', msg=msg)
751
+            self._validate_port_in_range_or_admin(context, subnets, port)
730 752
             return port, subnets
731 753
 
732 754
     def _notify_attaching_interface(self, context, router_db, port,
@@ -788,6 +810,10 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
788 810
         if not subnet['gateway_ip']:
789 811
             msg = _('Subnet for router interface must have a gateway IP')
790 812
             raise n_exc.BadRequest(resource='router', msg=msg)
813
+        if subnet['project_id'] != context.project_id and not context.is_admin:
814
+            msg = (_('Cannot add interface to router because subnet %s is not '
815
+                     'owned by project making the request') % subnet_id)
816
+            raise n_exc.BadRequest(resource='router', msg=msg)
791 817
         if (subnet['ip_version'] == 6 and subnet['ipv6_ra_mode'] is None and
792 818
                 subnet['ipv6_address_mode'] is not None):
793 819
             msg = (_('IPv6 subnet %s configured to receive RAs from an '

+ 56
- 0
neutron/tests/unit/extensions/test_l3.py View File

@@ -1265,6 +1265,62 @@ class L3NatTestCaseBase(L3NatTestCaseMixin):
1265 1265
                                                   expected_code=err_code,
1266 1266
                                                   tenant_id='bad_tenant')
1267 1267
 
1268
+    def test_router_add_interface_by_subnet_other_tenant_subnet_returns_400(
1269
+            self):
1270
+        router_tenant_id = _uuid()
1271
+        with self.router(tenant_id=router_tenant_id, set_context=True) as r:
1272
+            with self.network(shared=True) as n:
1273
+                with self.subnet(network=n) as s:
1274
+                    err_code = exc.HTTPBadRequest.code
1275
+                    self._router_interface_action('add',
1276
+                                                  r['router']['id'],
1277
+                                                  s['subnet']['id'],
1278
+                                                  None,
1279
+                                                  expected_code=err_code,
1280
+                                                  tenant_id=router_tenant_id)
1281
+
1282
+    def _test_router_add_interface_by_port_allocation_pool(
1283
+            self, out_of_pool=False, router_action_as_admin=False,
1284
+            expected_code=exc.HTTPOk.code):
1285
+        router_tenant_id = _uuid()
1286
+        with self.router(tenant_id=router_tenant_id, set_context=True) as r:
1287
+            with self.network(shared=True) as n:
1288
+                with self.subnet(network=n) as s1, (
1289
+                     self.subnet(network=n, cidr='fd00::/64',
1290
+                                 ip_version=6)) as s2, (
1291
+                     self.subnet(network=n, cidr='fd01::/64',
1292
+                                 ip_version=6)) as s3:
1293
+                    fixed_ips = [{'subnet_id': s1['subnet']['id']},
1294
+                                 {'subnet_id': s2['subnet']['id']},
1295
+                                 {'subnet_id': s3['subnet']['id']}]
1296
+                    if out_of_pool:
1297
+                        fixed_ips[1] = {'subnet_id': s2['subnet']['id'],
1298
+                                        'ip_address':
1299
+                                            s2['subnet']['gateway_ip']}
1300
+                    with self.port(subnet=s1, fixed_ips=fixed_ips,
1301
+                                   tenant_id=router_tenant_id,
1302
+                                   set_context=True) as p:
1303
+                        kwargs = {'expected_code': expected_code}
1304
+                        if not router_action_as_admin:
1305
+                            kwargs['tenant_id'] = router_tenant_id
1306
+                        self._router_interface_action(
1307
+                            'add', r['router']['id'], None, p['port']['id'],
1308
+                            **kwargs)
1309
+
1310
+    def test_router_add_interface_by_port_other_tenant_address_in_pool(
1311
+            self):
1312
+        self._test_router_add_interface_by_port_allocation_pool()
1313
+
1314
+    def test_router_add_interface_by_port_other_tenant_address_out_of_pool(
1315
+            self):
1316
+        self._test_router_add_interface_by_port_allocation_pool(
1317
+            out_of_pool=True, expected_code=exc.HTTPBadRequest.code)
1318
+
1319
+    def test_router_add_interface_by_port_admin_address_out_of_pool(
1320
+            self):
1321
+        self._test_router_add_interface_by_port_allocation_pool(
1322
+            out_of_pool=True, router_action_as_admin=True)
1323
+
1268 1324
     def test_router_add_interface_subnet_with_port_from_other_tenant(self):
1269 1325
         tenant_id = _uuid()
1270 1326
         other_tenant_id = _uuid()

Loading…
Cancel
Save