From e3d70f351d65d1c91a52e1966f64d645e016fced Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Wed, 22 Jan 2025 15:44:49 -0500 Subject: [PATCH] Provide internal URL shortening service Permit users to have very long profile names, and provide a URL shortening service to bridge the gap for fixed-width field limitations in DHCP/PXE. --- .../confluent/discovery/protocols/pxe.py | 44 ++++++++++++++----- confluent_server/confluent/httpapi.py | 15 ++++++- 2 files changed, 46 insertions(+), 13 deletions(-) diff --git a/confluent_server/confluent/discovery/protocols/pxe.py b/confluent_server/confluent/discovery/protocols/pxe.py index 64e64c79..2be13883 100644 --- a/confluent_server/confluent/discovery/protocols/pxe.py +++ b/confluent_server/confluent/discovery/protocols/pxe.py @@ -22,6 +22,7 @@ # option 97 = UUID (wireformat) +import base64 import confluent.config.configmanager as cfm import confluent.collective.manager as collective import confluent.noderange as noderange @@ -35,6 +36,7 @@ import eventlet import eventlet.green.socket as socket import eventlet.green.select as select import netifaces +import os import struct import time import traceback @@ -165,6 +167,18 @@ pxearchs = { } +shorturls = {} +def register_shorturl(url): + urlid = base64.urlsafe_b64encode(os.urandom(3)) + while urlid in shorturls: + urlid = base64.urlsafe_b64encode(os.urandom(3)) + urlid = urlid.decode() + shorturls[urlid] = url + returl = '/'.join(url.split('/')[:3]) + returl += '/confluent-api/boot/su/' + urlid + '/' + os.path.basename(url) + return returl + + uuidmap = {} macmap = {} attribwatcher = None @@ -369,12 +383,15 @@ def proxydhcp(handler, nodeguess): elif disco['arch'] == 'uefi-aarch64': bootfile = b'confluent/aarch64/ipxe.efi' if len(bootfile) > 127: - log.log( - {'info': 'Boot offer cannot be made to {0} as the ' - 'profile name "{1}" is {2} characters longer than is supported ' - 'for this boot method.'.format( - node, profile, len(bootfile) - 127)}) - continue + if bootfile.startswith(b'http'): + bootfile = register_shorturl(bootfile.decode('utf8')).encode('utf8') + else: + log.log( + {'info': 'Boot offer cannot be made to {0} as the ' + 'profile name "{1}" is {2} characters longer than is supported ' + 'for this boot method.'.format( + node, profile, len(bootfile) - 127)}) + continue rpv[:240] = rqv[:240].tobytes() rpv[0:1] = b'\x02' rpv[108:108 + len(bootfile)] = bootfile @@ -797,12 +814,15 @@ def reply_dhcp4(node, info, packet, cfg, reqview, httpboot, cfd, profile, sock=N if not isinstance(bootfile, bytes): bootfile = bootfile.encode('utf8') if len(bootfile) > 127: - log.log( - {'info': 'Boot offer cannot be made to {0} as the ' - 'profile name "{1}" is {2} characters longer than is supported ' - 'for this boot method.'.format( - node, profile, len(bootfile) - 127)}) - return + if bootfile.startswith(b'http'): + bootfile = register_shorturl(bootfile.decode('utf8')).encode('utf8') + else: + log.log( + {'info': 'Boot offer cannot be made to {0} as the ' + 'profile name "{1}" is {2} characters longer than is supported ' + 'for this boot method.'.format( + node, profile, len(bootfile) - 127)}) + return repview[108:108 + len(bootfile)] = bootfile elif info.get('architecture', None) == 'uefi-aarch64' and packet.get(77, None) == b'iPXE': if not profile: diff --git a/confluent_server/confluent/httpapi.py b/confluent_server/confluent/httpapi.py index 5048b8f8..e2144235 100644 --- a/confluent_server/confluent/httpapi.py +++ b/confluent_server/confluent/httpapi.py @@ -30,6 +30,7 @@ import confluent.config.attributes as attribs import confluent.config.configmanager as configmanager import confluent.consoleserver as consoleserver import confluent.discovery.core as disco +import confluent.discovery.protocols.pxe as pxe import confluent.forwarder as forwarder import confluent.exceptions as exc import confluent.log as log @@ -640,6 +641,8 @@ def resourcehandler(env, start_response): yield '500 - ' + str(e) return + + def resourcehandler_backend(env, start_response): """Function to handle new wsgi requests """ @@ -648,7 +651,7 @@ def resourcehandler_backend(env, start_response): ('Pragma', 'no-cache'), ('X-Content-Type-Options', 'nosniff'), ('Content-Security-Policy', "default-src 'self'"), - ('X-XSS-Protection', '1; mode=block'), ('X-Frame-Options', 'deny'), + ('X-XySS-Protection', '1; mode=block'), ('X-Frame-Options', 'deny'), ('Strict-Transport-Security', 'max-age=86400'), ('X-Permitted-Cross-Domain-Policies', 'none')] reqbody = None @@ -671,6 +674,16 @@ def resourcehandler_backend(env, start_response): request = env['PATH_INFO'].split('/') if not request[0]: request = request[1:] + if request[1] == 'su': # shorturl + targurl = pxe.shorturls.get(request[2], None) + if not targurl: + start_response('404 Not Found', headers) + yield '' + return + headers.append(('Location', targurl)) + start_response('302 Found', headers) + yield '' + return if len(request) != 4: start_response('400 Bad Request', headers) yield ''