diff --git a/confluent_server/confluent/config/attributes.py b/confluent_server/confluent/config/attributes.py index 48062153..89d58b7d 100644 --- a/confluent_server/confluent/config/attributes.py +++ b/confluent_server/confluent/config/attributes.py @@ -166,6 +166,23 @@ node = { 'indicates candidate managers, either for ' 'high availability or load balancing purposes.') }, + 'deployment.pendingprofile': { + 'description': ('An OS profile that is pending deployment. This indicates to ' + 'the network boot subsystem what should be offered when a potential ' + 'network boot request comes in') + }, + 'deployment.useinsecureprotocols': { + 'description': ('What phase(s) of boot are permitted to use insecure protocols ' + '(TFTP and HTTP without TLS. By default, HTTPS is allowed. However ' + 'this is not compatible with most firmware in most scenarios. Using ' + '"firmware" as the setting will still use HTTPS after the initial download, ' + 'though be aware that a successful compromise during the firmware phase ' + 'will negate future TLS protections. The value "always" will result in ' + 'tftp/http being used for entire deployment. Note that ONIE does not ' + 'support secure protocols, and in that case this setting must be "always" ' + 'or "firmware"'), + 'validlist': ('always', 'firmware', 'never'), + }, 'discovery.passwordrules': { 'description': 'Any specified rules shall be configured on the BMC ' 'upon discovery. "expiration=no,loginfailures=no,complexity=no,reuse=no" ' diff --git a/confluent_server/confluent/discovery/protocols/pxe.py b/confluent_server/confluent/discovery/protocols/pxe.py index 6ed05030..1be6aa0b 100644 --- a/confluent_server/confluent/discovery/protocols/pxe.py +++ b/confluent_server/confluent/discovery/protocols/pxe.py @@ -89,11 +89,11 @@ cmsgtype = ctypes.c_char * CMSG_SPACE(ctypes.sizeof(in_pktinfo)).value cmsgsize = CMSG_SPACE(ctypes.sizeof(in_pktinfo)).value pxearchs = { - '\x00\x00': 'bios-x86', - '\x00\x07': 'uefi-x64', - '\x00\x09': 'uefi-x64', - '\x00\x0b': 'uefi-aarch64', - '\x00\x10': 'uefi-httpboot', + b'\x00\x00': 'bios-x86', + b'\x00\x07': 'uefi-x64', + b'\x00\x09': 'uefi-x64', + b'\x00\x0b': 'uefi-aarch64', + b'\x00\x10': 'uefi-httpboot', } @@ -127,60 +127,46 @@ def _decode_ocp_vivso(rq, idx, size): idx += rq[idx + 1] + 2 return '', None, vivso - -def find_info_in_options(rq, optidx): - uuid = None - arch = None - vivso = None - ztpurlrequested = False - iscumulus = False +def opts_to_dict(rq, optidx): + reqdict = {} + disco = {'uuid':None, 'arch': None, 'vivso': None} try: - while uuid is None or arch is None: - if rq[optidx] == 53: # DHCP message type - # we want only length 1 and only discover (type 1) - if rq[optidx + 1] != 1 or rq[optidx + 2] != 1: - return uuid, arch, vivso - optidx += 3 - elif rq[optidx] == 55: - if 239 in rq[optidx + 2:optidx + 2 + rq[optidx + 1]]: - ztpurlrequested = True - optidx += rq[optidx + 1] + 2 - elif rq[optidx] == 60: - vci = stringify(rq[optidx + 2:optidx + 2 + rq[optidx + 1]]) - if vci.startswith('cumulus-linux'): - iscumulus = True - arch = vci.replace('cumulus-linux', '').strip() - optidx += rq[optidx + 1] + 2 - elif rq[optidx] == 97: - if rq[optidx + 1] != 17: - # 16 bytes of uuid and one reserved byte - return uuid, arch, vivso - if rq[optidx + 2] != 0: # the reserved byte should be zero, - # anything else would be a new spec that we don't know yet - return uuid, arch, vivso - uuid = decode_uuid(rq[optidx + 3:optidx + 19]) - optidx += 19 - elif rq[optidx] == 93: - if rq[optidx + 1] != 2: - return uuid, arch - archraw = bytes(rq[optidx + 2:optidx + 4]) - if archraw in pxearchs: - arch = pxearchs[archraw] - optidx += 4 - elif rq[optidx] == 125: - #vivso = rq[optidx + 2:optidx + 2 + rq[optidx + 1]] - if rq[optidx + 2:optidx + 6] == b'\x00\x00\xa6\x7f': # OCP - return _decode_ocp_vivso(rq, optidx + 7, rq[optidx + 6]) - optidx += rq[optidx + 1] + 2 - else: - optidx += rq[optidx + 1] + 2 + while optidx < len(rq): + optnum = rq[optidx] + optlen = rq[optidx + 1] + reqdict[optnum] = rq[optidx + 2:optidx + 2 + optlen] except IndexError: pass - if not vivso and iscumulus and ztpurlrequested: - if not uuid: - uuid = '' - vivso = {'service-type': 'cumulus-switch', 'arch': arch} - return uuid, arch, vivso + if reqdict.get(53, [0])[0] != 1: + return reqdict, disco + # It is a discover packet.. + iscumulus = False + maybeztp = False + if 239 in reqdict.get(55, []): + maybeztp = True + vci = stringify(reqdict.get(60, '')) + if vci.startswith('cumulus-linux'): + disco['arch'] = vci.replace('cumulus-linux', '').strip() + iscumulus = True + if reqdict.get(93, None): + disco['arch'] = pxearchs.get(bytes(reqdict[93]), None) + if reqdict.get(97, None): + uuidcandidate = reqdict[97] + if uuidcandidate[0] != 0: + return reqdict, disco + disco['uuid'] = decode_uuid(uuidcandidate[1:]) + if reqdict.get(125, None): + if reqdict[125][:4] == b'\x00\x00\xa6\x7f': # OCP + disco['vivso'] = _decode_ocp_vivso( + reqdict[125], 5, reqdict[125][4])[-1] + return reqdict, disco + if not disco['vivso'] and iscumulus and maybeztp: + if not disco['uuid']: + disco['uuid'] = '' + disco['vivso'] = {'service-type': 'cumulus-switch', + 'arch': disco['arch']} + return reqdict, disco + def ipfromint(numb): return socket.inet_ntoa(struct.pack('I', numb)) @@ -257,27 +243,28 @@ def snoop(handler, protocol=None): except ValueError: continue txid = struct.unpack('!I', rq[4:8])[0] - uuid, arch, vivso = find_info_in_options(rq, optidx) + rqinfo, disco = opts_to_dict(rq, optidx) + vivso = disco.get('vivso', None) if vivso: # info['modelnumber'] = info['attributes']['enclosure-machinetype-model'][0] - info = {'hwaddr': netaddr, 'uuid': uuid, + info = {'hwaddr': netaddr, 'uuid': disco['uuid'], 'architecture': vivso.get('arch', ''), 'services': (vivso['service-type'],), 'netinfo': {'ifidx': idx, 'recvip': recv, 'txid': txid}, 'attributes': {'enclosure-machinetype-model': [vivso.get('machine', '')]}} handler(info) - consider_discover(info, rq, net4) - continue - if uuid is None: + consider_discover(info, rq, net4, cfg) continue # We will fill out service to have something to byte into, # but the nature of the beast is that we do not have peers, # so that will not be present for a pxe snoop - info = {'hwaddr': netaddr, 'uuid': uuid, 'architecture': arch, + info = {'hwaddr': netaddr, 'uuid': disco['uuid'], 'architecture': disco['arch'], 'netinfo': {'ifidx': idx, 'recvip': recv, 'txid': txid}, 'services': ('pxe-client',)} - handler(info) - consider_discover(info, rq, net4) + if disco['uuid']: + handler(info) + consider_discover(info, rq, net4, cfg) + def clear_nodes(nodes): @@ -312,15 +299,20 @@ def remap_nodes(nodeattribs, configmanager): macmap[updates[node][attrib]['value']] = node -def check_reply(node, info, packet, sock): +def check_reply(node, info, packet, sock, cfg): + cfd = cfg.get_node_attributes(node, ('deployment.*')) + insecuremode = cfd.get(node, {}).get('deployment.useinsecureprotocols', 'never') + if insecuremode == 'never' and info['architecture'] != 'uefi-httpboot': + print('Ignoring request') + return print('Thinking about reply to {0}'.format(node)) -def consider_discover(info, packet, sock): +def consider_discover(info, packet, sock, cfg): if info.get('hwaddr', None) in macmap: - check_reply(macmap[info['hwaddr']], info, packet, sock) + check_reply(macmap[info['hwaddr']], info, packet, sock, cfg) elif info.get('uuid', None) in uuidmap: - check_reply(uuidmap[info['uuid']], info, packet, sock) + check_reply(uuidmap[info['uuid']], info, packet, sock, cfg) if __name__ == '__main__': @@ -328,4 +320,3 @@ if __name__ == '__main__': print(repr(info)) snoop(testsnoop) -