Merge ~ack/maas:1982315-use-rack-ip-if-external-dns-3.2 into maas:3.2

Proposed by Alberto Donato
Status: Merged
Approved by: Alberto Donato
Approved revision: 0b0b501a379151093776bc5b2a69ea64c462bc19
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~ack/maas:1982315-use-rack-ip-if-external-dns-3.2
Merge into: maas:3.2
Diff against target: 163 lines (+101/-21)
2 files modified
src/maasserver/compose_preseed.py (+23/-17)
src/maasserver/tests/test_compose_preseed.py (+78/-4)
Reviewer Review Type Date Requested Status
Alberto Donato (community) Approve
Review via email: mp+428997@code.launchpad.net

Commit message

LP:1982315 use rack IP instead of hostname in preseed if external DNS is used

This also makes handling of X-Forwarded-For a bit safer, since it ignores extra IPs that might come before the one from the rackd proxy (e.g. client-supplied values).

To post a comment you must log in.
Revision history for this message
Alberto Donato (ack) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/src/maasserver/compose_preseed.py b/src/maasserver/compose_preseed.py
index c814a01..145efc8 100644
--- a/src/maasserver/compose_preseed.py
+++ b/src/maasserver/compose_preseed.py
@@ -33,25 +33,36 @@ RSYSLOG_PORT = 5247
33RACK_CONTROLLER_PORT = 524833RACK_CONTROLLER_PORT = 5248
3434
3535
36def _subnet_uses_dns(subnet):
37 return (
38 subnet is not None and not subnet.dns_servers and subnet.vlan.dhcp_on
39 )
40
41
42def _wrap_ipv6_address(ip):
43 return str(ip) if ip_address(ip).version == 4 else f"[{ip}]"
44
45
36def _get_anon_rack_host(request, rack_controller):46def _get_anon_rack_host(request, rack_controller):
37 forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")47 forwarded_ips = request.META.get("HTTP_X_FORWARDED_FOR", "").split(", ")
38 if forwarded_for:48 if len(forwarded_ips) >= 2:
39 client_ip = forwarded_for.split(",")[0]49 # the reguest goes through the nginx proxy on the rack to the nginx
40 subnet = Subnet.objects.get_best_subnet_for_ip(client_ip)50 # proxy on the region
41 if subnet:51 forwarded_ip = forwarded_ips[-2]
52 subnet = Subnet.objects.get_best_subnet_for_ip(forwarded_ip)
53 if _subnet_uses_dns(subnet):
42 internal_domain = Config.objects.get_config("maas_internal_domain")54 internal_domain = Config.objects.get_config("maas_internal_domain")
43 return f"{get_resource_name_for_subnet(subnet)}.{internal_domain}"55 return f"{get_resource_name_for_subnet(subnet)}.{internal_domain}"
56 else:
57 return _wrap_ipv6_address(forwarded_ip)
44 return rack_controller.fqdn if rack_controller else ""58 return rack_controller.fqdn if rack_controller else ""
4559
4660
47def build_metadata_url(request, route, rack_controller, node=None, extra=""):61def build_metadata_url(request, route, rack_controller, node=None, extra=""):
48 host = _get_anon_rack_host(request, rack_controller)
49 if node and node.boot_cluster_ip:62 if node and node.boot_cluster_ip:
50 host = (63 host = _wrap_ipv6_address(node.boot_cluster_ip)
51 str(node.boot_cluster_ip)64 else:
52 if ip_address(node.boot_cluster_ip).version == 465 host = _get_anon_rack_host(request, rack_controller)
53 else f"[{node.boot_cluster_ip}]"
54 )
55 return (66 return (
56 request.build_absolute_uri(route) + extra67 request.build_absolute_uri(route) + extra
57 if not host68 if not host
@@ -89,12 +100,7 @@ def get_apt_proxy(request, rack_controller=None, node=None):
89 remote_ip = get_remote_ip(request)100 remote_ip = get_remote_ip(request)
90 if remote_ip is not None:101 if remote_ip is not None:
91 subnet = Subnet.objects.get_best_subnet_for_ip(remote_ip)102 subnet = Subnet.objects.get_best_subnet_for_ip(remote_ip)
92 use_dns = (103 if config["use_rack_proxy"] and _subnet_uses_dns(subnet):
93 subnet is not None
94 and not subnet.dns_servers
95 and subnet.vlan.dhcp_on
96 )
97 if config["use_rack_proxy"] and use_dns:
98 # Client can use the MAAS proxy on the rack controller with104 # Client can use the MAAS proxy on the rack controller with
99 # DNS resolution providing better HA.105 # DNS resolution providing better HA.
100 return "http://%s.%s:%d/" % (106 return "http://%s.%s:%d/" % (
diff --git a/src/maasserver/tests/test_compose_preseed.py b/src/maasserver/tests/test_compose_preseed.py
index c5a65c1..b733447 100644
--- a/src/maasserver/tests/test_compose_preseed.py
+++ b/src/maasserver/tests/test_compose_preseed.py
@@ -1273,9 +1273,12 @@ class TestBuildMetadataURL(MAASServerTestCase):
1273 node.boot_cluster_ip = factory.make_ip_address()1273 node.boot_cluster_ip = factory.make_ip_address()
1274 node.save()1274 node.save()
1275 request = make_HttpRequest()1275 request = make_HttpRequest()
1276 subnet = factory.make_Subnet()1276 subnet = factory.make_Subnet(dhcp_on=True, dns_servers=[])
1277 original_ip = subnet.get_next_ip_for_allocation()1277 rack_proxy_ip = subnet.get_next_ip_for_allocation()
1278 request.META["HTTP_X_FORWARDED_FOR"] = str(original_ip)1278 region_proxy_ip = subnet.get_next_ip_for_allocation()
1279 request.META[
1280 "HTTP_X_FORWARDED_FOR"
1281 ] = f"{rack_proxy_ip}, {region_proxy_ip}"
1279 route = "/MAAS"1282 route = "/MAAS"
1280 Config.objects.set_config("maas_internal_domain", factory.make_name())1283 Config.objects.set_config("maas_internal_domain", factory.make_name())
1281 # not passing node to simulate anonymous request, i.e enlistment1284 # not passing node to simulate anonymous request, i.e enlistment
@@ -1285,4 +1288,75 @@ class TestBuildMetadataURL(MAASServerTestCase):
1285 request, route, node.get_boot_rack_controller()1288 request, route, node.get_boot_rack_controller()
1286 ),1289 ),
1287 )1290 )
1288 route = "/MAAS"1291
1292 def test_uses_rack_ipv4_if_external_dns(self):
1293 # regression test for LP:1982315
1294 node = factory.make_Node()
1295 request = make_HttpRequest()
1296 subnet = factory.make_Subnet(
1297 cidr=factory.make_ipv4_network(),
1298 dhcp_on=True,
1299 dns_servers=[factory.make_ip_address()],
1300 )
1301 rack_proxy_ip = subnet.get_next_ip_for_allocation()
1302 region_proxy_ip = subnet.get_next_ip_for_allocation()
1303 request.META[
1304 "HTTP_X_FORWARDED_FOR"
1305 ] = f"{rack_proxy_ip}, {region_proxy_ip}"
1306 self.assertEqual(
1307 f"{request.scheme}://{rack_proxy_ip}:5248/MAAS",
1308 build_metadata_url(
1309 request,
1310 "/MAAS",
1311 node.get_boot_rack_controller(),
1312 node=node,
1313 ),
1314 )
1315
1316 def test_uses_rack_ipv6_if_external_dns(self):
1317 # regression test for LP:1982315
1318 node = factory.make_Node()
1319 request = make_HttpRequest()
1320 subnet = factory.make_Subnet(
1321 cidr=factory.make_ipv6_network(),
1322 dhcp_on=True,
1323 dns_servers=[factory.make_ip_address()],
1324 )
1325 rack_proxy_ip = subnet.get_next_ip_for_allocation()
1326 region_proxy_ip = subnet.get_next_ip_for_allocation()
1327 request.META[
1328 "HTTP_X_FORWARDED_FOR"
1329 ] = f"{rack_proxy_ip}, {region_proxy_ip}"
1330 self.assertEqual(
1331 f"{request.scheme}://[{rack_proxy_ip}]:5248/MAAS",
1332 build_metadata_url(
1333 request,
1334 "/MAAS",
1335 node.get_boot_rack_controller(),
1336 node=node,
1337 ),
1338 )
1339
1340 def test_uses_rack_ip_with_multiple_forwarded_for(self):
1341 node = factory.make_Node()
1342 request = make_HttpRequest()
1343 subnet = factory.make_Subnet(
1344 cidr=factory.make_ipv4_network(),
1345 dhcp_on=True,
1346 dns_servers=[factory.make_ip_address()],
1347 )
1348 rack_proxy_ip = subnet.get_next_ip_for_allocation()
1349 region_proxy_ip = subnet.get_next_ip_for_allocation()
1350 other_ip = subnet.get_next_ip_for_allocation()
1351 request.META[
1352 "HTTP_X_FORWARDED_FOR"
1353 ] = f"{other_ip}, {rack_proxy_ip}, {region_proxy_ip}"
1354 self.assertEqual(
1355 f"{request.scheme}://{rack_proxy_ip}:5248/MAAS",
1356 build_metadata_url(
1357 request,
1358 "/MAAS",
1359 node.get_boot_rack_controller(),
1360 node=node,
1361 ),
1362 )

Subscribers

People subscribed via source and target branches