mirror of
https://github.com/xcat2/confluent.git
synced 2026-05-02 21:37:46 +00:00
Implement requisite functions to do chain SMM discovery
Hook into the neighbor data and lldp to identify SMMs. Still need to provide context to the XCCs based on the chassis-uuid property.
This commit is contained in:
@@ -279,12 +279,17 @@ node = {
|
||||
'description': 'The method used to perform operations such as power '
|
||||
'control, get sensor data, get inventory, and so on. '
|
||||
},
|
||||
'enclosure.manager': {
|
||||
'description': "The management device for this node's chassis",
|
||||
# 'appliesto': ['system'],
|
||||
},
|
||||
'enclosure.bay': {
|
||||
'description': 'The bay in the enclosure, if any',
|
||||
# 'appliesto': ['system'],
|
||||
},
|
||||
'enclosure.extends': {
|
||||
'description': 'When using an extendable enclosure, this is the node '
|
||||
'representing the manager that is one closer to the '
|
||||
'uplink.',
|
||||
},
|
||||
'enclosure.manager': {
|
||||
'description': "The management device for this node's chassis",
|
||||
# 'appliesto': ['system'],
|
||||
},
|
||||
|
||||
|
||||
@@ -75,7 +75,10 @@ import confluent.messages as msg
|
||||
import confluent.networking.macmap as macmap
|
||||
import confluent.noderange as noderange
|
||||
import confluent.util as util
|
||||
import eventlet
|
||||
import traceback
|
||||
webclient = eventlet.import_patched('pyghmi.util.webclient')
|
||||
|
||||
|
||||
import eventlet
|
||||
import eventlet.greenpool
|
||||
@@ -579,8 +582,8 @@ def detected(info):
|
||||
if handler.cert_fail_reason == 'unreachable':
|
||||
log.log(
|
||||
{
|
||||
'info': '{0} with hwaddr {1} is not reachable at {2}'
|
||||
''.format(
|
||||
'info': '{0} with hwaddr {1} is not reachable by https '
|
||||
'at address {2}'.format(
|
||||
handler.devname, info['hwaddr'], handler.ipaddr
|
||||
)})
|
||||
info['addresses'] = [x for x in info.get('addresses', []) if x != handler.ipaddr]
|
||||
@@ -626,6 +629,44 @@ def detected(info):
|
||||
unknown_info[info['hwaddr']] = info
|
||||
|
||||
|
||||
|
||||
def b64tohex(b64str):
|
||||
bd = base64.b64decode(b64str)
|
||||
return ''.join(['{0:02x}'.format(ord(x)) for x in bd])
|
||||
|
||||
|
||||
def get_chained_smm_name(nodename, cfg, handler, nl):
|
||||
# first we check to see if directly connected
|
||||
mycert = handler.https_cert
|
||||
fprints = macmap.get_node_fingerprints(nodename, cfg)
|
||||
for fprint in fprints:
|
||||
if util.cert_matches(fprint, mycert):
|
||||
# ok we have a direct match, it is this node
|
||||
return nodename
|
||||
# ok, unable to get it, need to traverse the chain from the beginning
|
||||
while nl:
|
||||
if len(nl) != 1:
|
||||
raise exc.InvalidArgumentException('Multiple enclosures trying to '
|
||||
'extend a single enclosure')
|
||||
cd = cfg.get_node_attributes(nodename, 'hardwaremanagement.manager')
|
||||
smmaddr = cd[nodename]['hardwaremanagement.manager']['value']
|
||||
cv = util.TLSCertVerifier(
|
||||
cfg, nodename, 'pubkeys.tls_hardwaremanager').verify_cert
|
||||
wc = webclient.SecureHTTPConnection(smmaddr, verifycallback=cv)
|
||||
neighs = wc.grab_json_response('/scripts/neighdata.json')
|
||||
for idx in (4, 5):
|
||||
if 'sha256' not in neighs[idx]:
|
||||
continue
|
||||
fprint = 'sha256$' + b64tohex(neighs[idx]['sha256'])
|
||||
if util.cert_matches(fprint, mycert):
|
||||
return nl[0]
|
||||
# advance down the chain by one and try again
|
||||
nodename = nl[0]
|
||||
nl = list(cfg.filter_node_attributes(
|
||||
'enclosure.extends=' + nodename))
|
||||
return None
|
||||
|
||||
|
||||
def get_nodename(cfg, handler, info):
|
||||
nodename = None
|
||||
maccount = None
|
||||
@@ -646,6 +687,21 @@ def get_nodename(cfg, handler, info):
|
||||
if not nodename: # as a last resort, search switch for info
|
||||
nodename, macinfo = macmap.find_nodeinfo_by_mac(info['hwaddr'], cfg)
|
||||
maccount = macinfo['maccount']
|
||||
if nodename:
|
||||
if handler.devname == 'SMM':
|
||||
nl = list(cfg.filter_node_attributes(
|
||||
'enclosure.extends=' + nodename))
|
||||
if not nl:
|
||||
# we reached the end of the chain without success
|
||||
return None, None
|
||||
# We found an SMM, and it's in a chain per configuration
|
||||
# we need to ask the switch for the fingerprint to see
|
||||
# if we have a match or not
|
||||
newnodename = get_chained_smm_name(nodename, cfg, handler,
|
||||
nl)
|
||||
if newnodename:
|
||||
return newnodename, None
|
||||
|
||||
if (nodename and
|
||||
not handler.discoverable_by_switch(macinfo['maccount'])):
|
||||
if handler.devname == 'SMM':
|
||||
@@ -693,6 +749,7 @@ def eval_node(cfg, handler, info, nodename, manual=False):
|
||||
if not handler.is_enclosure and nl:
|
||||
# The specified node is an enclosure (has nodes mapped to it), but
|
||||
# what we are talking to is *not* an enclosure
|
||||
# might be ambiguous, need to match chassis-uuid as well..
|
||||
if 'enclosure.bay' not in info:
|
||||
unknown_info[info['hwaddr']] = info
|
||||
info['discostatus'] = 'unidentified'
|
||||
@@ -740,6 +797,17 @@ def eval_node(cfg, handler, info, nodename, manual=False):
|
||||
# we can and did accurately discover by switch or in enclosure
|
||||
# but... is this really ok? could be on an upstream port or
|
||||
# erroneously put in the enclosure with no nodes yet
|
||||
# so first, see if the candidate node is a chain host
|
||||
if info['maccount']:
|
||||
# discovery happened through switch
|
||||
nl = list(cfg.filter_node_attributes(
|
||||
'enclosure.extends=' + nodename))
|
||||
if nl:
|
||||
# The candidate nodename is the head of a chain, we must
|
||||
# validate the smm certificate by the switch
|
||||
macmap.get_node_fingerprint(nodename, cfg)
|
||||
util.handler.cert_matches(fprint, handler.https_cert)
|
||||
return
|
||||
if (info['maccount'] and
|
||||
not handler.discoverable_by_switch(info['maccount'])):
|
||||
errorstr = 'The detected node {0} was detected using switch, ' \
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
import confluent.config.configmanager as cfm
|
||||
import base64
|
||||
import confluent.exceptions as exc
|
||||
import confluent.log as log
|
||||
import confluent.messages as msg
|
||||
@@ -118,6 +119,24 @@ def _lldpdesc_to_ifname(switchid, idx, desc):
|
||||
def _dump_neighbordatum(info):
|
||||
return [msg.KeyValueData(info)]
|
||||
|
||||
def b64tohex(b64str):
|
||||
bd = base64.b64decode(b64str)
|
||||
return ''.join(['{0:02x}'.format(ord(x)) for x in bd])
|
||||
|
||||
def get_fingerprint(switch, port, configmanager, portmatch):
|
||||
update_switch_data(switch, configmanager)
|
||||
for neigh in _neighbypeerid:
|
||||
info = _neighbypeerid[neigh]
|
||||
if neigh == '!!vintage' or info.get('switch', None) != switch:
|
||||
continue
|
||||
if 'peersha256fingerprint' not in info:
|
||||
continue
|
||||
if info.get('switch', None) != switch:
|
||||
continue
|
||||
if portmatch(info.get('port'), port):
|
||||
return 'sha256$' + b64tohex(info['peersha256fingerprint'])
|
||||
return None
|
||||
|
||||
|
||||
def _extract_extended_desc(info, source, integritychecked):
|
||||
source = str(source)
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
# this module will provide mac to switch and full 'ifName' label
|
||||
# This functionality is restricted to the null tenant
|
||||
from confluent.networking.lldp import _handle_neighbor_query
|
||||
from confluent.networking.lldp import _handle_neighbor_query, get_fingerprint
|
||||
from confluent.networking.netutil import get_switchcreds, list_switches, get_portnamemap
|
||||
|
||||
if __name__ == '__main__':
|
||||
@@ -378,6 +378,17 @@ def handle_api_request(configmanager, inputdata, operation, pathcomponents):
|
||||
operation, '/'.join(pathcomponents)))
|
||||
|
||||
|
||||
def get_node_fingerprints(nodename, configmanager):
|
||||
cfg = configmanager.get_node_attributes(nodename, ['net*.switch',
|
||||
'net*.switchport'])
|
||||
for attrkey in cfg[nodename]:
|
||||
if attrkey.endswith('switch'):
|
||||
switch = cfg[nodename][attrkey]['value']
|
||||
port = cfg[nodename][attrkey + 'port']['value']
|
||||
yield get_fingerprint(switch, port, configmanager,
|
||||
_namesmatch)
|
||||
|
||||
|
||||
def handle_read_api_request(pathcomponents, configmanager):
|
||||
# TODO(jjohnson2): discovery core.py api handler design, apply it here
|
||||
# to make this a less tangled mess as it gets extended
|
||||
|
||||
@@ -100,9 +100,11 @@ def monotonic_time():
|
||||
|
||||
|
||||
def get_fingerprint(certificate, algo='sha512'):
|
||||
if algo != 'sha512':
|
||||
raise Exception("TODO: Non-sha512")
|
||||
return 'sha512$' + hashlib.sha512(certificate).hexdigest()
|
||||
if algo == 'sha256':
|
||||
return 'sha256$' + hashlib.sha256(certificate).hexdigest()
|
||||
elif algo == 'sha512':
|
||||
return 'sha512$' + hashlib.sha512(certificate).hexdigest()
|
||||
raise Exception('Unsupported fingerprint algorithm ' + algo)
|
||||
|
||||
|
||||
def cert_matches(fingerprint, certificate):
|
||||
@@ -110,8 +112,8 @@ def cert_matches(fingerprint, certificate):
|
||||
return False
|
||||
algo, _, fp = fingerprint.partition('$')
|
||||
newfp = None
|
||||
if algo == 'sha512':
|
||||
newfp = get_fingerprint(certificate)
|
||||
if algo in ('sha512', 'sha256'):
|
||||
newfp = get_fingerprint(certificate, algo)
|
||||
return newfp and fingerprint == newfp
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user