2
0
mirror of https://github.com/xcat2/confluent.git synced 2026-09-29 00:31:09 +00:00

Compare commits

...

23 Commits

Author SHA1 Message Date
Jarrod Johnson 8897842fc4 Fix SMM handler when None bmc
This fixes a common scenario for using fe80 collection
2019-07-26 13:50:59 -04:00
Jarrod Johnson a251a538b0 Improve SMM discovery
SMM discovery behavior has seemingly gotten more picky with time.
First switch to an IPMI-free if the user has custom password.  The
web based approach is much less problematic than SMM IPMI stack in
this context.

If user specifies they want to use default credentials, we have
no choice but to use IPMI.  Omit things and shuffle order of operations
to mitigate problems.  It isn't perfect, but it does work eventually.
2019-07-26 09:25:19 -04:00
Jarrod Johnson 480a747dcf Merge branch 'master' of github.com:jjohnson42/confluent 2019-07-25 13:10:08 -04:00
Jarrod Johnson af025f7304 Present log name when provided in nodeeventlog
Some managers combine logs, allow disambiguation through including in output.
2019-07-25 13:09:52 -04:00
Jarrod Johnson 21edd82177 Extend generic catches in redfish/ipmi
Have it provide more detailed error data at
a glance to short out some debug requirements.
2019-07-25 09:28:55 -04:00
Jarrod Johnson 8641885f86 Fix handling of socket error with neighbor
If a system is half up, a different sort of timeout is given.
Handle this and also preserve the original exception better
if not one of these two.
2019-07-25 08:52:47 -04:00
Jarrod Johnson 64cc2416d1 Fix list argument 2019-07-24 15:37:01 -04:00
Jarrod Johnson 2787e1d862 Present more data about missing entries from assign csv
When doing an assign on csv, present all the missing entries
rather than stopping on the first.
2019-07-23 11:57:35 -04:00
Jarrod Johnson 514a121c15 Print license install errors when provided
When the service gives errors installing keys,
relay them to the user.
2019-07-22 11:21:31 -04:00
Jarrod Johnson 00ce48b046 Fix behavior for bad nodelicense arguments
Correctly show help and exit if unrecognized
parameter.
2019-07-22 09:27:31 -04:00
Jarrod Johnson 44929e7975 Fix printing of unicode to pipe
nodesensors would have a unicode error on pipe output.
2019-07-19 15:36:43 -04:00
Jarrod Johnson da82fef0cb Have nodelist use nodeattrib completion
Since nodelist may also show attribute values, use same completion function.
2019-07-19 14:22:19 -04:00
Jarrod Johnson efcac0b181 Fix nodesensors -n with csv 2019-07-19 14:12:40 -04:00
Jarrod Johnson 46e2f53018 Always use GCM on encrypt
Continue to support read using the old scheme, but even when
an integrity key is available, only use it to aid in decrypting
classic format, and always write in new format.
2019-07-17 08:51:06 -04:00
Jarrod Johnson cf51928b3d Implement GCM and close gap in the HMAC
When generating key material from scratch, skip HMAC and
activate GCM mode.

When using existing CBC/HMAC keys, start covering the IV value
in the HMAC.  For compatibility, HMAC validity is checked with
and without IV.
2019-07-16 18:32:23 -04:00
Jarrod Johnson 151ba2e567 Add error messages to nodeattrib clear
nodeattrib -c was not reporting useful error information.
2019-07-15 13:28:09 -04:00
Jarrod Johnson bc87077397 Fix error handling and consistency in networking
by-port specification was inconsistent and unhelpful in error between macmap and lldp.
2019-07-15 11:07:59 -04:00
Jarrod Johnson a77b65737e Suppress usage on nodefirmware error
It is very bizarre to have the usage output
after an error.
2019-07-12 12:10:22 -04:00
Jarrod Johnson 19c2963cf9 Fix reassign with SMM
SMM validation assumes an earlier connection, fix so that it will accept the certificate
if no certificate expected yet.
2019-07-11 15:55:20 -04:00
Jarrod Johnson a0ea8eeae3 Fix nodediscover support of bmc_gateway
The command expected spaces, but documentation
said underscore, support both old csvs and doc
compliant csvs.
2019-07-11 15:36:31 -04:00
Jarrod Johnson 6ad1ce4df5 Back off concurrent retrieval.
Some BMCs are incapable of handling concurrent requests.
This is a blow particularly to high latency management given
Redfish's tendency to require a lot of resource fetches, but
we don't have a particularly discoverable strategy for knowing
in advance whether an implementation is up for some optimization.
2019-07-11 11:41:42 -04:00
Jarrod Johnson c53264872a Fix scenario with undefined passlength
If passwordcomplexity were requested without a passwordlength,
things would be a problem.
2019-07-11 10:53:43 -04:00
Jarrod Johnson d9f2c0b266 Correct mistake in setting names 2019-07-11 10:40:49 -04:00
15 changed files with 310 additions and 134 deletions
+7 -2
View File
@@ -92,6 +92,8 @@ def process_header(header):
fields.append('hardwaremanagement.manager')
elif datum in ('bmc gateway', 'xcc gateway', 'imm gateway'):
fields.append('net.bmc.ipv4_gateway')
elif datum in ('bmc_gateway', 'xcc_gateway', 'imm_gateway'):
fields.append('net.bmc.ipv4_gateway')
elif datum in ('bmcuser', 'username', 'user'):
fields.append('secret.hardwaremanagementuser')
elif datum in ('bmcpass', 'password', 'pass'):
@@ -152,6 +154,7 @@ def import_csv(options, session):
for field in fields:
if field in unique_fields:
unique_data[field] = set([])
broken = False
for record in records:
currfields = list(fields)
nodedatum = {}
@@ -168,14 +171,16 @@ def import_csv(options, session):
nodedatum[currfield] = datum
if not datum_complete(nodedatum):
sys.exit(1)
if not search_record(nodedatum, options, session):
if not search_record(nodedatum, options, session) and not broken:
blocking_scan(session)
if not search_record(nodedatum, options, session):
sys.stderr.write(
"Could not match the following data: " +
repr(nodedatum) + '\n')
sys.exit(1)
broken = True
nodedata.append(nodedatum)
if broken:
sys.exit(1)
for datum in nodedata:
maclist = search_record(datum, options, session)
datum = datum_to_attrib(datum)
+4 -2
View File
@@ -1,7 +1,7 @@
#!/usr/bin/python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015-2017 Lenovo
# Copyright 2015-2019 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -65,6 +65,8 @@ def format_event(evt):
display = dt.strptime(evt['timestamp'], '%Y-%m-%dT%H:%M:%S')
retparts.append(display.strftime('%m/%d/%Y %H:%M:%S'))
dscparts = []
if evt.get('log_id', None):
retparts.append(evt['log_id'] + ':')
if 'component_type' in evt and evt['component_type'] is not None:
dscparts.append(evt['component_type'])
if 'component' in evt and evt['component'] is not None:
@@ -102,4 +104,4 @@ for rsp in func('/noderange/{0}/events/hardware/log'.format(noderange)):
if 'events' in thisdata:
evtdata = thisdata['events']
for evt in evtdata:
print '{0}: {1}'.format(node, format_event(evt))
print('{0}: {1}'.format(node, format_event(evt)))
+1 -1
View File
@@ -151,7 +151,7 @@ def show_firmware(session):
for prefix in inv:
firmware_shown = True
printfirm(node, prefix, inv[prefix])
if not firmware_shown:
if not firmware_shown and not exitcode:
argparser.print_help()
+8 -3
View File
@@ -50,8 +50,9 @@ try:
downdir = args[2]
elif args[1] == 'delete':
delete = args[2]
else:
components = ['all']
elif args[1] != 'list':
argparser.print_help()
sys.exit(1)
except IndexError:
argparser.print_help()
sys.exit(1)
@@ -69,7 +70,11 @@ def install_license(session, filename):
filename = os.path.abspath(filename)
instargs = {'filename': filename}
for res in session.create(resource, instargs):
pass # print(repr(res))
for node in res.get('databynode', []):
if 'error' in res['databynode'][node]:
sys.stderr.write('{0}: {1}\n'.format(
node, res['databynode'][node]['error']))
sys.exit(res['databynode'][node].get('errorcode', 1))
show_licenses(session)
+3 -3
View File
@@ -150,7 +150,7 @@ def sensorpass(showout=True, appendtime=False):
showval += ' @' + time.strftime(
'%Y-%m-%dT%H:%M:%S')
print(u'{0}: {1}:{2}'.format(
node, sensedata['name'], showval))
node, sensedata['name'], showval).encode('utf8'))
sys.stdout.flush()
return resultdata
@@ -158,7 +158,7 @@ def sensorpass(showout=True, appendtime=False):
def format_csv(csvwriter, orderedsensors, resdata, showtime=True):
for nodekey in resdata:
if showtime:
if showtime.is_integer():
if isinstance(showtime, int):
rowdata = [time.strftime('%Y-%m-%dT%H:%M:%S'), nodekey]
else:
rowdata = [time.strftime('%Y-%m-%dT%H:%M:%S.') +
@@ -199,7 +199,7 @@ def main():
orderedsensors.append(name)
orderedsensors.sort()
for name in orderedsensors:
headernames.append(sensorheaders[name])
headernames.append(sensorheaders[name].encode('utf-8'))
if options.csv:
linebyline = False
csvwriter = csv.writer(sys.stdout)
+1 -1
View File
@@ -277,7 +277,7 @@ complete -F _confluent_ng_completion nodegroupremove
complete -F _confluent_nr_completion nodehealth
complete -F _confluent_nodeidentify_completion nodeidentify
complete -F _confluent_nr_completion nodeinventory
complete -F _confluent_nr_completion nodelist
complete -F _confluent_nodeattrib_completion nodelist
complete -F _confluent_nodemedia_completion nodemedia
complete -F _confluent_nodepower_completion nodepower
complete -F _confluent_nr_completion noderemove
@@ -145,10 +145,13 @@ def _parse_key(keydata, password=None):
if keydata.startswith('*unencrypted:'):
return base64.b64decode(keydata[13:])
elif password:
salt, iv, crypt, hmac = [base64.b64decode(x)
cryptbits = [base64.b64decode(x)
for x in keydata.split('!')]
salt, iv, crypt, hmac = cryptbits[:4]
privkey, integkey = _derive_keys(password, salt)
return decrypt_value([iv, crypt, hmac], privkey, integkey)
if len(cryptbits) > 4:
integkey = None
return decrypt_value(cryptbits[1:], privkey, integkey)
raise(exc.LockedCredentials(
"Passphrase protected secret requires password"))
@@ -157,7 +160,7 @@ def _format_key(key, password=None):
if password is not None:
salt = os.urandom(32)
privkey, integkey = _derive_keys(password, salt)
cval = crypt_value(key, key=privkey, integritykey=integkey)
cval = crypt_value(key, key=privkey) # , integritykey=integkey)
return {"passphraseprotected": (salt,) + cval}
else:
return {"unencryptedvalue": key}
@@ -356,11 +359,11 @@ def init_masterkey(password=None, autogen=True):
if cfgn:
_masterintegritykey = _get_protected_key(cfgn, password,
'master_integrity_key')
elif autogen:
_masterintegritykey = os.urandom(64)
set_global('master_integrity_key', _format_key(
_masterintegritykey,
password=password))
#elif autogen:
# _masterintegritykey = os.urandom(64)
# set_global('master_integrity_key', _format_key(
# _masterintegritykey,
# password=password))
def _push_rpc(stream, payload):
@@ -373,25 +376,34 @@ def _push_rpc(stream, payload):
def decrypt_value(cryptvalue,
key=None,
integritykey=None):
iv, cipherdata, hmac = cryptvalue
# for future reference, if cryptvalue len == 3, then cbc+hmac, 4 includes version
iv, cipherdata, hmac = cryptvalue[:3]
if key is None and integritykey is None:
if _masterkey is None or _masterintegritykey is None:
if _masterkey is None:
init_masterkey(autogen=False)
key = _masterkey
integritykey = _masterintegritykey
check_hmac = HMAC.new(integritykey, cipherdata, SHA256).digest()
if hmac != check_hmac:
raise Exception("bad HMAC value on crypted value")
decrypter = AES.new(key, AES.MODE_CBC, iv)
value = decrypter.decrypt(cipherdata)
padsize = ord(value[-1])
pad = value[-padsize:]
# Note that I cannot grasp what could be done with a subliminal
# channel in padding in this case, but check the padding anyway
for padbyte in pad:
if ord(padbyte) != padsize:
raise Exception("bad padding in encrypted value")
return value[0:-padsize]
if len(cryptvalue) == 3:
check_hmac = HMAC.new(integritykey, cipherdata, SHA256).digest()
if hmac != check_hmac:
check_hmac = HMAC.new(integritykey, cipherdata + iv, SHA256).digest()
if hmac != check_hmac:
raise Exception("bad HMAC value on crypted value")
decrypter = AES.new(key, AES.MODE_CBC, iv)
value = decrypter.decrypt(cipherdata)
padsize = ord(value[-1])
pad = value[-padsize:]
# Note that I cannot grasp what could be done with a subliminal
# channel in padding in this case, but check the padding anyway
for padbyte in pad:
if ord(padbyte) != padsize:
raise Exception("bad padding in encrypted value")
return value[0:-padsize]
else:
decrypter = AES.new(key, AES.MODE_GCM, nonce=iv)
value = decrypter.decrypt(cipherdata)
decrypter.verify(hmac)
return value
def fixup_attribute(attrname, attrval):
@@ -443,22 +455,18 @@ def crypt_value(value,
# encrypt given value
# PKCS7 is the padding scheme to employ, if no padded needed, pad with 16
# check HMAC prior to attempting decrypt
if key is None or integritykey is None:
if _masterkey is None or _masterintegritykey is None:
hmac = None
if key is None:
if _masterkey is None:
init_masterkey()
key = _masterkey
integritykey = _masterintegritykey
iv = os.urandom(16)
crypter = AES.new(key, AES.MODE_CBC, iv)
neededpad = 16 - (len(value) % 16)
pad = chr(neededpad) * neededpad
value += pad
iv = os.urandom(12)
crypter = AES.new(key, AES.MODE_GCM, nonce=iv)
try:
cryptval = crypter.encrypt(value)
cryptval, hmac = crypter.encrypt_and_digest(value)
except TypeError:
cryptval = crypter.encrypt(value.encode('utf-8'))
hmac = HMAC.new(integritykey, cryptval, SHA256).digest()
return iv, cryptval, hmac
cryptval, hmac = crypter.encrypt_and_digest(value.encode('utf-8'))
return iv, cryptval, hmac, '\x02'
def _load_dict_from_dbm(dpath, tdb):
@@ -1617,7 +1625,7 @@ class ConfigManager(object):
del attribmap[group][attr]
if 'noderange' in attribmap[group]:
if len(attribmap[group]) > 1:
raise ValueError('noderange attribute must be set by itself')
raise ValueError('noderange attribute must be set by itself')
for attr in attribmap[group]:
if attr in _attraliases:
newattr = _attraliases[attr]
@@ -2427,7 +2435,7 @@ def _restore_keys(jsond, password, newpassword=None, sync=True):
def _dump_keys(password, dojson=True):
if _masterkey is None or _masterintegritykey is None:
if _masterkey is None:
init_masterkey()
cryptkey = _format_key(_masterkey, password=password)
if 'passphraseprotected' in cryptkey:
@@ -2436,14 +2444,16 @@ def _dump_keys(password, dojson=True):
else:
cryptkey = '*unencrypted:{0}'.format(base64.b64encode(
cryptkey['unencryptedvalue']))
integritykey = _format_key(_masterintegritykey, password=password)
if 'passphraseprotected' in integritykey:
integritykey = '!'.join(map(base64.b64encode,
integritykey['passphraseprotected']))
else:
integritykey = '*unencrypted:{0}'.format(base64.b64encode(
integritykey['unencryptedvalue']))
keydata = {'cryptkey': cryptkey, 'integritykey': integritykey}
keydata = {'cryptkey': cryptkey}
if _masterintegritykey is not None:
integritykey = _format_key(_masterintegritykey, password=password)
if 'passphraseprotected' in integritykey:
integritykey = '!'.join(map(base64.b64encode,
integritykey['passphraseprotected']))
else:
integritykey = '*unencrypted:{0}'.format(base64.b64encode(
integritykey['unencryptedvalue']))
keydata['integritykey'] = integritykey
if dojson:
return json.dumps(keydata, sort_keys=True, indent=4, separators=(',', ': '))
return keydata
@@ -56,27 +56,27 @@ class NodeHandler(generic.NodeHandler):
def config(self, nodename, reset=False):
self._bmcconfig(nodename, reset)
def _bmcconfig(self, nodename, reset=False, customconfig=None):
def _bmcconfig(self, nodename, reset=False, customconfig=None, vc=None):
# TODO(jjohnson2): set ip parameters, user/pass, alert cfg maybe
# In general, try to use https automation, to make it consistent
# between hypothetical secure path and today.
creds = self.configmanager.get_node_attributes(
nodename,
['secret.hardwaremanagementuser',
'secret.hardwaremanagementpassword'], decrypt=True)
user = creds.get(nodename, {}).get(
'secret.hardwaremanagementuser', {}).get('value', None)
passwd = creds.get(nodename, {}).get(
'secret.hardwaremanagementpassword', {}).get('value', None)
try:
ic = self._get_ipmicmd()
passwd = DEFAULT_PASS
except pygexc.IpmiException as pi:
creds = self.configmanager.get_node_attributes(
nodename,
['secret.hardwaremanagementuser',
'secret.hardwaremanagementpassword'], decrypt=True)
user = creds.get(nodename, {}).get(
'secret.hardwaremanagementuser', {}).get('value', None)
havecustomcreds = False
if user is not None and user != DEFAULT_USER:
havecustomcreds = True
else:
user = DEFAULT_USER
passwd = creds.get(nodename, {}).get(
'secret.hardwaremanagementpassword', {}).get('value', None)
if passwd is not None and passwd != DEFAULT_PASS:
havecustomcreds = True
else:
@@ -85,8 +85,8 @@ class NodeHandler(generic.NodeHandler):
ic = self._get_ipmicmd(user, passwd)
else:
raise
if customconfig:
customconfig(ic)
if vc:
ic.register_key_handler(vc)
currusers = ic.get_users()
lanchan = ic.get_network_channel()
userdata = ic.xraw_command(netfn=6, command=0x44, data=(lanchan,
@@ -106,6 +106,55 @@ class NodeHandler(generic.NodeHandler):
raise exc.TargetEndpointBadCredentials(
'secret.hardwaremanagementuser and/or '
'secret.hardwaremanagementpassword was not configured')
newuser = cd['secret.hardwaremanagementuser']['value']
newpass = cd['secret.hardwaremanagementpassword']['value']
for uid in currusers:
if currusers[uid]['name'] == newuser:
# Use existing account that has been created
newuserslot = uid
if newpass != passwd: # don't mess with existing if no change
ic.set_user_password(newuserslot, password=newpass)
ic = self._get_ipmicmd(user, passwd)
if vc:
ic.register_key_handler(vc)
break
else:
newuserslot = lockedusers + 1
if newuserslot < 2:
newuserslot = 2
if newpass != passwd: # don't mess with existing if no change
ic.set_user_password(newuserslot, password=newpass)
ic.set_user_name(newuserslot, newuser)
if havecustomcreds:
ic = self._get_ipmicmd(user, passwd)
if vc:
ic.register_key_handler(vc)
#We are remote operating on the account we are
#using, no need to try to set user access
#ic.set_user_access(newuserslot, lanchan,
# privilege_level='administrator')
# Now to zap others
for uid in currusers:
if uid != newuserslot:
if uid <= lockedusers: # we cannot delete, settle for disable
ic.disable_user(uid, 'disable')
else:
# lead with the most critical thing, removing user access
ic.set_user_access(uid, channel=None, callback=False,
link_auth=False, ipmi_msg=False,
privilege_level='no_access')
# next, try to disable the password
ic.set_user_password(uid, mode='disable', password=None)
# ok, now we can be less paranoid
try:
ic.user_delete(uid)
except pygexc.IpmiException as ie:
if ie.ipmicode != 0xd5: # some response to the 0xff
# name...
# the user will remain, but that is life
raise
if customconfig:
customconfig(ic)
if ('hardwaremanagement.manager' in cd and
cd['hardwaremanagement.manager']['value'] and
not cd['hardwaremanagement.manager']['value'].startswith(
@@ -134,44 +183,6 @@ class NodeHandler(generic.NodeHandler):
else:
raise exc.TargetEndpointUnreachable(
'hardwaremanagement.manager must be set to desired address')
newuser = cd['secret.hardwaremanagementuser']['value']
newpass = cd['secret.hardwaremanagementpassword']['value']
for uid in currusers:
if currusers[uid]['name'] == newuser:
# Use existing account that has been created
newuserslot = uid
if newpass != passwd: # don't mess with existing if no change
ic.set_user_password(newuserslot, password=newpass)
break
else:
newuserslot = lockedusers + 1
if newuserslot < 2:
newuserslot = 2
if newpass != passwd: # don't mess with existing if no change
ic.set_user_password(newuserslot, password=newpass)
ic.set_user_name(newuserslot, newuser)
ic.set_user_access(newuserslot, lanchan,
privilege_level='administrator')
# Now to zap others
for uid in currusers:
if uid != newuserslot:
if uid <= lockedusers: # we cannot delete, settle for disable
ic.disable_user(uid, 'disable')
else:
# lead with the most critical thing, removing user access
ic.set_user_access(uid, channel=None, callback=False,
link_auth=False, ipmi_msg=False,
privilege_level='no_access')
# next, try to disable the password
ic.set_user_password(uid, mode='disable', password=None)
# ok, now we can be less paranoid
try:
ic.user_delete(uid)
except pygexc.IpmiException as ie:
if ie.ipmicode != 0xd5: # some response to the 0xff
# name...
# the user will remain, but that is life
raise
if reset:
ic.reset_bmc()
return ic
@@ -13,7 +13,15 @@
# limitations under the License.
import confluent.discovery.handlers.bmc as bmchandler
import confluent.exceptions as exc
import pyghmi.util.webclient as webclient
import struct
import urllib
import eventlet.support.greendns
import confluent.netutil as netutil
getaddrinfo = eventlet.support.greendns.getaddrinfo
from xml.etree.ElementTree import fromstring
def fixuuid(baduuid):
# SMM dumps it out in hex
@@ -26,7 +34,7 @@ def fixuuid(baduuid):
class NodeHandler(bmchandler.NodeHandler):
is_enclosure = True
devname = 'SMM'
maxmacs = 5 # support an enclosure, but try to avoid catching daisy chain
maxmacs = 6 # support an enclosure, but try to avoid catching daisy chain
def scan(self):
# the UUID is in a weird order, fix it up to match
@@ -40,9 +48,11 @@ class NodeHandler(bmchandler.NodeHandler):
# Assumption is by the time we call config, that discovery core has
# vetted self._fp. Our job here then is just to make sure that
# the currect connection matches the previously saved cert
if not self._fp: # circumstances are that we haven't validated yet
self._fp = certificate
return certificate == self._fp
def set_password_policy(self, ic):
def _webconfigrules(self, wc):
rules = []
for rule in self.ruleset.split(','):
if '=' not in rule:
@@ -60,10 +70,95 @@ class NodeHandler(bmchandler.NodeHandler):
rules.append('passwordReuseCheckNum:' + value)
if rules:
apirequest = 'set={0}'.format(','.join(rules))
ic.register_key_handler(self._validate_cert)
ic.oem_init()
ic._oem.smmhandler.wc.request('POST', '/data', apirequest)
ic._oem.smmhandler.wc.getresponse().read()
wc.request('POST', '/data', apirequest)
wc.getresponse().read()
def _webconfignet(self, wc, nodename):
cfg = self.configmanager
cd = cfg.get_node_attributes(
nodename, ['hardwaremanagement.manager'])
smmip = cd.get(nodename, {}).get('hardwaremanagement.manager', {}).get('value', None)
if smmip and ':' not in smmip:
smmip = getaddrinfo(smmip, 0)[0]
smmip = smmip[-1][0]
if smmip and ':' in smmip:
raise exc.NotImplementedException('IPv6 not supported')
netconfig = netutil.get_nic_config(cfg, nodename, ip=smmip)
netmask = netutil.cidr_to_mask(netconfig['prefix'])
setdata = 'set=ifIndex:0,v4DHCPEnabled:0,v4IPAddr:{0},v4NetMask:{1}'.format(smmip, netmask)
gateway = netconfig.get('ipv4_gateway', None)
if gateway:
setdata += ',v4Gateway:{0}'.format(gateway)
wc.request('POST', '/data', setdata)
rsp = wc.getresponse()
rspdata = rsp.read()
if '<statusCode>0' not in rspdata:
raise Exception("Error configuring SMM Network")
return
if smmip and ':' in smmip and not smmip.startswith('fe80::'):
raise exc.NotImplementedException('IPv6 configuration TODO')
if self.ipaddr.startswith('fe80::'):
cfg.set_node_attributes(
{nodename: {'hardwaremanagement.manager': self.ipaddr}})
def _webconfigcreds(self, username, password):
wc = webclient.SecureHTTPConnection(self.ipaddr, 443, verifycallback=self._validate_cert)
wc.connect()
authdata = { # start by trying factory defaults
'user': 'USERID',
'password': 'PASSW0RD',
}
headers = {'Connection': 'keep-alive', 'Content-Type': 'application/x-www-form-urlencoded'}
wc.request('POST', '/data/login', urllib.urlencode(authdata), headers)
rsp = wc.getresponse()
rspdata = rsp.read()
if 'authResult>0' not in rspdata:
# default credentials are refused, try with the actual
authdata['user'] = username
authdata['password'] = password
wc.request('POST', '/data/login', urllib.urlencode(authdata), headers)
rsp = wc.getresponse()
rspdata = rsp.read()
if 'renew_account' in rspdata:
raise Exception('Configured password has expired')
if 'authResult>0' not in rspdata:
raise Exception('Unknown username/password on SMM')
tokens = fromstring(rspdata)
st2 = tokens.findall('st2')[0].text
wc.set_header('ST2', st2)
return wc
if 'renew_account' in rspdata:
passwdchange = {'oripwd': 'PASSW0RD', 'newpwd': password}
tokens = fromstring(rspdata)
st2 = tokens.findall('st2')[0].text
wc.set_header('ST2', st2)
wc.request('POST', '/data/changepwd', urllib.urlencode(passwdchange))
rsp = wc.getresponse()
rspdata = rsp.read()
authdata['password'] = password
wc.request('POST', '/data/login', urllib.urlencode(authdata), headers)
rsp = wc.getresponse()
rspdata = rsp.read()
if 'authResult>0' in rspdata:
tokens = fromstring(rspdata)
st2 = tokens.findall('st2')[0].text
wc.set_header('ST2', st2)
if username == 'USERID':
return wc
wc.request('POST', '/data', 'set=user(2,1,{0},511,,4,15,0)'.format(username))
rsp = wc.getresponse()
rspdata = rsp.read()
wc.request('POST', '/data/logout')
rsp = wc.getresponse()
rspdata = rsp.read()
authdata['user'] = username
wc.request('POST', '/data/login', urllib.urlencode(authdata, headers))
rsp = wc.getresponse()
rspdata = rsp.read()
tokens = fromstring(rspdata)
st2 = tokens.findall('st2')[0].text
wc.set_header('ST2', st2)
return wc
def config(self, nodename):
# SMM for now has to reset to assure configuration applies
@@ -71,7 +166,25 @@ class NodeHandler(bmchandler.NodeHandler):
nodename, 'discovery.passwordrules')
self.ruleset = dpp.get(nodename, {}).get(
'discovery.passwordrules', {}).get('value', '')
ic = self._bmcconfig(nodename, customconfig=self.set_password_policy)
creds = self.configmanager.get_node_attributes(
nodename,
['secret.hardwaremanagementuser',
'secret.hardwaremanagementpassword'], decrypt=True)
username = creds.get(nodename, {}).get(
'secret.hardwaremanagementuser', {}).get('value', 'USERID')
passwd = creds.get(nodename, {}).get(
'secret.hardwaremanagementpassword', {}).get('value', 'PASSW0RD')
if passwd == 'PASSW0RD' and self.ruleset:
raise Exception('Cannot support default password and setting password rules at same time')
if passwd == 'PASSW0RD':
# We must avoid hitting the web interface due to forced password change, best effert
self._bmcconfig(nodename)
else:
# Switch to full web based configuration, to mitigate risks with the SMM
wc = self._webconfigcreds(username, passwd)
self._webconfigrules(wc)
self._webconfignet(wc, nodename)
# notes for smm:
# POST to:
@@ -86,4 +199,4 @@ class NodeHandler(bmchandler.NodeHandler):
# with body user=USERID&password=Passw0rd!4321
# yields:
# <?xml version="1.0" encoding="UTF-8"?><root> <status>ok</status> <authResult>0</authResult> <forwardUrl>index.html</forwardUrl> </root>
# note forwardUrl, if password change needed, will indicate something else
# note forwardUrl, if password change needed, will indicate something else
+6 -3
View File
@@ -1,7 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 IBM Corporation
# Copyright 2015-2017 Lenovo
# Copyright 2015-2019 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -33,10 +33,12 @@ valid_health_values = set([
'unknown',
])
passcomplexity = cfgfile.get_option('policy', 'passwordminlength')
passminlength = cfgfile.get_option('policy', 'minpasswordlength')
passcomplexity = cfgfile.get_option('policy', 'passwordcomplexity')
passminlength = cfgfile.get_option('policy', 'passwordminlength')
if passminlength:
passminlength = int(passminlength)
else:
passminlength = 0
if passcomplexity:
passcomplexity = int(passcomplexity)
else:
@@ -1247,6 +1249,7 @@ class EventCollection(ConfluentMessage):
'timestamp': event.get('timestamp', None),
'message': event.get('message', None),
'record_id': event.get('record_id', None),
'log_id': event.get('log_id', None),
}
if event['severity'] not in valid_health_values:
raise exc.NotImplementedException(
@@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2016, 2017 Lenovo
# Copyright 2016-2019 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -105,7 +105,11 @@ def close_enough(fuzz, literal):
if fuzz == literal:
return True
fuzz = '^' + fuzz.replace('-', '[/: -]') + '$'
matcher = re.compile(fuzz)
try:
matcher = re.compile(fuzz)
except Exception:
raise exc.InvalidArgumentException(
'Invalid regular expression specified')
return bool(matcher.match(literal))
@@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2016-2017 Lenovo
# Copyright 2016-2019 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -380,7 +380,7 @@ def handle_api_request(configmanager, inputdata, operation, pathcomponents):
if (operation in ('update', 'create') and
pathcomponents == ['networking', 'macs', 'rescan']):
if inputdata != {'rescan': 'start'}:
raise exc.InvalidArgumentException()
raise exc.InvalidArgumentException('Input must be rescan=start')
eventlet.spawn_n(rescan, configmanager)
return [msg.KeyValueData({'rescan': 'started'})]
raise exc.NotImplementedException(
@@ -458,9 +458,21 @@ def handle_read_api_request(pathcomponents, configmanager):
portname = portname.replace('-', '/')
maclist = _macsbyswitch[switchname][portname]
except KeyError:
raise exc.NotFoundException('No known macs for switch {0} '
'port {1}'.format(switchname,
portname))
foundsomemacs = False
if switchname in _macsbyswitch:
try:
matcher = re.compile(portname)
except Exception:
raise exc.InvalidArgumentException('Invalid regular expression specified')
maclist = []
for actualport in _macsbyswitch[switchname]:
if bool(matcher.match(actualport)):
foundsomemacs = True
maclist = maclist + _macsbyswitch[switchname][actualport]
if not foundsomemacs:
raise exc.NotFoundException('No known macs for switch {0} '
'port {1}'.format(switchname,
portname))
return [msg.ChildCollection(x.replace(':', '-'))
for x in sorted(maclist)]
if len(pathcomponents) == 8:
@@ -17,6 +17,7 @@ import confluent.exceptions as exc
import confluent.messages as msg
import confluent.config.attributes as allattributes
import confluent.util as util
from fnmatch import fnmatch
def retrieve(nodes, element, configmanager, inputdata):
@@ -148,8 +149,8 @@ def retrieve_nodes(nodes, element, configmanager, inputdata):
yield msg.ListAttributes(
node, {attribute: currattr}, desc)
else:
print attribute
print repr(currattr)
print(attribute)
print(repr(currattr))
raise Exception("BUGGY ATTRIBUTE FOR NODE")
@@ -231,12 +232,20 @@ def update_nodes(nodes, element, configmanager, inputdata):
updatenode = inputdata.get_attributes(node, allattributes.node)
clearattribs = []
if updatenode:
for attrib in updatenode.iterkeys():
for attrib in list(updatenode):
if updatenode[attrib] is None:
clearattribs.append(attrib)
if len(clearattribs) > 0:
for attrib in clearattribs:
del updatenode[attrib]
if attrib in allattributes.node or attrib.startswith('custom.') or attrib.startswith('net.'):
clearattribs.append(attrib)
else:
foundattrib = False
for candattrib in allattributes.node:
if fnmatch(candattrib, attrib):
clearattribs.append(candattrib)
foundattrib = True
if not foundattrib:
raise exc.InvalidArgumentException("No attribute matches '" + attrib + "' (try wildcard if trying to clear a group)")
if len(clearattribs) > 0:
configmanager.clear_node_attributes([node], clearattribs)
updatedict[node] = updatenode
try:
@@ -434,7 +434,7 @@ def perform_request(operator, node, element,
except pygexc.InvalidParameterValue as e:
results.put(msg.ConfluentNodeError(node, str(e)))
except Exception as e:
results.put(msg.ConfluentNodeError(node, 'Unexpected Error'))
results.put(msg.ConfluentNodeError(node, 'Unexpected Error: {0}'.format(str(e))))
traceback.print_exc()
finally:
results.put('Done')
@@ -152,7 +152,9 @@ def sanitize_invdata(indata):
class IpmiCommandWrapper(ipmicommand.Command):
def __init__(self, node, cfm, **kwargs):
kwargs['pool'] = eventlet.greenpool.GreenPool(4)
#kwargs['pool'] = eventlet.greenpool.GreenPool(4)
#Some BMCs at the time of this writing crumble under the weight
#of 4 concurrent requests. For now give up on this optimization.
self.cfm = cfm
self.node = node
self._inhealth = False
@@ -167,7 +169,7 @@ class IpmiCommandWrapper(ipmicommand.Command):
try:
super(IpmiCommandWrapper, self).__init__(**kwargs)
except socket.error as se:
if se[1] == 'EHOSTUNREACH':
if isinstance(se, socket.timeout) or (len(se) > 1 and se[1] == 'EHOSTUNREACH'):
raise exc.TargetEndpointUnreachable('timeout')
raise
except pygexc.PyghmiException as pe:
@@ -321,7 +323,7 @@ def perform_request(operator, node, element,
except (pygexc.InvalidParameterValue, pygexc.RedfishError) as e:
results.put(msg.ConfluentNodeError(node, str(e)))
except Exception as e:
results.put(msg.ConfluentNodeError(node, 'Unexpected Error'))
results.put(msg.ConfluentNodeError(node, 'Unexpected Error: {0}'.format(str(e))))
traceback.print_exc()
finally:
results.put('Done')