mirror of
https://github.com/xcat2/confluent.git
synced 2025-03-19 18:07:48 +00:00
Merge branch 'remote_discovery'
This commit is contained in:
commit
7751a71851
@ -57,12 +57,15 @@ def print_disco(options, session, currmac, outhandler, columns):
|
||||
|
||||
procinfo.update(tmpinfo)
|
||||
if 'Switch' in columns or 'Port' in columns:
|
||||
for tmpinfo in session.read(
|
||||
'/networking/macs/by-mac/{0}'.format(currmac)):
|
||||
if 'ports' in tmpinfo:
|
||||
# The api sorts so that the most specific available value
|
||||
# is last
|
||||
procinfo.update(tmpinfo['ports'][-1])
|
||||
if 'switch' in procinfo:
|
||||
procinfo['port'] = procinfo['switchport']
|
||||
else:
|
||||
for tmpinfo in session.read(
|
||||
'/networking/macs/by-mac/{0}'.format(currmac)):
|
||||
if 'ports' in tmpinfo:
|
||||
# The api sorts so that the most specific available value
|
||||
# is last
|
||||
procinfo.update(tmpinfo['ports'][-1])
|
||||
record = []
|
||||
for col in columns:
|
||||
rawcol = columnmapping[col]
|
||||
|
@ -256,7 +256,7 @@ node = {
|
||||
'so long as the node has no existing public key. '
|
||||
'"open" allows discovery even if a known public key '
|
||||
'is already stored',
|
||||
'validlist': ('manual', 'permissive', 'pxe', 'open'),
|
||||
'validlist': ('manual', 'permissive', 'pxe', 'open', 'verified'),
|
||||
},
|
||||
'info.note': {
|
||||
'description': 'A field used for administrators to make arbitrary '
|
||||
|
@ -217,7 +217,12 @@ def send_discovery_datum(info):
|
||||
if info['handler'] == pxeh:
|
||||
enrich_pxe_info(info)
|
||||
yield msg.KeyValueData({'nodename': info.get('nodename', '')})
|
||||
yield msg.KeyValueData({'ipaddrs': [_printable_ip(x) for x in addresses]})
|
||||
if not info.get('forwarder_server', None):
|
||||
yield msg.KeyValueData({'ipaddrs': [_printable_ip(x) for x in addresses]})
|
||||
switch = info.get('forwarder_server', None)
|
||||
if switch:
|
||||
yield msg.KeyValueData({'switch': switch})
|
||||
yield msg.KeyValueData({'switchport': info['port']})
|
||||
sn = info.get('serialnumber', '')
|
||||
mn = info.get('modelnumber', '')
|
||||
uuid = info.get('uuid', '')
|
||||
@ -429,6 +434,8 @@ def handle_api_request(configmanager, inputdata, operation, pathcomponents):
|
||||
return (msg.KeyValueData({'rescan': 'started'}),)
|
||||
|
||||
elif operation in ('update', 'create'):
|
||||
if pathcomponents == ['discovery', 'register']:
|
||||
return
|
||||
if 'node' not in inputdata:
|
||||
raise exc.InvalidArgumentException('Missing node name in input')
|
||||
mac = _get_mac_from_query(pathcomponents)
|
||||
@ -688,6 +695,8 @@ def detected(info):
|
||||
info['otheraddresses'] = set([])
|
||||
for i4addr in info.get('attributes', {}).get('ipv4-address', []):
|
||||
info['otheraddresses'].add(i4addr)
|
||||
for i4addr in info.get('attributes', {}).get('ipv4-addresses', []):
|
||||
info['otheraddresses'].add(i4addr)
|
||||
if handler and handler.https_supported and not handler.https_cert:
|
||||
if handler.cert_fail_reason == 'unreachable':
|
||||
log.log(
|
||||
@ -1144,7 +1153,15 @@ def discover_node(cfg, handler, info, nodename, manual):
|
||||
'pubkeys.tls_hardwaremanager attribute is cleared '
|
||||
'first'.format(nodename)})
|
||||
return False # With a permissive policy, do not discover new
|
||||
elif policies & set(('open', 'permissive')) or manual:
|
||||
elif policies & set(('open', 'permissive', 'verified')) or manual:
|
||||
if 'verified' in policies:
|
||||
if not handler.https_supported or not util.cert_matches(info['fingerprint'], handler.https_cert):
|
||||
log.log({'info': 'Detected replacement of {0} without verified '
|
||||
'fingerprint and discovery policy is setto verified, not '
|
||||
'doing discovery unless discovery.policy=open or '
|
||||
'pubkeys.tls_hardwaremanager attribute is cleared '
|
||||
'first'.format(nodename)})
|
||||
return False
|
||||
info['nodename'] = nodename
|
||||
if info['handler'] == pxeh:
|
||||
return do_pxe_discovery(cfg, handler, info, manual, nodename, policies)
|
||||
|
@ -12,6 +12,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import confluent.util as util
|
||||
import errno
|
||||
import eventlet
|
||||
import socket
|
||||
@ -31,6 +32,16 @@ class NodeHandler(object):
|
||||
self.info = info
|
||||
self.configmanager = configmanager
|
||||
targsa = [None]
|
||||
self.ipaddr = None
|
||||
self.relay_url = None
|
||||
self.relay_server = None
|
||||
self.web_ip = None
|
||||
self.web_port = None
|
||||
# if this is a remote registered component, prefer to use the agent forwarder
|
||||
if info.get('forwarder_url', False):
|
||||
self.relay_url = info['forwarder_url']
|
||||
self.relay_server = info['forwarder_server']
|
||||
return
|
||||
# first let us prefer LLA if possible, since that's most stable
|
||||
for sa in info['addresses']:
|
||||
if sa[0].startswith('fe80'):
|
||||
@ -103,11 +114,8 @@ class NodeHandler(object):
|
||||
def https_cert(self):
|
||||
if self._fp:
|
||||
return self._fp
|
||||
if ':' in self.ipaddr:
|
||||
ip = '[{0}]'.format(self.ipaddr)
|
||||
else:
|
||||
ip = self.ipaddr
|
||||
wc = webclient.SecureHTTPConnection(ip, verifycallback=self._savecert)
|
||||
ip, port = self.get_web_port_and_ip()
|
||||
wc = webclient.SecureHTTPConnection(ip, verifycallback=self._savecert, port=port)
|
||||
try:
|
||||
wc.connect()
|
||||
except IOError as ie:
|
||||
@ -123,3 +131,32 @@ class NodeHandler(object):
|
||||
self._certfailreason = 2
|
||||
return None
|
||||
return self._fp
|
||||
|
||||
def get_web_port_and_ip(self):
|
||||
if self.web_ip:
|
||||
return self.web_ip, self.web_port
|
||||
# get target ip and port, either direct or relay as applicable
|
||||
if self.relay_url:
|
||||
kv = util.TLSCertVerifier(self.configmanager, self.relay_server,
|
||||
'pubkeys.tls_hardwaremanager').verify_cert
|
||||
w = webclient.SecureHTTPConnection(self.relay_server, verifycallback=kv)
|
||||
relaycreds = self.configmanager.get_node_attributes(self.relay_server, 'secret.*', decrypt=True)
|
||||
relaycreds = relaycreds.get(self.relay_server, {})
|
||||
relayuser = relaycreds.get('secret.hardwaremanagementuser', {}).get('value', None)
|
||||
relaypass = relaycreds.get('secret.hardwaremanagementpassword', {}).get('value', None)
|
||||
if not relayuser or not relaypass:
|
||||
raise Exception('No credentials for {0}'.format(self.relay_server))
|
||||
w.set_basic_credentials(relayuser, relaypass)
|
||||
w.connect()
|
||||
w.request('GET', self.relay_url)
|
||||
r = w.getresponse()
|
||||
rb = r.read()
|
||||
if r.code != 302:
|
||||
raise Exception('Unexpected return from forwarder')
|
||||
newurl = r.getheader('Location')
|
||||
self.web_port = int(newurl.rsplit(':', 1)[-1][:-1])
|
||||
self.web_ip = self.relay_server
|
||||
else:
|
||||
self.web_port = 443
|
||||
self.web_ip = self.ipaddr
|
||||
return self.web_ip, self.web_port
|
||||
|
@ -135,7 +135,8 @@ class NodeHandler(bmchandler.NodeHandler):
|
||||
{nodename: {'hardwaremanagement.manager': self.ipaddr}})
|
||||
|
||||
def _webconfigcreds(self, username, password):
|
||||
wc = webclient.SecureHTTPConnection(self.ipaddr, 443, verifycallback=self._validate_cert)
|
||||
ip, port = self.get_web_port_and_ip()
|
||||
wc = webclient.SecureHTTPConnection(ip, port, verifycallback=self._validate_cert)
|
||||
wc.connect()
|
||||
authdata = { # start by trying factory defaults
|
||||
'user': 'USERID',
|
||||
|
@ -67,7 +67,8 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
return None
|
||||
|
||||
def scan(self):
|
||||
c = webclient.SecureHTTPConnection(self.ipaddr, 443,
|
||||
ip, port = self.get_web_port_and_ip()
|
||||
c = webclient.SecureHTTPConnection(ip, port,
|
||||
verifycallback=self.validate_cert)
|
||||
i = c.grab_json_response('/api/providers/logoninfo')
|
||||
modelname = i.get('items', [{}])[0].get('machine_name', None)
|
||||
@ -279,8 +280,9 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
isdefault = True
|
||||
errinfo = {}
|
||||
if self._wc is None:
|
||||
ip, port = self.get_web_port_and_ip()
|
||||
self._wc = webclient.SecureHTTPConnection(
|
||||
self.ipaddr, 443, verifycallback=self.validate_cert)
|
||||
ip, port, verifycallback=self.validate_cert)
|
||||
self._wc.connect()
|
||||
nodename = None
|
||||
if self.nodename:
|
||||
|
@ -382,10 +382,12 @@ def _find_service(service, target):
|
||||
if pi is not None:
|
||||
yield pi
|
||||
|
||||
def check_fish(urldata):
|
||||
def check_fish(urldata, port=443, verifycallback=None):
|
||||
if not verifycallback:
|
||||
verifycallback = lambda x: True
|
||||
url, data = urldata
|
||||
try:
|
||||
wc = webclient.SecureHTTPConnection(_get_svrip(data), 443, verifycallback=lambda x: True)
|
||||
wc = webclient.SecureHTTPConnection(_get_svrip(data), port, verifycallback=verifycallback)
|
||||
peerinfo = wc.grab_json_response(url)
|
||||
except socket.error:
|
||||
return None
|
||||
|
@ -1600,6 +1600,7 @@ class Disk(ConfluentMessage):
|
||||
'hotspare',
|
||||
'rebuilding',
|
||||
'online',
|
||||
'offline',
|
||||
])
|
||||
state_aliases = {
|
||||
'unconfigured bad': 'fault',
|
||||
|
@ -10,6 +10,7 @@ import eventlet.green.socket as socket
|
||||
import eventlet.green.subprocess as subprocess
|
||||
import confluent.discovery.handlers.xcc as xcc
|
||||
import confluent.discovery.handlers.tsm as tsm
|
||||
import confluent.discovery.core as disco
|
||||
import base64
|
||||
import hmac
|
||||
import hashlib
|
||||
@ -18,6 +19,10 @@ import json
|
||||
import os
|
||||
import time
|
||||
import yaml
|
||||
import confluent.discovery.protocols.ssdp as ssdp
|
||||
import eventlet
|
||||
webclient = eventlet.import_patched('pyghmi.util.webclient')
|
||||
|
||||
|
||||
currtz = None
|
||||
keymap = 'us'
|
||||
@ -112,7 +117,6 @@ def handle_request(env, start_response):
|
||||
start_response('401', [])
|
||||
yield 'Unauthorized'
|
||||
return
|
||||
|
||||
ea = cfg.get_node_attributes(nodename, ['crypted.selfapikey', 'deployment.apiarmed'])
|
||||
eak = ea.get(
|
||||
nodename, {}).get('crypted.selfapikey', {}).get('hashvalue', None)
|
||||
@ -152,6 +156,42 @@ def handle_request(env, start_response):
|
||||
operation = env['REQUEST_METHOD']
|
||||
if operation not in ('HEAD', 'GET') and 'CONTENT_LENGTH' in env and int(env['CONTENT_LENGTH']) > 0:
|
||||
reqbody = env['wsgi.input'].read(int(env['CONTENT_LENGTH']))
|
||||
if env['PATH_INFO'] == '/self/register_discovered':
|
||||
rb = json.loads(reqbody)
|
||||
if not rb.get('path', None):
|
||||
start_response('400 Bad Requst', [])
|
||||
yield 'Missing Path'
|
||||
return
|
||||
targurl = '/hubble/systems/by-port/{0}/webaccess'.format(rb['path'])
|
||||
tlsverifier = util.TLSCertVerifier(cfg, nodename, 'pubkeys.tls_hardwaremanager')
|
||||
wc = webclient.SecureHTTPConnection(nodename, 443, verifycallback=tlsverifier.verify_cert)
|
||||
relaycreds = cfg.get_node_attributes(nodename, 'secret.*', decrypt=True)
|
||||
relaycreds = relaycreds.get(nodename, {})
|
||||
relayuser = relaycreds.get('secret.hardwaremanagementuser', {}).get('value', None)
|
||||
relaypass = relaycreds.get('secret.hardwaremanagementpassword', {}).get('value', None)
|
||||
if not relayuser or not relaypass:
|
||||
raise Exception('No credentials for {0}'.format(nodename))
|
||||
wc.set_basic_credentials(relayuser, relaypass)
|
||||
wc.request('GET', targurl)
|
||||
rsp = wc.getresponse()
|
||||
_ = rsp.read()
|
||||
if rsp.status == 302:
|
||||
newurl = rsp.headers['Location']
|
||||
newhost, newport = newurl.replace('https://', '').split('/')[0].split(':')
|
||||
def verify_cert(certificate):
|
||||
hashval = base64.b64decode(rb['fingerprint'])
|
||||
if len(hashval) == 48:
|
||||
return hashlib.sha384(certificate).digest() == hashval
|
||||
raise Exception('Certificate validation failed')
|
||||
rb['addresses'] = [(newhost, newport)]
|
||||
rb['forwarder_url'] = targurl
|
||||
rb['forwarder_server'] = nodename
|
||||
rb['']
|
||||
ssdp.check_fish(('/DeviceDescription.json', rb), newport, verify_cert)
|
||||
disco.detected(rb)
|
||||
start_response('200 OK', [])
|
||||
yield 'Registered'
|
||||
return
|
||||
if env['PATH_INFO'] == '/self/bmcconfig':
|
||||
hmattr = cfg.get_node_attributes(nodename, 'hardwaremanagement.*')
|
||||
hmattr = hmattr.get(nodename, {})
|
||||
|
@ -146,12 +146,24 @@ def get_fingerprint(certificate, algo='sha512'):
|
||||
return 'sha256$' + hashlib.sha256(certificate).hexdigest()
|
||||
elif algo == 'sha512':
|
||||
return 'sha512$' + hashlib.sha512(certificate).hexdigest()
|
||||
elif algo == 'sha384':
|
||||
return 'sha384$' + hashlib.sha384(certificate).hexdigest()
|
||||
raise Exception('Unsupported fingerprint algorithm ' + algo)
|
||||
|
||||
|
||||
hashlens = {
|
||||
48: hashlib.sha384,
|
||||
64: hashlib.sha512,
|
||||
32: hashlib.sha256
|
||||
}
|
||||
|
||||
def cert_matches(fingerprint, certificate):
|
||||
if not fingerprint or not certificate:
|
||||
return False
|
||||
if '$' not in fingerprint:
|
||||
fingerprint = base64.b64decode(certificate)
|
||||
algo = hashlens[len(fingerprint)]
|
||||
return algo(certificate).digest() == fingerprint
|
||||
algo, _, fp = fingerprint.partition('$')
|
||||
newfp = None
|
||||
if algo in ('sha512', 'sha256'):
|
||||
|
Loading…
x
Reference in New Issue
Block a user