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

Compare commits

..

65 Commits

Author SHA1 Message Date
Jarrod Johnson 5c288a27dd Have EL8 use python3 for confluent 2019-10-04 10:54:20 -04:00
Jarrod Johnson d9be6ae2e9 Close console on disconnect
Fixes leftover console on user initiated disconnect.
2019-10-04 09:57:54 -04:00
Jarrod Johnson 59789bae7d Fix python3 ctypes str usage
In python3, the string is likely to be unicode and incompatible
with the libc function.  If it isn't bytes, force it to be bytes.
2019-10-03 16:06:15 -04:00
Jarrod Johnson 521be5d44d Further Python3 compatibility changes
With this as well as eficompressor and pyghmi updates, things
seem to be in roughly working order
2019-10-03 15:57:56 -04:00
Jarrod Johnson 6fb82bbbad Further Python3 compatibility changes 2019-10-02 11:29:13 -04:00
Jarrod Johnson 90e546bcac Implement a number of py3 compatible adjustments 2019-10-02 08:58:39 -04:00
Jarrod Johnson 147d59cba7 Migrate from PyPAM
PyPAM is no longer part of the distributions.  Closest match is also not in
the distributions and also contains a security problem without an external
patch, so it is pulled in and pull request with copyright and license
intact.
2019-10-01 11:28:43 -04:00
Jarrod Johnson 6a6fd3184e Add missing dependencies for EL8 2019-09-30 10:51:26 -04:00
Jarrod Johnson c532cf9ecf Add EL8 requires adapting to confluent 2019-09-25 15:38:36 -04:00
Jarrod Johnson 8909fb16d6 Change rpm spec to build using python2
This will fix shebang mangling.
2019-09-23 11:11:16 -04:00
Jarrod Johnson 44d6bde3ff Make /usr/bin/env python point to python2
Same as before, more RHEL8 compatibility changes
2019-09-23 11:04:52 -04:00
Jarrod Johnson 56fa13279e Explicitly indicate use of python2
RHEL8 will no longer tolerate implicit use of python.
For now relent to being python2, though ideally one day
it could be either.

Unfortunately, this means once code is ready for python3, we have to
probably implement build time changes for python3 enabled distros
to have different shebangs than python2 distros.
2019-09-23 10:59:00 -04:00
Jarrod Johnson 5008128d57 Add IPv6 support to TSM
TSM firmware fixes enable IPv6, enable our support of it.
2019-09-19 10:02:40 -04:00
Jarrod Johnson ed320f4a17 Add 'check' to permit comparison against current value
For implementing some security policies, it is useful
to check new value against current value.
2019-09-17 09:48:02 -04:00
Jarrod Johnson de8292f6dd Ignore current channel if current channel is disabled
A fluke can cause current channel to be 1 when we are wanting 8.
2019-09-06 13:31:14 -04:00
Jarrod Johnson 8e071a2568 Fix missing import
The smm change omitted a required import.
2019-09-05 12:45:29 -04:00
Jarrod Johnson 7d84d0a021 Add network config to SD635/SD655 discovery
This completes the most critical portion of SD635/SD655 functionality.
2019-09-05 12:10:58 -04:00
Jarrod Johnson c5f75bfa15 Fix slowdown when no password change needed
If the password was pre-provisioned, accelerate the config
phase of tsm bringup.
2019-09-05 11:10:38 -04:00
Jarrod Johnson 99d01d707f Fix incorrect bad auth on freshly changed password
If the database backing the password had changed but cache hadn't updated,
then the cache miss was taken as always meaning no valid login.
2019-09-05 11:09:11 -04:00
Jarrod Johnson 3e1690c860 Add authentication and ipmi configuration to SR635/SR655
Network configuration and the lockout policy remain to be configured.
2019-09-04 16:20:31 -04:00
Jarrod Johnson a3f5630535 Add SR635/SR655 detection
Currently merely detects.  A discover handler will be next step
to populate UUID and do initial setup.
2019-09-03 15:49:27 -04:00
Jarrod Johnson 8d6744947e Extended bmc config support to redfish
Though the underlying library is currenty a stub, bring the
confluent plugin to parity so that nodeconfig works fine
with redfish again.
2019-09-03 11:05:51 -04:00
Jarrod Johnson 21b3c89974 Improve redfish plugin errors and attrib handling
Redfish plugin was not handling the various socket error
behaviors correctly.

Additionally, the attribschanged handler was failing to actually
do anything, so commands would gladly reuse an old redfish
object if it were working.
2019-09-03 10:06:18 -04:00
Jarrod Johnson 9718881c7b Merge branch 'master' of github.com:jjohnson42/confluent 2019-08-30 10:22:59 -04:00
Jarrod Johnson fcbbdc8a8d Add python-pyopenssl and python-dateutil deps
For wheezy, the only supported user wants to
always have collective capability.
Workaround pyghmi missing the dateutil dependency.
2019-08-30 10:22:17 -04:00
Jarrod Johnson f03bb36dbe Fix nodeconfig treatment of bmc attributes
Make bmc behave like other categories/settings.
2019-08-29 15:24:14 -04:00
Jarrod Johnson 97a950b145 Retain XCC IP on discovery if already set
Do not change from DHCP if DHCP is working the way intended.
2019-08-28 09:46:35 -04:00
Jarrod Johnson a0e445d1b1 Force MSS down for forwarded connections
Some endpoints may not be able to fully handle the natural
MSS suggested by MTU.
2019-08-28 09:16:42 -04:00
Jarrod Johnson 65629548c9 Add support for new BMC config capabilities
This enables nodeconfig -r bmc and nodeconfig bmc.<attr> function.
2019-08-27 14:56:19 -04:00
Jarrod Johnson 6cfbf4533c Add support for BMC clear and extended attributes
This wires up new function in pyghmi.
2019-08-27 14:32:16 -04:00
Jarrod Johnson 5794dd7f8c Add CP storage to discovery 2019-08-22 16:15:01 -04:00
Jarrod Johnson 48fa74e7a9 Explicitly use utf8 when encode error encountered
Some platforms do not naturally do utf-8.  This is
pretty much always the answer, so try it if the default
encoding of a platform fails.
2019-08-22 08:57:47 -04:00
Jarrod Johnson 59d5110b8f Also enable ipmi if user sets in console.method 2019-08-20 09:29:25 -04:00
Jarrod Johnson 051d79727b Fix sysvinit
sysvinit lsb function is 'status_of_proc'
2019-08-20 08:59:01 -04:00
Jarrod Johnson 4cdbc7807b Add thinkagile storage to discovery
At least enumerate the ipv4 pieces.
2019-08-19 16:37:14 -04:00
Jarrod Johnson feecee82db Mitigate excessive open web sessions
Back to back discovery testing would exhaust web slots.
Severely mitigate through being more aggressive about logging out.
2019-08-19 10:44:47 -04:00
Jarrod Johnson 395c0d4697 Preserve IPMI state unless user wants otherwise
ALso a draft of navigating a forced password change
in the midst of a pre-hashed password conversion.
2019-08-16 16:33:57 -04:00
Jarrod Johnson a963a8ca35 Handle forced password change and no IPMI for XCC
XCC firmware will start mandating a password change before use.

Additionally, IPMI will be unavailable and will be needed for enabling SMM.

TODO:
-Discover USERID/TempW0rd42 nodes that weren't preconfigged

-Enable IPMI on non-SD530 if hardwaremanagement.method is not redfish.
2019-08-15 16:40:15 -04:00
Jarrod Johnson d95464df6f Add a non-ip network string xmit concept 2019-08-08 16:14:05 -04:00
Jarrod Johnson 3f6e6d4c39 Support unavailable sensors in redfish 2019-08-08 15:02:26 -04:00
Jarrod Johnson c3176ab86a Add blink and identify reporting to redfish 2019-08-05 16:22:50 -04:00
Jarrod Johnson 4dab5fc527 Adapt to RHEL or Debian openssl config locations 2019-08-05 16:16:42 -04:00
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
92 changed files with 1522 additions and 425 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 IBM Corporation
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
import optparse
import signal
import sys
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
import csv
import optparse
import signal
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
import csv
import optparse
import signal
+2 -2
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
@@ -128,4 +128,4 @@ else:
else:
print(res['item']['href'].replace('/', ''))
sys.exit(exitcode)
sys.exit(exitcode)
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015-2017 Lenovo
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015 Lenovo
+40 -16
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
@@ -98,6 +98,8 @@ setmode = None
assignment = {}
queryparms = {}
printsys = []
printbmc = []
printallbmc = False
setsys = {}
forceset = False
needval = None
@@ -122,7 +124,7 @@ def _assign_value():
def parse_config_line(arguments):
global setmode, forceset, key, value, needval, candidate, path, attrib
global setmode, printallbmc, forceset, key, value, needval, candidate, path, attrib
for param in arguments:
if param == 'show':
continue # forgive muscle memory of pasu users
@@ -154,6 +156,8 @@ def parse_config_line(arguments):
if setmode != False:
bailout('Cannot do set and query in same command')
if '.' not in param:
if param == 'bmc':
printallbmc = True
matchedparms = False
for candidate in cfgpaths:
if candidate.startswith('{0}.'.format(param)):
@@ -172,7 +176,10 @@ def parse_config_line(arguments):
if not matchedparms:
printsys.append(param)
elif param not in cfgpaths:
printsys.append(param)
if param.startswith('bmc.'):
printbmc.append(param.replace('bmc.', ''))
else:
printsys.append(param)
else:
path, attrib = cfgpaths[param]
path = '/noderange/{0}/{1}'.format(noderange, path)
@@ -197,18 +204,26 @@ else:
parse_config_line(args[1:])
session = client.Command()
rcode = 0
if options.restoredefault and options.restoredefault.lower() in (
if options.restoredefault:
if options.restoredefault.lower() in (
'sys', 'system', 'uefi', 'bios'):
for fr in session.update(
'/noderange/{0}/configuration/system/clear'.format(noderange),
{'clear': True}):
rcode |= client.printerror(fr)
sys.exit(rcode)
elif options.restoredefault:
sys.stderr.write(
'Unrecognized component to restore defaults: {0}\n'.format(
options.restoredefault))
sys.exit(1)
for fr in session.update(
'/noderange/{0}/configuration/system/clear'.format(noderange),
{'clear': True}):
rcode |= client.printerror(fr)
sys.exit(rcode)
elif options.restoredefault.lower() in (
'bmc', 'imm', 'xcc'):
for fr in session.update(
'/noderange/{0}/configuration/management_controller/clear'.format(noderange),
{'clear': True}):
rcode |= client.printerror(fr)
sys.exit(rcode)
else:
sys.stderr.write(
'Unrecognized component to restore defaults: {0}\n'.format(
options.restoredefault))
sys.exit(1)
if setmode:
if options.exclude:
sys.stderr.write('Cannot use exclude and assign at the same time\n')
@@ -217,8 +232,12 @@ if setmode:
attrnamebypath = {}
for key in assignment:
if key not in cfgpaths:
path = 'configuration/system/all'
attrib = key
if key.startswith('bmc.'):
path = 'configuration/management_controller/extended/all'
attrib = key.replace('bmc.', '')
else:
path = 'configuration/system/all'
attrib = key
else:
path, attrib = cfgpaths[key]
if path not in updatebypath:
@@ -250,6 +269,10 @@ else:
NullOpt(), queryparms[path])
if rc:
sys.exit(rc)
if printbmc or printallbmc:
rcode = client.print_attrib_path(
'/noderange/{0}/configuration/management_controller/extended/all'.format(noderange),
session, printbmc, options, attrprefix='bmc.')
if printsys or options.exclude:
if printsys == 'all':
printsys = []
@@ -258,6 +281,7 @@ else:
else:
path = '/noderange/{0}/configuration/system/advanced'.format(
noderange)
rcode = client.print_attrib_path(path, session, printsys,
options)
sys.exit(rcode)
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015 Lenovo
+2 -2
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
@@ -55,4 +55,4 @@ for r in session.create('/noderange/', attribs):
exitcode |= 1
if 'created' in r:
print('{0}: created'.format(r['created']))
sys.exit(exitcode)
sys.exit(exitcode)
+8 -3
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
@@ -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)
+5 -3
View File
@@ -1,7 +1,7 @@
#!/usr/bin/python
#!/usr/bin/python2
# 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)))
+3 -3
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2016-2017 Lenovo
@@ -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()
@@ -163,4 +163,4 @@ try:
update_firmware(session, upfile)
except KeyboardInterrupt:
print('')
sys.exit(exitcode)
sys.exit(exitcode)
+2 -2
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
@@ -125,4 +125,4 @@ else:
else:
print res['item']['href'].replace('/', '')
sys.exit(exitcode)
sys.exit(exitcode)
+2 -2
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
@@ -55,4 +55,4 @@ for r in session.create('/nodegroups/', attribs):
exitcode |= 1
if 'created' in r:
print('{0}: created'.format(r['created']))
sys.exit(exitcode)
sys.exit(exitcode)
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015-2017 Lenovo
+2 -2
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
@@ -49,4 +49,4 @@ for r in session.delete('/nodegroups/{0}'.format(noderange)):
exitcode |= 1
if 'deleted' in r:
print('{0}: deleted'.format(r['deleted']))
sys.exit(exitcode)
sys.exit(exitcode)
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2019 Lenovo
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015-2017 Lenovo
+2 -5
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015-2017 Lenovo
@@ -31,7 +31,7 @@ if path.startswith('/opt'):
import confluent.client as client
argparser = optparse.OptionParser(usage="Usage: %prog <noderange> [on|off]")
argparser = optparse.OptionParser(usage="Usage: %prog <noderange> [on|off|blink]")
(options, args) = argparser.parse_args()
try:
noderange = args[0]
@@ -42,9 +42,6 @@ client.check_globbing(noderange)
identifystate = None
if len(sys.argv) > 2:
identifystate = sys.argv[2]
else:
argparser.print_help()
sys.exit(1)
session = client.Command()
exitcode = 0
sys.exit(
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2016-2017 Lenovo
+9 -4
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2019 Lenovo
@@ -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)
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015-2017 Lenovo
+2 -2
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2018 Lenovo
@@ -188,4 +188,4 @@ def main():
sys.exit(1)
handler(noderange, media)
if __name__ == '__main__':
main()
main()
+2 -2
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015-2017 Lenovo
@@ -72,4 +72,4 @@ if options.previous:
# add dictionary to session
session.add_precede_dict(prev)
sys.exit(session.simple_noderange_command(noderange, '/power/state', setstate))
sys.exit(session.simple_noderange_command(noderange, '/power/state', setstate))
+2 -2
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
@@ -49,4 +49,4 @@ for r in session.delete('/noderange/{0}'.format(noderange)):
exitcode |= 1
if 'deleted' in r:
print('{0}: deleted'.format(r['deleted']))
sys.exit(exitcode)
sys.exit(exitcode)
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2019 Lenovo
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015-2017 Lenovo
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2016-2017 Lenovo
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2016-2017 Lenovo
+4 -4
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015-2017 Lenovo
@@ -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
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015 Lenovo
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2016-2017 Lenovo
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2018 Lenovo
+2 -2
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2018 Lenovo
@@ -144,4 +144,4 @@ def main():
sys.exit(1)
handler(noderange, media)
if __name__ == '__main__':
main()
main()
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2019 Lenovo
+8 -3
View File
@@ -1,7 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 IBM Corporation
# Copyright 2015-2018 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.
@@ -107,7 +107,10 @@ def printerror(res, node=None):
def cprint(txt):
print(txt)
try:
print(txt)
except UnicodeEncodeError:
print(txt.encode('utf8'))
sys.stdout.flush()
def _parseserver(string):
@@ -390,7 +393,7 @@ def printattributes(session, requestargs, showtype, nodetype, noderange, options
return print_attrib_path(path, session, requestargs, options)
def print_attrib_path(path, session, requestargs, options, rename=None):
def print_attrib_path(path, session, requestargs, options, rename=None, attrprefix=None):
exitcode = 0
seenattributes = set([])
for res in session.read(path):
@@ -413,6 +416,8 @@ def print_attrib_path(path, session, requestargs, options, rename=None):
printattr = rename.get(attr, attr)
else:
printattr = attr
if attrprefix:
printattr = attrprefix + printattr
currattr = res['databynode'][node][attr]
if show_attr(attr, requestargs, seenattributes, options):
if 'value' in currattr:
+8 -3
View File
@@ -25,6 +25,11 @@ try:
except NameError:
unicode = str
try:
range = xrange
except NameError:
pass
def decodestr(value):
ret = None
try:
@@ -40,7 +45,7 @@ def decodestr(value):
def unicode_dictvalues(dictdata):
for key in dictdata:
if isinstance(dictdata[key], str):
if isinstance(dictdata[key], bytes):
dictdata[key] = decodestr(dictdata[key])
elif isinstance(dictdata[key], datetime):
dictdata[key] = dictdata[key].strftime('%Y-%m-%dT%H:%M:%S')
@@ -51,7 +56,7 @@ def unicode_dictvalues(dictdata):
def _unicode_list(currlist):
for i in xrange(len(currlist)):
for i in range(len(currlist)):
if isinstance(currlist[i], str):
currlist[i] = decodestr(currlist[i])
elif isinstance(currlist[i], dict):
@@ -66,7 +71,7 @@ def send(handle, data):
data = data.encode('utf-8')
except AttributeError:
pass
if isinstance(data, str) or isinstance(data, unicode):
if isinstance(data, bytes) or isinstance(data, unicode):
# plain text, e.g. console data
tl = len(data)
if tl == 0:
+10 -2
View File
@@ -24,10 +24,18 @@ a confluent server.
%setup -n %{name}-%{version} -n %{name}-%{version}
%build
python setup.py build
%if "%{dist}" == ".el8"
python3 setup.py build
%else
python2 setup.py build
%endif
%install
python setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES --install-scripts=/opt/confluent/bin --install-purelib=/opt/confluent/lib/python
%if "%{dist}" == ".el8"
python3 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES --install-scripts=/opt/confluent/bin --install-purelib=/opt/confluent/lib/python
%else
python2 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES --install-scripts=/opt/confluent/bin --install-purelib=/opt/confluent/lib/python
%endif
%clean
+2 -2
View File
@@ -84,7 +84,7 @@ function _confluent_generic_completion()
}
_confluent_nodeidentify_completion()
{
COMP_CANDIDATES=("on,off -h")
COMP_CANDIDATES=("on,off,blink -h")
_confluent_generic_completion
}
@@ -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
+2 -1
View File
@@ -3,7 +3,7 @@ nodeidentify(8) -- Control the identify LED of confluent nodes
## SYNOPSIS
`nodidentify <noderange> [on|off]`
`nodidentify <noderange> [on|off|blink]`
## DESCRIPTION
@@ -13,6 +13,7 @@ options are supported:
* `on`: Turn on the identify LED
* `off`: Turn off the identify LED
* `blink`: Set the identify LED to blink (when supported by the system)
## EXAMPLES:
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# This is a sample python script for going through all observed mac addresses
# and assuming they are BMC related and printing nodeattrib commands
+2 -2
View File
@@ -22,10 +22,10 @@ This provides the modules common for both client and server
%setup -n %{name}-%{version} -n %{name}-%{version}
%build
python setup.py build
python2 setup.py build
%install
python setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES --install-purelib=/opt/confluent/lib/python --install-scripts=/opt/confluent/bin
python2 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES --install-purelib=/opt/confluent/lib/python --install-scripts=/opt/confluent/bin
%clean
rm -rf $RPM_BUILD_ROOT
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
import argparse
import errno
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 IBM Corporation
+8 -3
View File
@@ -1,11 +1,16 @@
from os.path import exists
import shutil
import socket
import subprocess
import tempfile
def get_openssl_conf_location():
# CentOS/RHAT
return '/etc/pki/tls/openssl.cnf'
if exists('/etc/pki/tls/openssl.cnf'):
return '/etc/pki/tls/openssl.cnf'
elif exists('/etc/ssl/openssl.cnf');
return '/etc/ssl/openssl.cnf'
else:
raise Exception("Cannot find openssl config file")
def get_ip_addresses():
lines = subprocess.check_output('ip addr'.split(' '))
@@ -47,4 +52,4 @@ def create_certificate():
)
if __name__ == '__main__':
create_certificate()
create_certificate()
+2 -2
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
@@ -82,7 +82,7 @@ elif args[0] == 'dump':
"or -s to do encrypted backup that requires keys.json from "
"another backup to restore.")
sys.exit(1)
os.umask(077)
os.umask(0o77)
main._initsecurity(conf.get_config())
if not os.path.exists(dumpdir):
os.makedirs(dumpdir)
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 IBM Corporation
+1 -1
View File
@@ -30,7 +30,7 @@ shopt -s extglob
cd deb_dist/!(*.orig)/
if [ "$OPKGNAME" = "confluent-server" ]; then
if grep wheezy /etc/os-release; then
sed -i 's/^\(Depends:.*\)/\1, python-confluent-client, python-lxml, python-eficompressor, python-pycryptodomex/' debian/control
sed -i 's/^\(Depends:.*\)/\1, python-confluent-client, python-lxml, python-eficompressor, python-pycryptodomex, python-dateutil, python-pyopenssl/' debian/control
else
sed -i 's/^\(Depends:.*\)/\1, confluent-client, python-lxml, python-eficompressor, python-pycryptodome, python-dateutil/' debian/control
fi
+10 -34
View File
@@ -28,8 +28,9 @@ import hashlib
import hmac
import multiprocessing
import confluent.userutil as userutil
pam = None
try:
import PAM
import confluent.pam as pam
except ImportError:
pass
import time
@@ -94,23 +95,6 @@ _deniedbyrole = {
]
}
}
class Credentials(object):
def __init__(self, username, passphrase):
self.username = username
self.passphrase = passphrase
self.haspam = False
def pam_conv(self, auth, query_list):
# use stored credentials in a pam conversation
self.haspam = True
resp = []
for query_entry in query_list:
query, pamtype = query_entry
if query.startswith('Password'):
resp.append((self.passphrase, 0))
else:
return None
return resp
def _prune_passcache():
@@ -119,9 +103,9 @@ def _prune_passcache():
while True:
curtime = time.time()
for passent in _passcache.iterkeys():
if passent[2] < curtime - 10:
if passent[2] < curtime - 90:
del _passcache[passent]
eventlet.sleep(10)
eventlet.sleep(90)
def _get_usertenant(name, tenant=False):
@@ -227,7 +211,6 @@ def check_user_passphrase(name, passphrase, operation=None, element=None, tenant
# would normally make an event and wait
# but here there's no need for that
eventlet.sleep(0.5)
credobj = Credentials(user, passphrase)
cfm = configmanager.ConfigManager(tenant, username=user)
ucfg = cfm.get_user(user)
if ucfg is None:
@@ -249,7 +232,6 @@ def check_user_passphrase(name, passphrase, operation=None, element=None, tenant
# while someone is legitimately logged in
# invalidate cache and force the slower check
del _passcache[(user, tenant)]
return None
if 'cryptpass' in ucfg:
_passchecking[(user, tenant)] = True
# TODO(jbjohnso): WORKERPOOL
@@ -279,22 +261,16 @@ def check_user_passphrase(name, passphrase, operation=None, element=None, tenant
if crypt == crypted:
_passcache[(user, tenant)] = hashlib.sha256(passphrase).digest()
return authorize(user, element, tenant, operation)
try:
pammy = PAM.pam()
pammy.start(_pamservice, user, credobj.pam_conv)
pammy.authenticate()
pammy.acct_mgmt()
if pam:
pammy = pam.pam()
usergood = pammy.authenticate(user, passphrase)
del pammy
_passcache[(user, tenant)] = hashlib.sha256(passphrase).digest()
return authorize(user, element, tenant, operation, skipuserobj=False)
except NameError:
pass
except PAM.error:
pass
if usergood:
_passcache[(user, tenant)] = hashlib.sha256(passphrase).digest()
return authorize(user, element, tenant, operation, skipuserobj=False)
eventlet.sleep(0.05) # stall even on test for existence of a username
return None
def _apply_pbkdf(passphrase, salt):
return KDF.PBKDF2(passphrase, salt, 32, 10000,
lambda p, s: hmac.new(p, s, hashlib.sha256).digest())
+4 -1
View File
@@ -16,7 +16,10 @@
# This defines config variable to store the global configuration for confluent
import ConfigParser
try:
import ConfigParser
except ModuleNotFoundError:
import configparser as ConfigParser
import os
_config = None
@@ -46,7 +46,10 @@ import Cryptodome.Protocol.KDF as KDF
from Cryptodome.Cipher import AES
from Cryptodome.Hash import HMAC
from Cryptodome.Hash import SHA256
import anydbm as dbm
try:
import anydbm as dbm
except ModuleNotFoundError:
import dbm
import ast
import base64
import confluent.config.attributes as allattributes
@@ -57,7 +60,10 @@ import confluent.util
import confluent.netutil as netutil
import confluent.exceptions as exc
import copy
import cPickle
try:
import cPickle
except ModuleNotFoundError:
import pickle as cPickle
import errno
import eventlet
import eventlet.event as event
@@ -74,6 +80,10 @@ import struct
import sys
import threading
import traceback
try:
unicode
except NameError:
unicode = str
_masterkey = None
@@ -145,10 +155,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 +170,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 +369,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 +386,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 +465,16 @@ 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
try:
cryptval = crypter.encrypt(value)
except TypeError:
cryptval = crypter.encrypt(value.encode('utf-8'))
hmac = HMAC.new(integritykey, cryptval, SHA256).digest()
return iv, cryptval, hmac
iv = os.urandom(12)
crypter = AES.new(key, AES.MODE_GCM, nonce=iv)
value = confluent.util.stringify(value).encode('utf-8')
cryptval, hmac = crypter.encrypt_and_digest(value)
return iv, cryptval, hmac, '\x02'
def _load_dict_from_dbm(dpath, tdb):
@@ -466,16 +482,19 @@ def _load_dict_from_dbm(dpath, tdb):
dbe = dbm.open(tdb, 'r')
currdict = _cfgstore
for elem in dpath:
elem = confluent.util.stringify(elem)
if elem not in currdict:
currdict[elem] = {}
currdict = currdict[elem]
try:
for tk in dbe:
currdict[tk] = cPickle.loads(dbe[tk])
for tk in dbe.keys():
tks = confluent.util.stringify(tk)
currdict[tks] = cPickle.loads(dbe[tk])
except AttributeError:
tk = dbe.firstkey()
while tk != None:
currdict[tk] = cPickle.loads(dbe[tk])
tks = confluent.util.stringify(tk)
currdict[tks] = cPickle.loads(dbe[tk])
tk = dbe.nextkey(tk)
except dbm.error:
return
@@ -514,13 +533,7 @@ def set_global(globalname, value, sync=True):
"""
if _cfgstore is None:
init(not sync)
try:
globalname = globalname.encode('utf-8')
except AttributeError:
# We have to remove the unicode-ness of the string,
# but if it is already bytes in python 3, then we will
# get an attributeerror, so pass
pass
globalname = confluent.util.stringify(globalname)
with _dirtylock:
if 'dirtyglobals' not in _cfgstore:
_cfgstore['dirtyglobals'] = set()
@@ -777,10 +790,7 @@ def apply_pending_collective_updates():
def _true_add_collective_member(name, address, fingerprint, sync=True):
try:
name = name.encode('utf-8')
except AttributeError:
pass
name = confluent.util.stringify(name)
if _cfgstore is None:
init(not sync) # use not sync to avoid read from disk
if 'collective' not in _cfgstore:
@@ -815,8 +825,7 @@ def get_collective_member_by_address(address):
def _mark_dirtykey(category, key, tenant=None):
if type(key) in (str, unicode):
key = key.encode('utf-8')
key = confluent.util.stringify(key)
with _dirtylock:
if 'dirtykeys' not in _cfgstore:
_cfgstore['dirtykeys'] = {}
@@ -1137,9 +1146,9 @@ class ConfigManager(object):
Returns an identifier that can be used to unsubscribe from these
notifications using remove_watcher
"""
notifierid = random.randint(0, sys.maxint)
notifierid = random.randint(0, sys.maxsize)
while notifierid in self._notifierids:
notifierid = random.randint(0, sys.maxint)
notifierid = random.randint(0, sys.maxsize)
self._notifierids[notifierid] = {'attriblist': []}
if self.tenant not in self._attribwatchers:
self._attribwatchers[self.tenant] = {}
@@ -1178,9 +1187,9 @@ class ConfigManager(object):
# use in case of cancellation.
# I anticipate no more than a handful of watchers of this sort, so
# this loop should not have to iterate too many times
notifierid = random.randint(0, sys.maxint)
notifierid = random.randint(0, sys.maxsize)
while notifierid in self._notifierids:
notifierid = random.randint(0, sys.maxint)
notifierid = random.randint(0, sys.maxsize)
# going to track that this is a nodecollection type watcher,
# but there is no additional data associated.
self._notifierids[notifierid] = set(['nodecollection'])
@@ -1296,7 +1305,7 @@ class ConfigManager(object):
def _true_create_usergroup(self, groupname, role="Administrator"):
if 'usergroups' not in self._cfgstore:
self._cfgstore['usergroups'] = {}
groupname = groupname.encode('utf-8')
groupname = confluent.util.stringify(groupname)
if groupname in self._cfgstore['usergroups']:
raise Exception("Duplicate groupname requested")
self._cfgstore['usergroups'][groupname] = {'role': role}
@@ -1398,7 +1407,7 @@ class ConfigManager(object):
raise Exception("Duplicate id requested")
if 'users' not in self._cfgstore:
self._cfgstore['users'] = {}
name = name.encode('utf-8')
name = confluent.util.stringify(name)
if name in self._cfgstore['users']:
raise Exception("Duplicate username requested")
self._cfgstore['users'][name] = {'id': uid}
@@ -1617,7 +1626,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]
@@ -1656,7 +1665,7 @@ class ConfigManager(object):
"{0} node does not exist to add to {1}".format(
node, group))
for group in attribmap:
group = group.encode('utf-8')
group = confluent.util.stringify(group)
if group not in self._cfgstore['nodegroups']:
self._cfgstore['nodegroups'][group] = {'nodes': set()}
cfgobj = self._cfgstore['nodegroups'][group]
@@ -1714,8 +1723,8 @@ class ConfigManager(object):
attributes = realattributes
if type(groups) in (str, unicode):
groups = (groups,)
for group in groups:
group = group.encode('utf-8')
for group in groups:
group = confluent.util.stringify(group)
try:
groupentry = self._cfgstore['nodegroups'][group]
except KeyError:
@@ -1806,7 +1815,8 @@ class ConfigManager(object):
'nodeattrs': {node: [attrname]},
'callback': attribwatcher[watchkey][notifierid]
}
for watcher in notifdata.itervalues():
for watcher in notifdata:
watcher = notifdata[watcher]
callback = watcher['callback']
eventlet.spawn_n(_do_notifier, self, watcher, callback)
@@ -1820,14 +1830,15 @@ class ConfigManager(object):
def _true_del_nodes(self, nodes):
if self.tenant in self._nodecollwatchers:
for watcher in self._nodecollwatchers[self.tenant].itervalues():
for watcher in self._nodecollwatchers[self.tenant]:
watcher = self._nodecollwatchers[self.tenant][watcher]
watcher(added=(), deleting=nodes, renamed=(), configmanager=self)
changeset = {}
for node in nodes:
# set a reserved attribute for the sake of the change notification
# framework to trigger on
changeset[node] = {'_nodedeleted': 1}
node = node.encode('utf-8')
node = confluent.util.stringify(node)
if node in self._cfgstore['nodes']:
self._sync_groups_to_node(node=node, groups=[],
changeset=changeset)
@@ -1875,7 +1886,7 @@ class ConfigManager(object):
realattributes.append(attrname)
attributes = realattributes
for node in nodes:
node = node.encode('utf-8')
node = confluent.util.stringify(node)
try:
nodek = self._cfgstore['nodes'][node]
except KeyError:
@@ -1946,7 +1957,8 @@ class ConfigManager(object):
self._recalculate_expressions(cfgobj, formatter=exprmgr, node=renamemap[name], changeset=changeset)
if self.tenant in self._nodecollwatchers:
nodecollwatchers = self._nodecollwatchers[self.tenant]
for watcher in nodecollwatchers.itervalues():
for watcher in nodecollwatchers:
watcher = nodecollwatchers[watcher]
eventlet.spawn_n(_do_add_watcher, watcher, (), self, renamemap)
self._bg_sync_to_file()
@@ -2003,7 +2015,7 @@ class ConfigManager(object):
# first do a sanity check of the input upfront
# this mitigates risk of arguments being partially applied
for node in attribmap:
node = node.encode('utf-8')
node = confluent.util.stringify(node)
if node == '':
raise ValueError('"{0}" is not a valid node name'.format(node))
if autocreate:
@@ -2058,8 +2070,8 @@ class ConfigManager(object):
attrname, node)
raise ValueError(errstr)
attribmap[node][attrname] = attrval
for node in attribmap:
node = node.encode('utf-8')
for node in attribmap:
node = confluent.util.stringify(node)
exprmgr = None
if node not in self._cfgstore['nodes']:
newnodes.append(node)
@@ -2102,7 +2114,8 @@ class ConfigManager(object):
if newnodes:
if self.tenant in self._nodecollwatchers:
nodecollwatchers = self._nodecollwatchers[self.tenant]
for watcher in nodecollwatchers.itervalues():
for watcher in nodecollwatchers:
watcher = nodecollwatchers[watcher]
eventlet.spawn_n(_do_add_watcher, watcher, newnodes, self)
self._bg_sync_to_file()
#TODO: wait for synchronization to suceed/fail??)
@@ -2240,7 +2253,7 @@ class ConfigManager(object):
_cfgstore = {}
rootpath = cls._cfgdir
try:
with open(os.path.join(rootpath, 'transactioncount'), 'r') as f:
with open(os.path.join(rootpath, 'transactioncount'), 'rb') as f:
txbytes = f.read()
if len(txbytes) == 8:
_txcount = struct.unpack('!Q', txbytes)[0]
@@ -2298,7 +2311,7 @@ class ConfigManager(object):
if statelessmode:
return
_mkpath(cls._cfgdir)
with open(os.path.join(cls._cfgdir, 'transactioncount'), 'w') as f:
with open(os.path.join(cls._cfgdir, 'transactioncount'), 'wb') as f:
f.write(struct.pack('!Q', _txcount))
if (fullsync or 'dirtyglobals' in _cfgstore and
'globals' in _cfgstore):
@@ -2409,7 +2422,9 @@ def _restore_keys(jsond, password, newpassword=None, sync=True):
else:
keydata = json.loads(jsond)
cryptkey = _parse_key(keydata['cryptkey'], password)
integritykey = _parse_key(keydata['integritykey'], password)
integritykey = None
if 'integritykey' in keydata:
integritykey = _parse_key(keydata['integritykey'], password)
conf.init_config()
cfg = conf.get_config()
if cfg.has_option('security', 'externalcfgkey'):
@@ -2418,8 +2433,9 @@ def _restore_keys(jsond, password, newpassword=None, sync=True):
newpassword = keyfile.read()
set_global('master_privacy_key', _format_key(cryptkey,
password=newpassword), sync)
set_global('master_integrity_key', _format_key(integritykey,
password=newpassword), sync)
if integritykey:
set_global('master_integrity_key', _format_key(integritykey,
password=newpassword), sync)
_masterkey = cryptkey
_masterintegritykey = integritykey
if sync:
@@ -2427,7 +2443,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 +2452,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
+24 -15
View File
@@ -94,7 +94,7 @@ def _utf8_normalize(data, shiftin, decoder):
def pytechars2line(chars, maxlen=None):
line = '\x1b[m' # start at default params
line = b'\x1b[m' # start at default params
lb = False # last bold
li = False # last italic
lu = False # last underline
@@ -130,9 +130,12 @@ def pytechars2line(chars, maxlen=None):
csi.append(7 if lr else 27)
if csi:
line += b'\x1b[' + b';'.join(['{0}'.format(x) for x in csi]) + b'm'
if not hasdata and char.data.encode('utf-8').rstrip():
if not hasdata and char.data.rstrip():
hasdata = True
line += char.data.encode('utf-8')
chardata = char.data
if not isinstance(chardata, bytes):
chardata = chardata.encode('utf-8')
line += chardata
if maxlen and len >= maxlen:
break
len += 1
@@ -185,7 +188,7 @@ class ConsoleHandler(object):
if termstate & 1:
self.appmodedetected = True
if termstate & 2:
self.shiftin = '0'
self.shiftin = b'0'
self.users = {}
self._attribwatcher = None
self._console = None
@@ -210,6 +213,8 @@ class ConsoleHandler(object):
return retrytime + (retrytime * random.random())
def feedbuffer(self, data):
if not isinstance(data, bytes):
data = data.encode('utf-8')
try:
self.termstream.feed(data)
except StopIteration: # corrupt parser state, start over
@@ -448,6 +453,7 @@ class ConsoleHandler(object):
def _got_disconnected(self):
if self.connectstate != 'unconnected':
self._console.close()
self.connectstate = 'unconnected'
self.log(
logdata='console disconnected', ltype=log.DataTypes.event,
@@ -528,14 +534,14 @@ class ConsoleHandler(object):
elif data == '':
# ignore empty strings from a cconsole provider
return
if '\x1b[?1l' in data: # request for ansi mode cursor keys
if b'\x1b[?1l' in data: # request for ansi mode cursor keys
self.appmodedetected = False
if '\x1b[?1h' in data: # remember the session wants the client to use
if b'\x1b[?1h' in data: # remember the session wants the client to use
# 'application mode' Thus far only observed on esxi
self.appmodedetected = True
if '\x1b)0' in data:
if b'\x1b)0' in data:
# console indicates it wants access to special drawing characters
self.shiftin = '0'
self.shiftin = b'0'
eventdata = 0
if self.appmodedetected:
eventdata |= 1
@@ -588,20 +594,23 @@ class ConsoleHandler(object):
if pendingbl:
retdata += pendingbl
pendingbl = b''
retdata += nline + '\r\n'
retdata += nline + b'\r\n'
else:
pendingbl += nline + '\r\n'
pendingbl += nline + b'\r\n'
if len(retdata) > 6:
retdata = retdata[:-2] # remove the last \r\n
retdata += b'\x1b[{0};{1}H'.format(self.buffer.cursor.y + 1,
self.buffer.cursor.x + 1)
cursordata = '\x1b[{0};{1}H'.format(self.buffer.cursor.y + 1,
self.buffer.cursor.x + 1)
if not isinstance(cursordata, bytes):
cursordata = cursordata.encode('utf-8')
retdata += cursordata
if self.shiftin is not None: # detected that terminal requested a
# shiftin character set, relay that to the terminal that cannected
retdata += '\x1b)' + self.shiftin
retdata += b'\x1b)' + self.shiftin
if self.appmodedetected:
retdata += '\x1b[?1h'
retdata += b'\x1b[?1h'
else:
retdata += '\x1b[?1l'
retdata += b'\x1b[?1l'
return retdata, connstate
def write(self, data):
+21 -1
View File
@@ -86,7 +86,10 @@ def seek_element(currplace, currkey):
def nested_lookup(nestdict, key):
try:
return reduce(seek_element, key, nestdict)
currloc = nestdict
for currk in key:
currloc = seek_element(currloc, currk)
return currloc
except TypeError:
raise exc.NotFoundException("Invalid element requested")
@@ -106,6 +109,8 @@ def load_plugins():
for plugin in os.listdir(plugindir):
if plugin.startswith('.'):
continue
if '__pycache__' in plugin:
continue
(plugin, plugtype) = os.path.splitext(plugin)
if plugtype == '.sh':
pluginmap[plugin] = shellmodule.Plugin(
@@ -163,6 +168,10 @@ def _init_core():
'default': 'ipmi',
}),
},
'clear': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'users': PluginCollection({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
@@ -205,6 +214,16 @@ def _init_core():
'default': 'ipmi',
}),
},
'extended': {
'all': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'advanced': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
},
},
'storage': {
'all': PluginRoute({
@@ -384,6 +403,7 @@ def _init_core():
nodegroupresources = {
'attributes': {
'check': PluginRoute({'handler': 'attributes'}),
'rename': PluginRoute({'handler': 'attributes'}),
'all': PluginRoute({'handler': 'attributes'}),
'current': PluginRoute({'handler': 'attributes'}),
+9 -1
View File
@@ -68,6 +68,8 @@ import confluent.discovery.protocols.pxe as pxe
#import confluent.discovery.protocols.ssdp as ssdp
import confluent.discovery.protocols.slp as slp
import confluent.discovery.handlers.imm as imm
import confluent.discovery.handlers.cpstorage as cpstorage
import confluent.discovery.handlers.tsm as tsm
import confluent.discovery.handlers.pxe as pxeh
import confluent.discovery.handlers.smm as smm
import confluent.discovery.handlers.xcc as xcc
@@ -101,6 +103,8 @@ nodehandlers = {
'service:management-hardware.IBM:integrated-management-module2': imm,
'pxe-client': pxeh,
'service:io-device.Lenovo:management-module': None,
'service:thinkagile-storage': cpstorage,
'service:lenovo-tsm': tsm,
}
servicenames = {
@@ -109,6 +113,8 @@ servicenames = {
'service:management-hardware.Lenovo:lenovo-xclarity-controller': 'lenovo-xcc',
'service:management-hardware.IBM:integrated-management-module2': 'lenovo-imm2',
'service:io-device.Lenovo:management-module': 'lenovo-switch',
'service:thinkagile-storage': 'thinkagile-storagebmc',
'service:lenovo-tsm': 'lenovo-tsm',
}
servicebyname = {
@@ -117,6 +123,8 @@ servicebyname = {
'lenovo-xcc': 'service:management-hardware.Lenovo:lenovo-xclarity-controller',
'lenovo-imm2': 'service:management-hardware.IBM:integrated-management-module2',
'lenovo-switch': 'service:io-device.Lenovo:management-module',
'thinkagile-storage': 'service:thinkagile-storagebmc',
'lenovo-tsm': 'service:lenovo-tsm',
}
discopool = eventlet.greenpool.GreenPool(500)
@@ -881,7 +889,7 @@ def eval_node(cfg, handler, info, nodename, manual=False):
handler.probe() # unicast interrogation as possible to get more data
# switch concurrently
# do some preconfig, for example, to bring a SMM online if applicable
handler.preconfig()
handler.preconfig(nodename)
except Exception as e:
unknown_info[info['hwaddr']] = info
info['discostatus'] = 'unidentified'
@@ -26,11 +26,11 @@ ipmicommand.session.threading = eventlet.green.threading
ipmicommand.session.socket.getaddrinfo = eventlet.support.greendns.getaddrinfo
getaddrinfo = eventlet.support.greendns.getaddrinfo
DEFAULT_USER = 'USERID'
DEFAULT_PASS = 'PASSW0RD'
class NodeHandler(generic.NodeHandler):
DEFAULT_USER = 'USERID'
DEFAULT_PASS = 'PASSW0RD'
def _get_ipmicmd(self, user=None, password=None):
priv = None
@@ -39,9 +39,9 @@ class NodeHandler(generic.NodeHandler):
raise pygexc.IpmiException()
priv = 4 # manually indicate priv to avoid double-attempt
if user is None:
user = DEFAULT_USER
user = self.DEFAULT_USER
if password is None:
password = DEFAULT_PASS
password = self.DEFAULT_PASS
return ipmicommand.Command(self.ipaddr, user, password,
privlevel=priv, keepalive=False)
@@ -56,37 +56,37 @@ 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
passwd = self.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:
if user is not None and user != self.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:
user = self.DEFAULT_USER
if passwd is not None and passwd != self.DEFAULT_PASS:
havecustomcreds = True
else:
passwd = DEFAULT_PASS
passwd = self.DEFAULT_PASS
if havecustomcreds:
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
@@ -0,0 +1,21 @@
# Copyright 2019 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import confluent.discovery.handlers.bmc as bmchandler
class NodeHandler(bmchandler.NodeHandler):
DEFAULT_USER = 'admin'
DEFAULT_PASS = 'admin'
devname = 'BMC'
maxmacs = 2
@@ -57,7 +57,7 @@ class NodeHandler(object):
# serial number and uuid to flesh out data as needed
return
def preconfig(self):
def preconfig(self, possiblenode):
return
def discoverable_by_switch(self, macs):
@@ -13,7 +13,16 @@
# limitations under the License.
import confluent.discovery.handlers.bmc as bmchandler
import confluent.exceptions as exc
import eventlet
webclient = eventlet.import_patched('pyghmi.util.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 +35,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 +49,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 +71,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 +167,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 +200,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
@@ -0,0 +1,185 @@
# Copyright 2019 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import confluent.discovery.handlers.generic as generic
import confluent.exceptions as exc
import confluent.netutil as netutil
import confluent.util as util
import eventlet.support.greendns
import json
import urllib
getaddrinfo = eventlet.support.greendns.getaddrinfo
webclient = eventlet.import_patched('pyghmi.util.webclient')
class NodeHandler(generic.NodeHandler):
devname = 'TSM'
DEFAULT_USER = 'USERID'
DEFAULT_PASS = 'PASSW0RD'
def __init__(self, info, configmanager):
self.trieddefault = None
self.targuser = None
self.curruser = None
self.currpass = None
self.targpass = None
self.nodename = None
self.csrftok = None
self.channel = None
self.atdefault = True
super(NodeHandler, self).__init__(info, configmanager)
def validate_cert(self, certificate):
# broadly speaking, merely checks consistency moment to moment,
# but if https_cert gets stricter, this check means something
fprint = util.get_fingerprint(self.https_cert)
return util.cert_matches(fprint, certificate)
def _get_wc(self):
authdata = { # start by trying factory defaults
'username': self.DEFAULT_USER,
'password': self.DEFAULT_PASS,
}
if not self.trieddefault:
wc = webclient.SecureHTTPConnection(self.ipaddr, 443, verifycallback=self.validate_cert)
rsp, status = wc.grab_json_response_with_status('/api/session', urllib.urlencode(authdata))
if status > 400:
self.trieddefault = True
if '555' in rsp:
passchange = {
'Password': self.targpass,
'RetypePassword': self.targpass,
'param': 4,
'default_password': self.DEFAULT_PASS,
'username': self.DEFAULT_USER
}
rsp, status = wc.grab_json_response_with_status('/api/reset-pass', urllib.urlencode(passchange))
authdata['password'] = self.targpass
rsp, status = wc.grab_json_response_with_status('/api/session', urllib.urlencode(authdata))
self.csrftok = rsp['CSRFToken']
self.channel = rsp['channel']
self.curruser = self.DEFAULT_USER
self.currpass = self.targpass
return wc
else:
self.curruser = self.DEFAULT_USER
self.currpass = self.DEFAULT_PASS
self.csrftok = rsp['CSRFToken']
self.channel = rsp['channel']
return wc
if self.curruser:
authdata['username'] = self.curruser
authdata['password'] = self.currpass
rsp, status = wc.grab_json_response_with_status('/api/session', urllib.urlencode(authdata))
if rsp.status != 200:
return None
self.csrftok = rsp['CSRFToken']
self.channel = rsp['channel']
return wc
authdata['username'] = self.targuser
authdata['password'] = self.targpass
rsp, status = wc.grab_json_response_with_status('/api/session', urllib.urlencode(authdata))
if status != 200:
return None
self.curruser = self.targuser
self.currpass = self.targpass
self.csrftok = rsp['CSRFToken']
self.channel = rsp['channel']
return wc
def config(self, nodename):
self.nodename = nodename
creds = self.configmanager.get_node_attributes(
nodename, ['secret.hardwaremanagementuser',
'secret.hardwaremanagementpassword',
'hardwaremanagement.manager', 'hardwaremanagement.method', 'console.method'],
True)
cd = creds.get(nodename, {})
user, passwd, _ = self.get_node_credentials(
nodename, creds, self.DEFAULT_USER, self.DEFAULT_PASS)
self.targuser = user
self.targpass = passwd
wc = self._get_wc()
wc.set_header('X-CSRFTOKEN', self.csrftok)
curruserinfo = {}
authupdate = False
wc.set_header('Content-Type', 'application/json')
if user != self.curruser:
authupdate = True
if not curruserinfo:
curruserinfo = wc.grab_json_response('/api/settings/users')
authchg = curruserinfo[1]
authchg['name'] = user
if passwd != self.currpass:
authupdate = True
if not curruserinfo:
curruserinfo = wc.grab_json_response('/api/settings/users')
authchg = curruserinfo[1]
authchg['changepassword'] = 0
authchg['password_size'] = 'bytes_20'
authchg['password'] = passwd
authchg['confirm_password'] = passwd
if authupdate:
rsp, status = wc.grab_json_response_with_status('/api/settings/users/2', authchg, method='PUT')
if (cd.get('hardwaremanagement.method', {}).get('value', 'ipmi') != 'redfish'
or cd.get('console.method', {}).get('value', None) == 'ipmi'):
# IPMI must be enabled per user config
wc.grab_json_response('/api/settings/ipmilanconfig', {
'ipv4_enable': 1, 'ipv6_enable': 1,
'uncheckedipv4lanEnable': 0, 'uncheckedipv6lanEnable': 0,
'checkedipv4lanEnable': 1, 'checkedipv6lanEnable': 1})
if ('hardwaremanagement.manager' in cd and
cd['hardwaremanagement.manager']['value'] and
not cd['hardwaremanagement.manager']['value'].startswith(
'fe80::')):
newip = cd['hardwaremanagement.manager']['value']
newipinfo = getaddrinfo(newip, 0)[0]
newip = newipinfo[-1][0]
if ':' in newip:
raise exc.NotImplementedException('IPv6 remote config TODO')
currnet = wc.grab_json_response('/api/settings/network')
for net in currnet:
if net['channel_number'] == self.channel and net['lan_enable'] == 0:
# ignore false indication and switch to 8 (dedicated)
self.channel = 8
if net['channel_number'] == self.channel:
# we have found the interface to potentially manipulate
if net['ipv4_address'] != newip:
netconfig = netutil.get_nic_config(self.configmanager, nodename, ip=newip)
newmask = netutil.cidr_to_mask(netconfig['prefix'])
net['ipv4_address'] = newip
net['ipv4_subnet'] = newmask
if netconfig['ipv4_gateway']:
net['ipv4_gateway'] = netconfig['ipv4_gateway']
net['ipv4_dhcp_enable'] = 0
rsp, status = wc.grab_json_response_with_status(
'/api/settings/network/{0}'.format(net['id']), net, method='PUT')
break
elif self.ipaddr.startswith('fe80::'):
self.configmanager.set_node_attributes(
{nodename: {'hardwaremanagement.manager': self.ipaddr}})
else:
raise exc.TargetEndpointUnreachable(
'hardwaremanagement.manager must be set to desired address (No IPv6 Link Local detected)')
rsp, status = wc.grab_json_response_with_status('/api/session', method='DELETE')
if __name__ == '__main__':
import confluent.config.configmanager as cfm
c = cfm.ConfigManager(None)
import sys
info = {'addresses': [[sys.argv[1]]] }
print(repr(info))
testr = NodeHandler(info, c)
testr.config(sys.argv[2])
@@ -16,13 +16,14 @@ import base64
import confluent.discovery.handlers.imm as immhandler
import confluent.netutil as netutil
import confluent.util as util
import errno
import eventlet
import eventlet.support.greendns
import json
import os
import pyghmi.exceptions as pygexc
xcc = eventlet.import_patched('pyghmi.redfish.oem.lenovo.xcc')
import pyghmi.util.webclient as webclient
import eventlet.green.socket as socket
webclient = eventlet.import_patched('pyghmi.util.webclient')
import struct
getaddrinfo = eventlet.support.greendns.getaddrinfo
@@ -41,10 +42,13 @@ class NodeHandler(immhandler.NodeHandler):
devname = 'XCC'
def __init__(self, info, configmanager):
self._xcchdlr = None
self._wc = None
self.nodename = None
self.tmpnodename = None
self.tmppasswd = None
self._atdefaultcreds = True
self._needpasswordchange = True
self._currcreds = (None, None)
super(NodeHandler, self).__init__(info, configmanager)
@classmethod
@@ -53,23 +57,54 @@ class NodeHandler(immhandler.NodeHandler):
# This is not adequate for being satisfied
return bool(info.get('attributes', {}))
def preconfig(self):
def preconfig(self, possiblenode):
self.tmpnodename = possiblenode
ff = self.info.get('attributes', {}).get('enclosure-form-factor', '')
if ff not in ('dense-computing', [u'dense-computing']):
# skip preconfig for non-SD530 servers
return
self.trieddefault = None # Reset state on a preconfig attempt
# attempt to enable SMM
#it's normal to get a 'not supported' (193) for systems without an SMM
# need to branch on 3.00+ firmware
currfirm = self.info.get('attributes', {}).get('firmware-image-info', [''])[0]
currfirm = currfirm.split(':')
if len(currfirm) > 1:
currfirm = float(currfirm[1])
disableipmi = False
if currfirm >= 3:
# IPMI is disabled and we need it, also we need to go to *some* password
wc = self.wc
if not wc:
# We cannot try to enable SMM here without risking real credentials
# on the wire to untrusted parties
return
wc.grab_json_response('/api/providers/logout')
wc.set_basic_credentials(self._currcreds[0], self._currcreds[1])
rsp = wc.grab_json_response('/redfish/v1/Managers/1/NetworkProtocol')
if not rsp.get('IPMI', {}).get('ProtocolEnabled', True):
disableipmi = True
_, _ = wc.grab_json_response_with_status(
'/redfish/v1/Managers/1/NetworkProtocol',
{'IPMI': {'ProtocolEnabled': True}}, method='PATCH')
ipmicmd = None
try:
ipmicmd = self._get_ipmicmd()
ipmicmd = self._get_ipmicmd(self._currcreds[0], self._currcreds[1])
ipmicmd.xraw_command(netfn=0x3a, command=0xf1, data=(1,))
except pygexc.IpmiException as e:
if (e.ipmicode != 193 and 'Unauthorized name' not in str(e) and
'Incorrect password' not in str(e)):
# raise an issue if anything other than to be expected
if disableipmi:
_, _ = wc.grab_json_response_with_status(
'/redfish/v1/Managers/1/NetworkProtocol',
{'IPMI': {'ProtocolEnabled': False}}, method='PATCH')
raise
self.trieddefault = True
if disableipmi:
_, _ = wc.grab_json_response_with_status(
'/redfish/v1/Managers/1/NetworkProtocol',
{'IPMI': {'ProtocolEnabled': False}}, method='PATCH')
#TODO: decide how to clean out if important
#as it stands, this can step on itself
#if ipmicmd:
@@ -81,29 +116,111 @@ class NodeHandler(immhandler.NodeHandler):
fprint = util.get_fingerprint(self.https_cert)
return util.cert_matches(fprint, certificate)
def get_webclient(self, username, password, newpassword):
wc = self._wc.dupe()
try:
wc.connect()
except socket.error as se:
if se.errno != errno.ECONNREFUSED:
raise
return (None, None)
pwdchanged = False
adata = json.dumps({'username': username,
'password': password
})
headers = {'Connection': 'keep-alive',
'Content-Type': 'application/json'}
wc.request('POST', '/api/login', adata, headers)
rsp = wc.getresponse()
if rsp.status != 200 and password == 'PASSW0RD':
rsp.read()
adata = json.dumps({
'username': username,
'password': newpassword,
})
headers = {'Connection': 'keep-alive',
'Content-Type': 'application/json'}
wc.request('POST', '/api/login', adata, headers)
rsp = wc.getresponse()
if rsp.status == 200:
pwdchanged = True
password = newpassword
else:
rsp.read()
return (None, None)
if rsp.status == 200:
self._currcreds = (username, password)
wc.set_basic_credentials(username, password)
rspdata = json.loads(rsp.read())
wc.set_header('Content-Type', 'application/json')
wc.set_header('Authorization', 'Bearer ' + rspdata['access_token'])
if '_csrf_token' in wc.cookies:
wc.set_header('X-XSRF-TOKEN', wc.cookies['_csrf_token'])
if rspdata.get('pwchg_required', None) == 'true':
wc.request('POST', '/api/function', json.dumps(
{'USER_UserPassChange': '1,{0}'.format(newpassword)}))
rsp = wc.getresponse()
rsp.read()
if rsp.status != 200:
return (None, None)
self._currcreds = (username, newpassword)
wc.set_basic_credentials(username, newpassword)
pwdchanged = True
if '_csrf_token' in wc.cookies:
wc.set_header('X-XSRF-TOKEN', wc.cookies['_csrf_token'])
return (wc, pwdchanged)
@property
def wc(self):
passwd = None
isdefault = True
if self._wc is None:
self._wc = webclient.SecureHTTPConnection(
self.ipaddr, 443, verifycallback=self.validate_cert)
self._wc.connect()
self._xcchdlr = xcc.OEMHandler(None, None, self._wc, False)
if not self.trieddefault:
self._xcchdlr.set_credentials('USERID', 'PASSW0RD')
wc = self._xcchdlr.get_webclient()
nodename = None
if self.nodename:
nodename = self.nodename
inpreconfig = False
elif self.tmpnodename:
nodename = None
inpreconfig = True
if self._currcreds[0] is not None:
wc, pwdchanged = self.get_webclient(self._currcreds[0], self._currcreds[1], None)
if wc:
return wc
if nodename:
creds = self.configmanager.get_node_attributes(
nodename, ['secret.hardwaremanagementuser',
'secret.hardwaremanagementpassword'], decrypt=True)
user, passwd, isdefault = self.get_node_credentials(
nodename, creds, 'USERID', 'PASSW0RD')
if not self.trieddefault:
if not passwd:
# So in preconfig context, we don't have admin permission to
# actually divulge anything to the target
# however the target *will* demand a new password... if it's currently
# PASSW0RD
# use TempW0rd42 to avoid divulging a real password on the line
# This is replacing one well known password (PASSW0RD) with another
# (TempW0rd42)
passwd = 'TempW0rd42'
wc, pwdchanged = self.get_webclient('USERID', 'PASSW0RD', passwd)
if wc:
if pwdchanged:
if inpreconfig:
self.tmppasswd = passwd
else:
self._needpasswordchange = False
return wc
self.trieddefault = True
creds = self.configmanager.get_node_attributes(
self.nodename, ['secret.hardwaremanagementuser',
'secret.hardwaremanagementpassword'], decrypt=True)
user, passwd, isdefault = self.get_node_credentials(
self.nodename, creds, 'USERID', 'PASSW0RD')
if isdefault:
return
self._atdefaultcreds = False
self._xcchdlr.set_credentials(user, passwd)
wc = self._xcchdlr.get_webclient()
if self.tmppasswd:
wc, _ = self.get_webclient('USERID', self.tmppasswd, passwd)
else:
wc, _ = self.get_webclient(user, passwd, None)
if wc:
return wc
@@ -128,7 +245,9 @@ class NodeHandler(immhandler.NodeHandler):
if name.lower() == 'reuse':
ruleset['USER_GlobalMinPassReuseCycle'] = value
try:
self.wc.grab_json_response('/api/dataset', ruleset)
wc = self.wc
wc.grab_json_response('/api/dataset', ruleset)
wc.grab_json_response('/api/providers/logout')
except Exception as e:
print(repr(e))
pass
@@ -155,12 +274,15 @@ class NodeHandler(immhandler.NodeHandler):
if not uid:
raise Exception("XCC has neither the default user nor configured user")
# The following will work if the password is force change or normal..
wc.grab_json_response('/api/function',
{'USER_UserPassChange': '{0},{1}'.format(uid, passwd)})
if self._needpasswordchange and self.tmppasswd != passwd:
wc.grab_json_response('/api/function',
{'USER_UserPassChange': '{0},{1}'.format(uid, passwd)})
if username != 'USERID':
wc.grab_json_response(
'/api/function',
{'USER_UserModify': '{0},{1},,1,4,0,0,0,0,,8,'.format(uid, username)})
self.tmppasswd = None
self._currcreds = (username, passwd)
def _convert_sha256account(self, user, passwd, wc):
# First check if the specified user is sha256...
@@ -182,6 +304,7 @@ class NodeHandler(immhandler.NodeHandler):
tpass = base64.b64encode(os.urandom(9)) + 'Iw47$'
userparams = "{0},6pmu0ezczzcp,{1},1,4,0,0,0,0,,8,".format(tmpuid, tpass)
result = wc.grab_json_response('/api/function', {'USER_UserCreate': userparams})
wc.grab_json_response('/api/providers/logout')
adata = json.dumps({
'username': '6pmu0ezczzcp',
'password': tpass,
@@ -201,11 +324,20 @@ class NodeHandler(immhandler.NodeHandler):
'/api/function',
{'USER_UserPassChange': '{0},{1}'.format(tmpuid, newpass)})
nwc.grab_json_response('/api/function', {'USER_UserDelete': "{0},{1}".format(curruser['users_user_id'], user)})
userparams = "{0},{1},{2},1,4,0,0,0,0,,8,".format(curruser['users_user_id'], user, passwd)
userparams = "{0},{1},{2},1,4,0,0,0,0,,8,".format(curruser['users_user_id'], user, tpass)
nwc.grab_json_response('/api/function', {'USER_UserCreate': userparams})
nwc.grab_json_response('/api/providers/logout')
nwc, pwdchanged = self.get_webclient(user, tpass, passwd)
if not pwdchanged:
nwc.grab_json_response(
'/api/function',
{'USER_UserPassChange': '{0},{1}'.format(curruser['users_user_id'], passwd)})
nwc.grab_json_response('/api/providers/logout')
finally:
self._wc = None
self.wc.grab_json_response('/api/function', {'USER_UserDelete': "{0},{1}".format(tmpuid, '6pmu0ezczzcp')})
wc = self.wc
wc.grab_json_response('/api/function', {'USER_UserDelete': "{0},{1}".format(tmpuid, '6pmu0ezczzcp')})
wc.grab_json_response('/api/providers/logout')
def config(self, nodename, reset=False):
self.nodename = nodename
@@ -229,8 +361,20 @@ class NodeHandler(immhandler.NodeHandler):
cd = self.configmanager.get_node_attributes(
nodename, ['secret.hardwaremanagementuser',
'secret.hardwaremanagementpassword',
'hardwaremanagement.manager'], True)
'hardwaremanagement.manager', 'hardwaremanagement.method', 'console.method'],
True)
cd = cd.get(nodename, {})
if (cd.get('hardwaremanagement.method', {}).get('value', 'ipmi') != 'redfish'
or cd.get('console.method', {}).get('value', None) == 'ipmi'):
nwc = wc.dupe()
nwc.set_basic_credentials(self._currcreds[0], self._currcreds[1])
rsp = nwc.grab_json_response('/redfish/v1/Managers/1/NetworkProtocol')
if not rsp.get('IPMI', {}).get('ProtocolEnabled', True):
# User has indicated IPMI support, but XCC is currently disabled
# change XCC to be consistent
_, _ = nwc.grab_json_response_with_status(
'/redfish/v1/Managers/1/NetworkProtocol',
{'IPMI': {'ProtocolEnabled': True}}, method='PATCH')
if ('hardwaremanagement.manager' in cd and
cd['hardwaremanagement.manager']['value'] and
not cd['hardwaremanagement.manager']['value'].startswith(
@@ -242,17 +386,24 @@ class NodeHandler(immhandler.NodeHandler):
raise exc.NotImplementedException('IPv6 remote config TODO')
netconfig = netutil.get_nic_config(self.configmanager, nodename, ip=newip)
newmask = netutil.cidr_to_mask(netconfig['prefix'])
# do not change the ipv4_config if the current config looks
statargs = {'ENET_IPv4Ena': '1', 'ENET_IPv4AddrSource': '0', 'ENET_IPv4StaticIPAddr': newip, 'ENET_IPv4StaticIPNetMask': newmask}
if netconfig['ipv4_gateway']:
statargs['ENET_IPv4GatewayIPAddr'] = netconfig['ipv4_gateway']
wc.grab_json_response('/api/dataset', statargs)
currinfo = wc.grab_json_response('/api/providers/logoninfo')
currip = currinfo.get('items', [{}])[0].get('ipv4_address', '')
# do not change the ipv4_config if the current config looks right already
if currip != newip:
statargs = {
'ENET_IPv4Ena': '1', 'ENET_IPv4AddrSource': '0',
'ENET_IPv4StaticIPAddr': newip, 'ENET_IPv4StaticIPNetMask': newmask
}
if netconfig['ipv4_gateway']:
statargs['ENET_IPv4GatewayIPAddr'] = netconfig['ipv4_gateway']
wc.grab_json_response('/api/dataset', statargs)
elif self.ipaddr.startswith('fe80::'):
self.configmanager.set_node_attributes(
{nodename: {'hardwaremanagement.manager': self.ipaddr}})
else:
raise exc.TargetEndpointUnreachable(
'hardwaremanagement.manager must be set to desired address (No IPv6 Link Local detected)')
wc.grab_json_response('/api/providers/logout')
ff = self.info.get('attributes', {}).get('enclosure-form-factor', '')
if ff not in ('dense-computing', [u'dense-computing']):
return
@@ -98,7 +98,7 @@ def snoop(handler, protocol=None):
netaddr = ':'.join(['{0:02x}'.format(x) for x in netaddr])
optidx = 0
try:
optidx = rq.index('\x63\x82\x53\x63') + 4
optidx = rq.index(b'\x63\x82\x53\x63') + 4
except ValueError:
continue
uuid, arch = find_info_in_options(rq, optidx)
@@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
# Copyright 2017-2019 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -27,6 +27,8 @@ import traceback
_slp_services = set([
'service:management-hardware.IBM:integrated-management-module2',
'service:lenovo-smm',
'service:ipmi',
'service:lighttpd',
'service:management-hardware.Lenovo:lenovo-xclarity-controller',
'service:management-hardware.IBM:chassis-management-module',
'service:management-hardware.Lenovo:chassis-management-module',
@@ -47,7 +49,6 @@ except AttributeError:
IPPROTO_IPV6 = 41 # Assume Windows value if socket is missing it
def _parse_slp_header(packet):
packet = bytearray(packet)
if len(packet) < 16 or packet[0] != 2:
@@ -245,20 +246,24 @@ def _grab_rsps(socks, rsps, interval, xidmap):
def _parse_attrlist(attrstr):
attribs = {}
previousattrlen = None
while attrstr:
if len(attrstr) == previousattrlen:
raise Exception('Looping in attrstr parsing')
previousattrlen = len(attrstr)
if attrstr[0] == '(':
if ')' not in attrstr:
if b')' not in attrstr:
attribs['INCOMPLETE'] = True
return attribs
currattr = attrstr[1:attrstr.index(')')]
if '=' not in currattr: # Not allegedly kosher, but still..
currattr = attrstr[1:attrstr.index(b')')]
if b'=' not in currattr: # Not allegedly kosher, but still..
currattr = currattr.decode('utf-8')
attribs[currattr] = None
else:
attrname, attrval = currattr.split('=', 1)
attrname = attrname.decode('utf-8')
attribs[attrname] = []
for val in attrval.split(','):
for val in attrval.split(b','):
try:
val = val.decode('utf-8')
except UnicodeDecodeError:
@@ -272,9 +277,9 @@ def _parse_attrlist(attrstr):
val = finalval
if 'uuid' in attrname and len(val) == 16:
lebytes = struct.unpack_from(
'<IHH', buffer(val[:8]))
'<IHH', memoryview(val[:8]))
bebytes = struct.unpack_from(
'>HHI', buffer(val[8:]))
'>HHI', memoryview(val[8:]))
val = '{0:08X}-{1:04X}-{2:04X}-{3:04X}-' \
'{4:04X}{5:08X}'.format(
lebytes[0], lebytes[1], lebytes[2], bebytes[0],
@@ -282,12 +287,12 @@ def _parse_attrlist(attrstr):
).lower()
attribs[attrname].append(val)
attrstr = attrstr[attrstr.index(')'):]
elif attrstr[0] == ',':
elif attrstr[0] == b','[0]:
attrstr = attrstr[1:]
elif ',' in attrstr:
currattr = attrstr[:attrstr.index(',')]
elif b',' in attrstr:
currattr = attrstr[:attrstr.index(b',')]
attribs[currattr] = None
attrstr = attrstr[attrstr.index(','):]
attrstr = attrstr[attrstr.index(b','):]
else:
currattr = attrstr
attribs[currattr] = None
@@ -484,6 +489,18 @@ def snoop(handler, protocol=None):
_add_attributes(peerbymacaddress[mac])
peerbymacaddress[mac]['hwaddr'] = mac
peerbymacaddress[mac]['protocol'] = protocol
if 'service:ipmi' in peerbymacaddress[mac]['services']:
if 'service:ipmi//Athena:623' in peerbymacaddress[mac].get('urls', ()):
peerbymacaddress[mac]['services'] = ['service:thinkagile-storage']
else:
continue
if 'service:lightttpd' in peerbymacaddress[mac]['services']:
currinf = peerbymacaddress[mac]
curratt = currinf.get('attributes', {})
if curratt.get('System-Manufacturing', [None])[0] == 'Lenovo' and curratt.get('type', [None])[0] == 'LenovoThinkServer':
peerbymacaddress[mac]['services'] = ['service:lenovo-tsm']
else:
continue
handler(peerbymacaddress[mac])
except Exception as e:
tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
@@ -547,6 +564,11 @@ def scan(srvtypes=_slp_services, addresses=None, localonly=False):
_grab_rsps((net, net4), rsps, 1, xidmap)
# now to analyze and flesh out the responses
for id in rsps:
if 'service:ipmi' in rsps[id]['services']:
if 'service:ipmi://Athena:623' in rsps[id]['urls']:
rsps[id]['services'] = ['service:thinkagile-storage']
else:
continue
if localonly:
for addr in rsps[id]['addresses']:
if 'fe80' in addr[0]:
@@ -554,6 +576,15 @@ def scan(srvtypes=_slp_services, addresses=None, localonly=False):
else:
continue
_add_attributes(rsps[id])
if 'service:lighttpd' in rsps[id]['services']:
currinf = rsps[id]
curratt = currinf.get('attributes', {})
if curratt.get('System-Manufacturing', [None])[0] == 'Lenovo' and curratt.get('type', [None])[0] == 'LenovoThinkServer':
currinf['services'] = ['service:lenovo-tsm']
curratt['enclosure-serial-number'] = curratt['Product-Serial']
curratt['enclosure-machinetype-model'] = curratt['Machine-Type']
else:
continue
del rsps[id]['payload']
del rsps[id]['function']
del rsps[id]['xid']
@@ -78,8 +78,13 @@ def snoop(handler, byehandler=None):
for i4 in util.list_ips():
ssdp4mcast = socket.inet_pton(socket.AF_INET, mcastv4addr) + \
socket.inet_aton(i4['addr'])
net4.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP,
ssdp4mcast)
try:
net4.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP,
ssdp4mcast)
except socket.error as e:
if e.errno != 98:
# errno 98 can happen if aliased, skip for now
raise
net4.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
net4.bind(('', 1900))
net6.bind(('', 1900))
+2
View File
@@ -49,6 +49,7 @@ def forward_port(sock, target, clientip, sessionid):
continue
try:
client = socket.create_connection((target, 443))
client.setsockopt(socket.IPPROTO_TCP, socket.TCP_MAXSEG, 1456)
except Exception:
conn.close()
continue
@@ -68,6 +69,7 @@ def forward_video():
try:
vidclient = socket.create_connection((vidtargetbypeer[cli[0]],
3900))
vidclient.setsockopt(socket.IPPROTO_TCP, socket.TCP_MAXSEG, 1456)
except Exception:
conn.close()
continue
+8 -2
View File
@@ -17,7 +17,10 @@
# This SCGI server provides a http wrap to confluent api
# It additionally manages httprequest console sessions
import base64
import Cookie
try:
import Cookie
except ModuleNotFoundError:
import http.cookies as Cookie
import confluent.auth as auth
import confluent.config.attributes as attribs
import confluent.consoleserver as consoleserver
@@ -39,7 +42,10 @@ import socket
import sys
import traceback
import time
import urlparse
try:
import urlparse
except ModuleNotFoundError:
import urllib.parse as urlparse
import eventlet.wsgi
#scgi = eventlet.import_patched('flup.server.scgi')
tlvdata = confluent.tlvdata
+7 -1
View File
@@ -76,6 +76,10 @@ import stat
import struct
import time
import traceback
try:
unicode
except NameError:
unicode = str
daemonized = False
logfull = False
@@ -176,6 +180,8 @@ class BaseRotatingHandler(object):
self.textfile = open(self.textpath, mode='ab')
if self.binfile is None:
self.binfile = open(self.binpath, mode='ab')
if not isinstance(textrecord, bytes):
textrecord = textrecord.encode('utf-8')
self.textfile.write(textrecord)
self.binfile.write(binrecord)
self.textfile.flush()
@@ -742,7 +748,7 @@ class Logger(object):
pass
def log(self, logdata=None, ltype=None, event=0, eventdata=None):
if type(logdata) not in (str, unicode, dict):
if type(logdata) not in (bytes, unicode, dict):
raise Exception("Unsupported logdata")
if ltype is None:
if type(logdata) == dict:
+3 -1
View File
@@ -43,9 +43,11 @@ except ImportError:
import confluent.discovery.core as disco
import eventlet
dbgif = False
if map(int, (eventlet.__version__.split('.'))) > [0, 18]:
try:
import eventlet.backdoor as backdoor
dbgif = True
except Exception:
pass
havefcntl = True
try:
import fcntl
+26 -16
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.
@@ -25,6 +25,11 @@ from copy import deepcopy
from datetime import datetime
import json
try:
unicode
except NameError:
unicode = str
valid_health_values = set([
'ok',
'warning',
@@ -33,10 +38,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:
@@ -52,7 +59,7 @@ def _htmlify_structure(indict):
if isinstance(indict, dict):
for key in sorted(indict):
ret += "<li>{0}: ".format(key)
if type(indict[key]) in (str, unicode, float, int):
if type(indict[key]) in (bytes, unicode, float, int):
ret += str(indict[key])
elif isinstance(indict[key], datetime):
ret += indict[key].strftime('%Y-%m-%dT%H:%M:%S')
@@ -60,7 +67,7 @@ def _htmlify_structure(indict):
ret += _htmlify_structure(indict[key])
elif isinstance(indict, list):
if len(indict) > 0:
if type(indict[0]) in (str, unicode, None):
if type(indict[0]) in (bytes, unicode, None):
nd = []
for datum in indict:
if datum is None:
@@ -154,7 +161,7 @@ class ConfluentMessage(object):
'<input type="checkbox" name="restexplorerhonorkey" '
'value="{1}">\r').format(valtype, key, self.desc)
return snippet
if (isinstance(val, bool) or isinstance(val, str) or
if (isinstance(val, bool) or isinstance(val, bytes) or
isinstance(val, unicode)):
value = str(val)
elif val is not None and 'value' in val:
@@ -448,10 +455,11 @@ def get_input_message(path, operation, inputdata, nodes=None, multinode=False,
elif (path[:4] == ['configuration', 'management_controller', 'ntp',
'servers'] and operation != 'retrieve' and len(path) == 5):
return InputNTPServer(path, nodes, inputdata)
elif (path[:3] == ['configuration', 'system', 'all'] and
elif (path[:3] in (['configuration', 'system', 'all'],
['configuration', 'management_controller', 'extended']) and
operation != 'retrieve'):
return InputConfigChangeSet(path, inputdata, nodes, configmanager)
elif (path[:3] == ['configuration', 'system', 'clear'] and
elif (path[0] == 'configuration' and path[2] == 'clear' and
operation != 'retrieve'):
return InputConfigClear(path, inputdata, nodes, configmanager)
elif (path[:3] == ['configuration', 'storage', 'disks'] and
@@ -584,7 +592,7 @@ class InputConfigChangeSet(InputExpression):
endattrs = {}
for attr in attrs:
origval = attrs[attr]
if isinstance(origval, str) or isinstance(origval, unicode):
if isinstance(origval, bytes) or isinstance(origval, unicode):
origval = {'expression': origval}
if 'expression' not in origval:
endattrs[attr] = attrs[attr]
@@ -611,7 +619,7 @@ class InputAttributes(ConfluentMessage):
if nodes is None:
self.attribs = inputdata
for attrib in self.attribs:
if type(self.attribs[attrib]) in (str, unicode):
if type(self.attribs[attrib]) in (bytes, unicode):
try:
# ok, try to use format against the string
# store back result to the attribute to
@@ -637,7 +645,7 @@ class InputAttributes(ConfluentMessage):
return {}
nodeattr = deepcopy(self.nodeattribs[node])
for attr in nodeattr:
if type(nodeattr[attr]) in (str, unicode):
if type(nodeattr[attr]) in (bytes, unicode):
try:
# as above, use format() to see if string follows
# expression, store value back in case of escapes
@@ -740,7 +748,7 @@ class InputCredential(ConfluentMessage):
if len(path) == 4:
inputdata['uid'] = path[-1]
# if the operation is 'create' check if all fields are present
if (isinstance(inputdata['uid'], str) and
if (type(inputdata['uid']) in (bytes, unicode) and
not inputdata['uid'].isdigit()):
inputdata['uid'] = inputdata['uid']
else:
@@ -766,7 +774,7 @@ class InputCredential(ConfluentMessage):
return {}
credential = deepcopy(self.credentials[node])
for attr in credential:
if type(credential[attr]) in (str, unicode):
if type(credential[attr]) in (bytes, unicode):
try:
# as above, use format() to see if string follows
# expression, store value back in case of escapes
@@ -820,6 +828,7 @@ class InputIdentifyMessage(ConfluentInputMessage):
valid_values = set([
'on',
'off',
'blink',
])
keyname = 'identify'
@@ -1247,6 +1256,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(
@@ -1354,7 +1364,7 @@ class AlertDestination(ConfluentMessage):
class InputAlertDestination(ConfluentMessage):
valid_alert_params = {
'acknowledge': lambda x: False if type(x) in (unicode,str) and x.lower() == 'false' else bool(x),
'acknowledge': lambda x: False if type(x) in (unicode, bytes) and x.lower() == 'false' else bool(x),
'acknowledge_timeout': lambda x: int(x) if x and x.isdigit() else None,
'ip': lambda x: x,
'retries': lambda x: int(x)
@@ -1568,7 +1578,7 @@ class Attributes(ConfluentMessage):
nkv = {}
self.notnode = name is None
for key in kv:
if type(kv[key]) in (str, unicode):
if type(kv[key]) in (bytes, unicode):
nkv[key] = {'value': kv[key]}
else:
nkv[key] = kv[key]
@@ -1679,7 +1689,7 @@ class CryptedAttributes(Attributes):
# for now, just keep the dictionary keys and discard crypt value
self.desc = desc
nkv = {}
for key in kv.iterkeys():
for key in kv:
nkv[key] = {'isset': False}
try:
if kv[key] is not None and kv[key]['cryptvalue'] != '':
+4 -4
View File
@@ -26,7 +26,7 @@ neightime = 0
import re
_validmac = re.compile('..:..:..:..:..:..')
_validmac = re.compile(b'..:..:..:..:..:..')
def update_neigh():
@@ -39,11 +39,11 @@ def update_neigh():
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
(neighdata, err) = ipn.communicate()
for entry in neighdata.split('\n'):
entry = entry.split(' ')
for entry in neighdata.split(b'\n'):
entry = entry.split(b' ')
if len(entry) < 5 or not entry[4]:
continue
if entry[0] in ('192.168.0.100', '192.168.70.100', '192.168.70.125'):
if entry[0] in (b'192.168.0.100', b'192.168.70.100', b'192.168.70.125'):
# Note that these addresses are common static ip addresses
# that are hopelessly ambiguous if there are many
# so ignore such entries and move on
@@ -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:
+9 -4
View File
@@ -25,6 +25,11 @@ import itertools
import pyparsing as pp
import re
try:
range = xrange
except NameError:
pass
# construct custom grammar with pyparsing
_nodeword = pp.Word(pp.alphanums + '~^$/=-_:.*+!')
_nodebracket = pp.QuotedString(quoteChar='[', endQuoteChar=']',
@@ -166,7 +171,7 @@ class NodeRange(object):
return self.failorreturn(seqrange)
finalfmt = ''
iterators = []
for idx in xrange(len(leftbits)):
for idx in range(len(leftbits)):
if leftbits[idx] == rightbits[idx]:
finalfmt += leftbits[idx]
elif leftbits[idx][0] in pp.alphas:
@@ -181,7 +186,7 @@ class NodeRange(object):
if leftnum > rightnum:
width = len(rightbits[idx])
minnum = rightnum
maxnum = leftnum + 1 # xrange goes to n-1...
maxnum = leftnum + 1 # range goes to n-1...
elif rightnum > leftnum:
width = len(leftbits[idx])
minnum = leftnum
@@ -189,7 +194,7 @@ class NodeRange(object):
else: # differently padded, but same number...
return self.failorreturn(seqrange)
numformat = '{0:0%d}' % width
for num in xrange(minnum, maxnum):
for num in range(minnum, maxnum):
curseq.append(numformat.format(num))
results = set([])
for combo in itertools.product(*iterators):
@@ -222,7 +227,7 @@ class NodeRange(object):
if self.cfm is None:
raise Exception('Verification configmanager required')
return set(self.cfm.filter_node_attributes(element, filternodes))
for idx in xrange(len(element)):
for idx in range(len(element)):
if element[idx][0] == '[':
nodes = set([])
for numeric in NodeRange(element[idx][1:-1]).nodes:
+235
View File
@@ -0,0 +1,235 @@
# Pulled from:
# https://raw.githubusercontent.com/FirefighterBlu3/python-pam/fe44b334970f421635d9e373b563c9e6566613bd/pam.py
# and https://github.com/FirefighterBlu3/python-pam/pull/16/files
# (c) 2007 Chris AtLee <chris@atlee.ca>
# Licensed under the MIT license:
# http://www.opensource.org/licenses/mit-license.php
#
# Original author: Chris AtLee
#
# Modified by David Ford, 2011-12-6
# added py3 support and encoding
# added pam_end
# added pam_setcred to reset credentials after seeing Leon Walker's remarks
# added byref as well
# use readline to prestuff the getuser input
'''
PAM module for python
Provides an authenticate function that will allow the caller to authenticate
a user against the Pluggable Authentication Modules (PAM) on the system.
Implemented using ctypes, so no compilation is necessary.
'''
__all__ = ['pam']
__version__ = '1.8.4'
__author__ = 'David Ford <david@blue-labs.org>'
__released__ = '2018 June 15'
import sys
from ctypes import CDLL, POINTER, Structure, CFUNCTYPE, cast, byref, sizeof
from ctypes import c_void_p, c_size_t, c_char_p, c_char, c_int
from ctypes import memmove
from ctypes.util import find_library
class PamHandle(Structure):
"""wrapper class for pam_handle_t pointer"""
_fields_ = [ ("handle", c_void_p) ]
def __init__(self):
Structure.__init__(self)
self.handle = 0
class PamMessage(Structure):
"""wrapper class for pam_message structure"""
_fields_ = [ ("msg_style", c_int), ("msg", c_char_p) ]
def __repr__(self):
return "<PamMessage %i '%s'>" % (self.msg_style, self.msg)
class PamResponse(Structure):
"""wrapper class for pam_response structure"""
_fields_ = [ ("resp", c_char_p), ("resp_retcode", c_int) ]
def __repr__(self):
return "<PamResponse %i '%s'>" % (self.resp_retcode, self.resp)
conv_func = CFUNCTYPE(c_int, c_int, POINTER(POINTER(PamMessage)), POINTER(POINTER(PamResponse)), c_void_p)
class PamConv(Structure):
"""wrapper class for pam_conv structure"""
_fields_ = [ ("conv", conv_func), ("appdata_ptr", c_void_p) ]
# Various constants
PAM_PROMPT_ECHO_OFF = 1
PAM_PROMPT_ECHO_ON = 2
PAM_ERROR_MSG = 3
PAM_TEXT_INFO = 4
PAM_REINITIALIZE_CRED = 8
libc = CDLL(find_library("c"))
libpam = CDLL(find_library("pam"))
calloc = libc.calloc
calloc.restype = c_void_p
calloc.argtypes = [c_size_t, c_size_t]
# bug #6 (@NIPE-SYSTEMS), some libpam versions don't include this function
if hasattr(libpam, 'pam_end'):
pam_end = libpam.pam_end
pam_end.restype = c_int
pam_end.argtypes = [PamHandle, c_int]
pam_start = libpam.pam_start
pam_start.restype = c_int
pam_start.argtypes = [c_char_p, c_char_p, POINTER(PamConv), POINTER(PamHandle)]
pam_acct_mgmt = libpam.pam_acct_mgmt
pam_acct_mgmt.restype = c_int
pam_acct_mgmt.argtypes = [PamHandle, c_int]
pam_setcred = libpam.pam_setcred
pam_setcred.restype = c_int
pam_setcred.argtypes = [PamHandle, c_int]
pam_strerror = libpam.pam_strerror
pam_strerror.restype = c_char_p
pam_strerror.argtypes = [PamHandle, c_int]
pam_authenticate = libpam.pam_authenticate
pam_authenticate.restype = c_int
pam_authenticate.argtypes = [PamHandle, c_int]
class pam():
code = 0
reason = None
def __init__(self):
pass
def authenticate(self, username, password, service='login', encoding='utf-8', resetcreds=True):
"""username and password authentication for the given service.
Returns True for success, or False for failure.
self.code (integer) and self.reason (string) are always stored and may
be referenced for the reason why authentication failed. 0/'Success' will
be stored for success.
Python3 expects bytes() for ctypes inputs. This function will make
necessary conversions using the supplied encoding.
Inputs:
username: username to authenticate
password: password in plain text
service: PAM service to authenticate against, defaults to 'login'
Returns:
success: True
failure: False
"""
@conv_func
def my_conv(n_messages, messages, p_response, app_data):
"""Simple conversation function that responds to any
prompt where the echo is off with the supplied password"""
# Create an array of n_messages response objects
addr = calloc(n_messages, sizeof(PamResponse))
response = cast(addr, POINTER(PamResponse))
p_response[0] = response
for i in range(n_messages):
if messages[i].contents.msg_style == PAM_PROMPT_ECHO_OFF:
dst = calloc(len(password)+1, sizeof(c_char))
memmove(dst, cpassword, len(password))
response[i].resp = dst
response[i].resp_retcode = 0
return 0
# python3 ctypes prefers bytes
if sys.version_info >= (3,):
if isinstance(username, str): username = username.encode(encoding)
if isinstance(password, str): password = password.encode(encoding)
if isinstance(service, str): service = service.encode(encoding)
else:
if isinstance(username, unicode):
username = username.encode(encoding)
if isinstance(password, unicode):
password = password.encode(encoding)
if isinstance(service, unicode):
service = service.encode(encoding)
if b'\x00' in username or b'\x00' in password or b'\x00' in service:
self.code = 4 # PAM_SYSTEM_ERR in Linux-PAM
self.reason = 'strings may not contain NUL'
return False
# do this up front so we can safely throw an exception if there's
# anything wrong with it
cpassword = c_char_p(password)
handle = PamHandle()
conv = PamConv(my_conv, 0)
retval = pam_start(service, username, byref(conv), byref(handle))
if retval != 0:
# This is not an authentication error, something has gone wrong starting up PAM
self.code = retval
self.reason = "pam_start() failed"
return False
retval = pam_authenticate(handle, 0)
auth_success = retval == 0
if auth_success:
retval = pam_acct_mgmt(handle, 0)
auth_success = retval == 0
if auth_success and resetcreds:
retval = pam_setcred(handle, PAM_REINITIALIZE_CRED)
# store information to inform the caller why we failed
self.code = retval
self.reason = pam_strerror(handle, retval)
if sys.version_info >= (3,):
self.reason = self.reason.decode(encoding)
if hasattr(libpam, 'pam_end'):
pam_end(handle, retval)
return auth_success
def authenticate(*vargs, **dargs):
"""
Compatibility function for older versions of python-pam.
"""
return pam().authenticate(*vargs, **dargs)
if __name__ == "__main__":
import readline, getpass
def input_with_prefill(prompt, text):
def hook():
readline.insert_text(text)
readline.redisplay()
readline.set_pre_input_hook(hook)
if sys.version_info >= (3,):
result = input(prompt)
else:
result = raw_input(prompt)
readline.set_pre_input_hook()
return result
pam = pam()
username = input_with_prefill('Username: ', getpass.getuser())
# enter a valid username and an invalid/valid password, to verify both failure and success
pam.authenticate(username, getpass.getpass())
print('{} {}'.format(pam.code, pam.reason))
@@ -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):
@@ -133,7 +134,7 @@ def retrieve_nodes(nodes, element, configmanager, inputdata):
attribute, {}).get('description', ''))
elif element[-1] == 'current':
for node in util.natural_sort(list(attributes)):
for attribute in sorted(attributes[node].iterkeys()):
for attribute in sorted(attributes[node]):
currattr = attributes[node][attribute]
try:
desc = allattributes.node[attribute]['description']
@@ -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")
@@ -163,6 +164,20 @@ def update(nodes, element, configmanager, inputdata):
def update_nodegroup(group, element, configmanager, inputdata):
if element == 'check':
check = inputdata.attribs
decrypt = configmanager.decrypt
configmanager.decrypt = True
currinfo = configmanager.get_nodegroup_attributes(group, list(check))
configmanager.decrypt = decrypt
for inf in check:
checkvalue = check[inf]
if isinstance(checkvalue, dict):
checkvalue = checkvalue.get('value', None)
currvalue = currinfo.get(inf, {}).get('value')
if checkvalue == currvalue:
raise exc.InvalidArgumentException('Checked value matches existing value')
return retrieve_nodegroup(group, element, configmanager, inputdata)
if 'rename' in element:
namemap = {}
namemap[group] = inputdata.attribs['rename']
@@ -170,7 +185,7 @@ def update_nodegroup(group, element, configmanager, inputdata):
return yield_rename_resources(namemap, isnode=False)
try:
clearattribs = []
for attrib in inputdata.attribs.iterkeys():
for attrib in inputdata.attribs:
if inputdata.attribs[attrib] is None:
clearattribs.append(attrib)
for attrib in clearattribs:
@@ -220,6 +235,18 @@ def update_nodes(nodes, element, configmanager, inputdata):
raise exc.InvalidArgumentException(
'No action to take, noderange is empty (if trying to define '
'group attributes, use nodegroupattrib)')
if element[-1] == 'check':
for node in nodes:
check = inputdata.get_attributes(node, allattributes.node)
currinfo = configmanager.get_node_attributes(node, list(check), decrypt=True)
for inf in check:
checkvalue = check[inf]
if isinstance(checkvalue, dict):
checkvalue = checkvalue.get('value', None)
currvalue = currinfo.get(node, {}).get(inf, {}).get('value')
if checkvalue == currvalue:
raise exc.InvalidArgumentException('Checked value matches existing value')
return retrieve(nodes, element, configmanager, inputdata)
if 'rename' in element:
namemap = {}
for node in nodes:
@@ -231,12 +258,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:
@@ -383,7 +383,7 @@ def perform_requests(operator, nodes, element, cfg, inputdata, realop):
raise datum
if (hasattr(datum, 'kvpairs') and datum.kvpairs and
len(datum.kvpairs) == 1):
bundle.append((datum.kvpairs.keys()[0], datum))
bundle.append((list(datum.kvpairs)[0], datum))
numnodes -= 1
else:
yield datum
@@ -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')
@@ -491,8 +491,8 @@ class IpmiHandler(object):
# raise exc.TargetEndpointUnreachable(
# "Login process to " + connparams['bmc'] + " died")
except socket.gaierror as ge:
if ge[0] == -2:
raise exc.TargetEndpointUnreachable(ge[1])
if ge.errno == -2:
raise exc.TargetEndpointUnreachable(ge.strerror)
raise
self.ipmicmd = persistent_ipmicmds[(node, tenant)]
@@ -623,12 +623,18 @@ class IpmiHandler(object):
return self.handle_domain_name()
elif self.element[1:3] == ['management_controller', 'ntp']:
return self.handle_ntp()
elif self.element[1:4] == ['management_controller', 'extended', 'all']:
return self.handle_bmcconfig()
elif self.element[1:4] == ['management_controller', 'extended', 'all']:
return self.handle_bmcconfig(True)
elif self.element[1:3] == ['system', 'all']:
return self.handle_sysconfig()
elif self.element[1:3] == ['system', 'advanced']:
return self.handle_sysconfig(True)
elif self.element[1:3] == ['system', 'clear']:
return self.handle_sysconfigclear()
elif self.element[1:3] == ['management_controller', 'clear']:
return self.handle_bmcconfigclear()
elif self.element[1:3] == ['management_controller', 'licenses']:
return self.handle_licenses()
elif self.element[1:3] == ['management_controller', 'save_licenses']:
@@ -1296,6 +1302,9 @@ class IpmiHandler(object):
def identify(self):
if 'update' == self.op:
identifystate = self.inputdata.inputbynode[self.node] == 'on'
if self.inputdata.inputbynode[self.node] == 'blink':
raise exc.InvalidArgumentException(
'"blink" is not supported with ipmi')
self.ipmicmd.set_identify(on=identifystate)
self.output.put(msg.IdentifyState(
node=self.node, state=self.inputdata.inputbynode[self.node]))
@@ -1370,12 +1379,31 @@ class IpmiHandler(object):
self.ipmicmd.set_domain_name(dn)
return
def handle_bmcconfigclear(self):
if 'read' == self.op:
raise exc.InvalidArgumentException(
'Cannot read the "clear" resource')
self.ipmicmd.clear_bmc_configuration()
def handle_sysconfigclear(self):
if 'read' == self.op:
raise exc.InvalidArgumentException(
'Cannot read the "clear" resource')
self.ipmicmd.clear_system_configuration()
def handle_bmcconfig(self, advanced=False):
if 'read' == self.op:
try:
self.output.put(msg.ConfigSet(
self.node,
self.ipmicmd.get_bmc_configuration()))
except Exception as e:
self.output.put(
msg.ConfluentNodeError(self.node, str(e)))
elif 'update' == self.op:
self.ipmicmd.set_bmc_configuration(
self.inputdata.get_attributes(self.node))
def handle_sysconfig(self, advanced=False):
if 'read' == self.op:
try:
@@ -1,5 +1,5 @@
# Copyright 2014 IBM Corporation
# Copyright 2015-2018 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.
@@ -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,13 @@ class IpmiCommandWrapper(ipmicommand.Command):
try:
super(IpmiCommandWrapper, self).__init__(**kwargs)
except socket.error as se:
if se[1] == 'EHOSTUNREACH':
if (hasattr(se, 'errno')
and se.errno in (errno.ENETUNREACH, errno.EHOSTUNREACH, errno.EADDRNOTAVAIL)):
if hasattr(se, 'strerror'):
raise exc.TargetEndpointUnreachable(se.strerror)
else:
raise exc.TargetEndpointUnreachable(str(se))
if isinstance(se, socket.timeout) or (len(se) > 1 and se[1] == 'EHOSTUNREACH'):
raise exc.TargetEndpointUnreachable('timeout')
raise
except pygexc.PyghmiException as pe:
@@ -181,10 +189,8 @@ class IpmiCommandWrapper(ipmicommand.Command):
def _attribschanged(self, nodeattribs, configmanager, **kwargs):
try:
self.ipmi_session._mark_broken()
except AttributeError:
# if ipmi_session doesn't already exist,
# then do nothing
del persistent_ipmicmds[(self.node, configmanager.tenant)]
except KeyError:
pass
def get_health(self):
@@ -268,7 +274,7 @@ def perform_requests(operator, nodes, element, cfg, inputdata, realop):
raise datum
if (hasattr(datum, 'kvpairs') and datum.kvpairs and
len(datum.kvpairs) == 1):
bundle.append((datum.kvpairs.keys()[0], datum))
bundle.append((list(datum.kvpairs)[0], datum))
numnodes -= 1
else:
yield datum
@@ -298,6 +304,11 @@ def perform_request(operator, node, element,
try:
return IpmiHandler(operator, node, element, configdata, inputdata,
cfg, results, realop).handle_request()
except socket.error as se:
if hasattr(se, 'strerror'):
results.put(msg.ConfluentTargetTimeout(node, se.strerror))
else:
results.put(msg.ConfluentTargetTimeout(node, str(se)))
except pygexc.IpmiException as ipmiexc:
excmsg = str(ipmiexc)
if excmsg in ('Session no longer connected', 'timeout'):
@@ -321,10 +332,12 @@ 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')
if (node, cfg.tenant) in persistent_ipmicmds:
del persistent_ipmicmds[(node, cfg.tenant)]
persistent_ipmicmds = {}
@@ -363,8 +376,8 @@ class IpmiHandler(object):
self.loggedin = True
self.ipmicmd = persistent_ipmicmds[(node, tenant)]
except socket.gaierror as ge:
if ge[0] == -2:
raise exc.TargetEndpointUnreachable(ge[1])
if ge.errno == -2:
raise exc.TargetEndpointUnreachable(ge.strerror)
raise
self.ipmicmd = persistent_ipmicmds[(node, tenant)]
@@ -488,6 +501,8 @@ class IpmiHandler(object):
return self.handle_domain_name()
elif self.element[1:3] == ['management_controller', 'ntp']:
return self.handle_ntp()
elif self.element[1:4] == ['management_controller', 'extended', 'all']:
return self.handle_bmcconfig()
elif self.element[1:3] == ['system', 'all']:
return self.handle_sysconfig()
elif self.element[1:3] == ['system', 'advanced']:
@@ -680,6 +695,10 @@ class IpmiHandler(object):
try:
reading = self.ipmicmd.get_sensor_reading(
sensor['name'])
if reading.unavailable:
self.output.put(msg.SensorReadings([EmptySensor(
sensor['name'])], name=self.node))
continue
except pygexc.IpmiException as ie:
if ie.ipmicode == 203:
self.output.put(msg.SensorReadings([EmptySensor(
@@ -700,6 +719,11 @@ class IpmiHandler(object):
try:
reading = self.ipmicmd.get_sensor_reading(
self.sensormap[sensorname])
if reading.unavailable:
self.output.put(msg.ConfluentResourceUnavailable(
self.node, 'Unavailable'
))
return
if hasattr(reading, 'health'):
reading.health = _str_health(reading.health)
self.output.put(
@@ -1161,13 +1185,14 @@ class IpmiHandler(object):
def identify(self):
if 'update' == self.op:
identifystate = self.inputdata.inputbynode[self.node] == 'on'
self.ipmicmd.set_identify(on=identifystate)
blinkstate = self.inputdata.inputbynode[self.node] == 'blink'
self.ipmicmd.set_identify(on=identifystate, blink=blinkstate)
self.output.put(msg.IdentifyState(
node=self.node, state=self.inputdata.inputbynode[self.node]))
return
elif 'read' == self.op:
# ipmi has identify as read-only for now
self.output.put(msg.IdentifyState(node=self.node, state=''))
identify = self.ipmicmd.get_identify().get('identifystate', '')
self.output.put(msg.IdentifyState(node=self.node, state=identify))
return
def power(self):
@@ -1235,6 +1260,19 @@ class IpmiHandler(object):
self.ipmicmd.set_domain_name(dn)
return
def handle_bmcconfig(self, advanced=False):
if 'read' == self.op:
try:
self.output.put(msg.ConfigSet(
self.node,
self.ipmicmd.get_bmc_configuration()))
except Exception as e:
self.output.put(
msg.ConfluentNodeError(self.node, str(e)))
elif 'update' == self.op:
self.ipmicmd.set_bmc_configuration(
self.inputdata.get_attributes(self.node))
def handle_sysconfigclear(self):
if 'read' == self.op:
raise exc.InvalidArgumentException(
@@ -1387,5 +1425,3 @@ def delete(nodes, element, configmanager, inputdata):
element, type='ffdc')
return perform_requests(
'delete', nodes, element, configmanager, inputdata, 'delete')
+2 -2
View File
@@ -89,7 +89,7 @@ class ExecConsole(conapi.Console):
stdin=slave, stdout=slave,
stderr=subprocess.PIPE, close_fds=True)
except OSError:
print "Unable to execute " + self.executable + " (permissions?)"
print("Unable to execute " + self.executable + " (permissions?)")
self.close()
return
os.close(slave)
@@ -104,7 +104,7 @@ class ExecConsole(conapi.Console):
try:
os.close(self._master)
except OSError:
print "Error closing master of child process, ignoring"
print("Error closing master of child process, ignoring")
if self.subproc is None or self.subproc.poll() is not None:
return
self.subproc.terminate()
+1 -1
View File
@@ -412,7 +412,7 @@ def _unixdomainhandler():
except OSError: # if file does not exist, no big deal
pass
if not os.path.isdir("/var/run/confluent"):
os.makedirs('/var/run/confluent', 0755)
os.makedirs('/var/run/confluent', 0o755)
unixsocket.bind("/var/run/confluent/api.sock")
os.chmod("/var/run/confluent/api.sock",
stat.S_IWOTH | stat.S_IROTH | stat.S_IWGRP |
+2
View File
@@ -18,6 +18,8 @@ def getgrouplist(name, gid, ng=32):
_getgrouplist.argtypes = [c_char_p, c_uint, POINTER(c_uint * ng), POINTER(c_int)]
glist = (c_uint * ng)()
nglist = c_int(ng)
if not isinstance(name, bytes):
name = name.encode('utf-8')
count = _getgrouplist(name, gid, byref(glist), byref(nglist))
if count < 0:
raise TooSmallException(nglist.value)
+8
View File
@@ -27,6 +27,14 @@ import socket
import ssl
import struct
def stringify(instr):
# Normalize unicode and bytes to 'str', correcting for
# current python version
if isinstance(instr, bytes) and not isinstance(instr, str):
return instr.decode('utf-8')
elif not isinstance(instr, bytes) and not isinstance(instr, str):
return instr.encode('utf-8')
return instr
def list_interface_indexes():
# Getting the interface indexes in a portable manner
+14 -2
View File
@@ -12,7 +12,11 @@ Group: Development/Libraries
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
Prefix: %{_prefix}
BuildArch: noarch
%if "%{dist}" == ".el8"
Requires: python3-pyghmi >= 1.0.34, python3-eventlet, python3-greenlet, python3-pycryptodomex >= 3.4.7, confluent_client, python3-pyparsing, python3-paramiko, python3-dns, python3-netifaces, python3-pyasn1 >= 0.2.3, python3-pysnmp >= 4.3.4, python3-pyte, python3-lxml, python3-eficompressor, python3-setuptools, python3-dateutil, python3-enum34, python3-asn1crypto, python3-ipaddress, python3-cffi, python3-pyOpenSSL
%else
Requires: python-pyghmi >= 1.0.34, python-eventlet, python-greenlet, python-pycryptodomex >= 3.4.7, confluent_client, python-pyparsing, python-paramiko, python-dns, python-netifaces, python2-pyasn1 >= 0.2.3, python-pysnmp >= 4.3.4, python-pyte, python-lxml, python-eficompressor, python-setuptools, python-dateutil
%endif
Vendor: Jarrod Johnson <jjohnson2@lenovo.com>
Url: http://xcat.sf.net/
@@ -23,10 +27,18 @@ Server for console management and systems management aggregation
%setup -n %{name}-%{version} -n %{name}-%{version}
%build
python setup.py build
%if "%{dist}" == ".el8"
python3 setup.py build
%else
python2 setup.py build
%endif
%install
python setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES.bare --install-purelib=/opt/confluent/lib/python --install-scripts=/opt/confluent/bin
%if "%{dist}" == ".el8"
python3 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES.bare --install-purelib=/opt/confluent/lib/python --install-scripts=/opt/confluent/bin
%else
python2 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES.bare --install-purelib=/opt/confluent/lib/python --install-scripts=/opt/confluent/bin
%endif
for file in $(grep confluent/__init__.py INSTALLED_FILES.bare); do
rm $RPM_BUILD_ROOT/$file
done
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 IBM Corporation
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
# Copyright 2017 Lenovo
#
+1 -1
View File
@@ -60,7 +60,7 @@ case $1 in
stop
;;
status)
status -p /var/run/confluent/pid $confluent
status_of_proc -p /var/run/confluent/pid $confluent
;;
esac
+85
View File
@@ -0,0 +1,85 @@
/* Copyright 2019 Lenovo */
#include <arpa/inet.h>
#include <crypt.h>
#include <net/if.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define OUI_ETHERTYPE 0x88b7
#define MAXPACKET 1024
#define CHDR "\xa4\x8c\xdb\x30\x01"
int get_interface_index(int sock, char *interface) {
struct ifreq req;
memset(&req, 0, sizeof(req));
strncpy(req.ifr_name, interface, IFNAMSIZ);
if (ioctl(sock, SIOCGIFINDEX, &req) < 0) {
return -1;
}
return req.ifr_ifindex;
}
unsigned char* genpasswd() {
unsigned char * passwd;
int urandom;
passwd = calloc(33, sizeof(char));
urandom = open("/dev/urandom", O_RDONLY);
read(urandom, passwd, 32);
close(urandom);
for (urandom = 0; urandom < 32; urandom++) {
passwd[urandom] = 0x30 + (passwd[urandom] >> 2);
}
return passwd;
}
int parse_macaddr(char* macaddr) {
unsigned char *curr;
unsigned char idx;
curr = strtok(macaddr, ":-");
idx = 0;
while (curr != NULL) {
macaddr[idx++] = strtoul(curr, NULL, 16);
curr = strtok(NULL, ":-");
}
}
int main(int argc, char* argv[]) {
int sock;
int iface;
unsigned char* passwd;
unsigned char* macaddr;
unsigned char buffer[MAXPACKET];
passwd = genpasswd();
if (argc < 3) {
fprintf(stderr, "Missing interface name and target MAC\n");
exit(1);
}
printf("%s\n", argv[2]);
parse_macaddr(argv[2]);
printf("%s\n", argv[2]);
sock = socket(AF_PACKET, SOCK_DGRAM, htons(OUI_ETHERTYPE));
if (sock < 0) {
fprintf(stderr, "Unable to open socket (run as root?)\n");
exit(1);
}
iface = get_interface_index(sock, argv[1]);
if (iface < 0) {
fprintf(stderr, "Unable to find specified interface '%s'\n", argv[1]);
exit(1);
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
import pyghmi.util.webclient as webclient
import json
import os
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
import pyghmi.util.webclient as webclient
import json
import os
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/python2
import pyghmi.util.webclient as webclient
from xml.etree.ElementTree import fromstring
import os
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python
#!/usr/bin/python2
import collections
import os
import struct