From fea71a0ce4652cb1a591808471c3bd8b447367e0 Mon Sep 17 00:00:00 2001 From: Markus Hilger Date: Fri, 31 Jul 2026 22:25:32 +0200 Subject: [PATCH 1/4] Honor deployment.useinsecureprotocols for ProxyDHCP boot reply_dhcp4 declines to answer a PXE boot request unless deployment.useinsecureprotocols is set to firmware or always, but proxydhcp had no such check. A node left at the default of never was therefore still offered a TFTP bootfile and a plain http boot.ipxe URL whenever the request arrived on port 4011 rather than port 67, so the attribute silently did nothing in ProxyDHCP deployments alongside an independent DHCP server. Apply the same gate, including the UEFI HTTP boot exemption, and log the same remediation hint. The node attributes are now fetched once and passed through to get_deployment_profile instead of being looked up again there. Requests whose architecture could not be determined are ignored rather than falling through to the reply. opts_to_dict stops parsing before the client architecture option whenever the message type is not a request, and such a packet would otherwise reach the iPXE branch and be handed a plain http boot.ipxe URL without ever passing the gate. --- .../confluent/discovery/protocols/pxe.py | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/confluent_server/confluent/discovery/protocols/pxe.py b/confluent_server/confluent/discovery/protocols/pxe.py index 146678e1..5fae0159 100644 --- a/confluent_server/confluent/discovery/protocols/pxe.py +++ b/confluent_server/confluent/discovery/protocols/pxe.py @@ -313,10 +313,27 @@ async def proxydhcp(handler, nodeguess): disco.get('uuid', 'unknown'), disco.get('hwaddr', 'unknown') )}) continue + cfd = cfg.get_node_attributes( + node, ('deployment.*', 'collective.managercandidates')) + if disco['arch'] is None: + continue + insecuremode = cfd.get(node, {}).get('deployment.useinsecureprotocols', + {}).get('value', 'never') + if not insecuremode: + insecuremode = 'never' + if insecuremode == 'never' and disco['arch'] != 'uefi-httpboot': + if not skiplogging: + log.log( + {'info': 'Boot attempt by {0} detected in insecure mode, but ' + 'insecure mode is disabled. Set the attribute ' + '`deployment.useinsecureprotocols` to `firmware` or ' + '`always` to enable support, or use UEFI HTTP boot ' + 'with HTTPS.'.format(node)}) + continue profile = None if not myipn: myipn = socket.inet_aton(recv) - profile, stgprofile = get_deployment_profile(node, cfg) + profile, stgprofile = get_deployment_profile(node, cfg, cfd) if profile: log.log({ 'info': 'Offering proxyDHCP boot from {0} to {1} ({2})'.format(recv, node, client[0])}) @@ -326,7 +343,7 @@ async def proxydhcp(handler, nodeguess): continue if opts.get(77, None) == b'iPXE': if not profile: - profile, stgprofile = get_deployment_profile(node, cfg) + profile, stgprofile = get_deployment_profile(node, cfg, cfd) if not profile: log.log({'info': 'No pending profile for {0}, skipping proxyDHCP reply'.format(node)}) continue From a7b476b3fcd8e81d3c8413fbf7035c38bc9c3cd0 Mon Sep 17 00:00:00 2001 From: Markus Hilger Date: Wed, 5 Aug 2026 03:17:41 +0200 Subject: [PATCH 2/4] Ignore UEFI HTTP boot on ProxyDHCP port --- confluent_server/confluent/discovery/protocols/pxe.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/confluent_server/confluent/discovery/protocols/pxe.py b/confluent_server/confluent/discovery/protocols/pxe.py index 5fae0159..c09829e6 100644 --- a/confluent_server/confluent/discovery/protocols/pxe.py +++ b/confluent_server/confluent/discovery/protocols/pxe.py @@ -317,11 +317,14 @@ async def proxydhcp(handler, nodeguess): node, ('deployment.*', 'collective.managercandidates')) if disco['arch'] is None: continue + if disco['arch'] == 'uefi-httpboot': + # HTTP boot is offered by the DHCP path, not proxyDHCP + continue insecuremode = cfd.get(node, {}).get('deployment.useinsecureprotocols', {}).get('value', 'never') if not insecuremode: insecuremode = 'never' - if insecuremode == 'never' and disco['arch'] != 'uefi-httpboot': + if insecuremode == 'never': if not skiplogging: log.log( {'info': 'Boot attempt by {0} detected in insecure mode, but ' From ad2d021fcca1344e14d6e97c1ad6b0f55ebbfab6 Mon Sep 17 00:00:00 2001 From: Markus Hilger Date: Wed, 5 Aug 2026 03:23:27 +0200 Subject: [PATCH 3/4] Restore proxyDHCP log throttling The per-MAC 90 second log throttle in proxydhcp has been inert: the `skiplogging = True` reset sat in relay_proxydhcp, where it is a dead local, while the loop in proxydhcp only ever assigns False. Once the first packet is handled the flag stays False for the life of the process, so every retransmitted boot request logs again even though ignoredisco is updated to suppress it. Reset the flag at the top of each loop iteration instead, next to the timestamp check it belongs to, and drop the dead assignment. --- confluent_server/confluent/discovery/protocols/pxe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/confluent_server/confluent/discovery/protocols/pxe.py b/confluent_server/confluent/discovery/protocols/pxe.py index c09829e6..ed962c02 100644 --- a/confluent_server/confluent/discovery/protocols/pxe.py +++ b/confluent_server/confluent/discovery/protocols/pxe.py @@ -279,7 +279,6 @@ def relay_proxydhcp(sock, pktq): elif disco.get('uuid', None) in uuidmap: node = uuidmap[disco['uuid']] myipn = myipbypeer.get(data[28:28+hwlen], None) - skiplogging = True pktq.put_nowait((disco, peer, myipn, idx, recv, node, opts, data)) @@ -296,6 +295,7 @@ async def proxydhcp(handler, nodeguess): try: disco, client, myipn, idx, recv, node, opts, data = await pktq.get() netaddr = disco['hwaddr'] + skiplogging = True if time.time() > ignoredisco.get(netaddr, 0) + 90: skiplogging = False ignoredisco[netaddr] = time.time() From 4570d9f8af496433eff9cd14f3b27c80282e1cb0 Mon Sep 17 00:00:00 2001 From: Markus Hilger Date: Wed, 5 Aug 2026 03:42:22 +0200 Subject: [PATCH 4/4] Throttle the insecure mode boot refusal log reply_dhcp4 logs the insecure mode remediation hint on every DHCP discover it refuses. A node in this state never receives a reply, so it retries for as long as it is powered on and the same message repeats every few seconds. Rate limit it per hardware address the way the neighbouring boot attempt messages already do, reusing the ignoremacs window that check_reply uses for the missing profile hint. --- confluent_server/confluent/discovery/protocols/pxe.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/confluent_server/confluent/discovery/protocols/pxe.py b/confluent_server/confluent/discovery/protocols/pxe.py index ed962c02..db640bf3 100644 --- a/confluent_server/confluent/discovery/protocols/pxe.py +++ b/confluent_server/confluent/discovery/protocols/pxe.py @@ -715,7 +715,9 @@ async def reply_dhcp4(node, info, packet, cfg, reqview, httpboot, cfd, profile, if not insecuremode: insecuremode = 'never' if insecuremode == 'never' and not httpboot: - if rqtype == 1 and info.get('architecture', None): + if (rqtype == 1 and info.get('architecture', None) + and time.time() > ignoremacs.get(info['hwaddr'], 0) + 90): + ignoremacs[info['hwaddr']] = time.time() log.log( {'info': 'Boot attempt by {0} detected in insecure mode, but ' 'insecure mode is disabled. Set the attribute '