From 984c3044fe219e6871bd3218444d4373cfb88dd0 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Wed, 5 May 2021 09:17:42 -0400 Subject: [PATCH 01/12] Invalidate sealed token on new token If a new token grant occurs, do not retain stale token sealed, as it can be misleading. --- confluent_server/confluent/credserver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/confluent_server/confluent/credserver.py b/confluent_server/confluent/credserver.py index e0894091..b10e9b1e 100644 --- a/confluent_server/confluent/credserver.py +++ b/confluent_server/confluent/credserver.py @@ -88,7 +88,7 @@ class CredServer(object): client.close() return echotoken = util.stringify(client.recv(tlv[1])) - cfgupdate = {nodename: {'crypted.selfapikey': {'hashvalue': echotoken}, 'deployment.apiarmed': ''}} + cfgupdate = {nodename: {'crypted.selfapikey': {'hashvalue': echotoken}, 'deployment.sealedapikey': '', 'deployment.apiarmed': ''}} if apiarmed == 'continuous': del cfgupdate[nodename]['deployment.apiarmed'] self.cfm.set_node_attributes(cfgupdate) From 77a043faa6e58ba862b64bef91a5be0af9abc7e3 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Wed, 5 May 2021 17:45:04 -0400 Subject: [PATCH 02/12] Change apiclient to append if outputting to existing file --- confluent_osdeploy/common/opt/confluent/bin/apiclient | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/confluent_osdeploy/common/opt/confluent/bin/apiclient b/confluent_osdeploy/common/opt/confluent/bin/apiclient index 5a526c19..5f60c7f3 100644 --- a/confluent_osdeploy/common/opt/confluent/bin/apiclient +++ b/confluent_osdeploy/common/opt/confluent/bin/apiclient @@ -143,7 +143,7 @@ if __name__ == '__main__': except ValueError: data = None if outbin: - with open(outbin, 'wb') as outf: + with open(outbin, 'ab+') as outf: reader = HTTPSClient(json=json).grab_url( sys.argv[1], data, returnrsp=True) chunk = reader.read(16384) From b74d648e7f41518e5ea1086689651e2812017876 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Wed, 12 May 2021 13:48:44 -0400 Subject: [PATCH 03/12] Fix refactored inline command run This addresses the mistake in refactoring the inline command shortcut --- confluent_client/bin/confetty | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/confluent_client/bin/confetty b/confluent_client/bin/confetty index 17d3d800..f2cd044c 100755 --- a/confluent_client/bin/confetty +++ b/confluent_client/bin/confetty @@ -765,7 +765,7 @@ def conserver_command(filehandle, localcommand): bootmode = 'uefi' bootdev = 'default' - rc = run_inline_command(consolename, '/boot/nextdevice', bootdev, '', bootmode=bootmode) + rc = run_inline_command('/boot/nextdevice', bootdev, '', bootmode=bootmode) if rc: print("Error]\r") From 5fea265dc83346dc71a91b529e52945df14dbd31 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Thu, 13 May 2021 10:38:02 -0400 Subject: [PATCH 04/12] Change CSV to have unix style line endings The excel dialog has nice minimal quoting, but DOS line endings. Unix dialog has good line endings, but excessive quoting. Create a hybrid dialect and use it for nodesensors output. --- confluent_client/bin/nodesensors | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/confluent_client/bin/nodesensors b/confluent_client/bin/nodesensors index f9c544c6..3044a930 100755 --- a/confluent_client/bin/nodesensors +++ b/confluent_client/bin/nodesensors @@ -32,6 +32,13 @@ path = os.path.realpath(os.path.join(path, '..', 'lib', 'python')) if path.startswith('/opt'): sys.path.append(path) + +class hybridcsv(csv.excel): + lineterminator = '\n' + + +csv.register_dialect('hybrid', hybridcsv) + import confluent.client as client sensorcollections = { @@ -209,7 +216,7 @@ def main(): headernames.append(headername) if options.csv: linebyline = False - csvwriter = csv.writer(sys.stdout) + csvwriter = csv.writer(sys.stdout, dialect='hybrid') if options.interval: csvwriter.writerow(['time', 'node'] + headernames) else: From 1f320af8e59a5ffe935dad6fdab7ab7036c8db8c Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Tue, 18 May 2021 09:44:02 -0400 Subject: [PATCH 05/12] Add error message for long profile names If a profile name pushes the filename field of dhcp beyond what it can support, log an event and do not offer a corrupted dhcp offer packet. --- .../confluent/discovery/protocols/pxe.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/confluent_server/confluent/discovery/protocols/pxe.py b/confluent_server/confluent/discovery/protocols/pxe.py index 625ab99b..e0d1719f 100644 --- a/confluent_server/confluent/discovery/protocols/pxe.py +++ b/confluent_server/confluent/discovery/protocols/pxe.py @@ -276,6 +276,13 @@ def proxydhcp(): bootfile = b'confluent/x86_64/ipxe.efi' elif disco['arch'] == 'bios-x86': bootfile = b'confluent/x86_64/ipxe.kkpxe' + if len(bootfile) > 127: + log.log( + {'info': 'Boot offer cannot be made to {0} as the ' + 'profile name "{1}" is {2} characters longer than is supported ' + 'for this boot method.'.format( + node, profile, len(bootfile) - 127)}) + continue rpv[:240] = rqv[:240].tobytes() rpv[0:1] = b'\x02' rpv[108:108 + len(bootfile)] = bootfile @@ -485,6 +492,13 @@ def check_reply(node, info, packet, sock, cfg, reqview): ) if not isinstance(bootfile, bytes): bootfile = bootfile.encode('utf8') + if len(bootfile) > 127: + log.log( + {'info': 'Boot offer cannot be made to {0} as the ' + 'profile name "{1}" is {2} characters longer than is supported ' + 'for this boot method.'.format( + node, profile, len(bootfile) - 127)}) + return repview[108:108 + len(bootfile)] = bootfile repview[20:24] = myipn gateway = None From b283b50cc6bdc20d64230ad2a5ea8f73d15e2f62 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Tue, 18 May 2021 10:33:27 -0400 Subject: [PATCH 06/12] Improve error on nodeconfig parse errors The current error is vague and confusing, clarify with more data showing what went wrong. --- confluent_client/bin/nodeconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/confluent_client/bin/nodeconfig b/confluent_client/bin/nodeconfig index 80732644..00764970 100755 --- a/confluent_client/bin/nodeconfig +++ b/confluent_client/bin/nodeconfig @@ -159,7 +159,7 @@ def parse_config_line(arguments, single=False): if setmode is None: setmode = True if setmode != True: - bailout('Cannot do set and query in same command') + bailout('Cannot do set and query in same command: Query detected but "{0}" appears to be set'.format(param)) if '=' in param: key, _, value = param.partition('=') _assign_value() @@ -171,7 +171,7 @@ def parse_config_line(arguments, single=False): if setmode is None: setmode = False if setmode != False: - bailout('Cannot do set and query in same command') + bailout('Cannot do set and query in same command: Set mode detected but "{0}" appears to be a query'.format(param)) if '.' not in param: if param == 'bmc': printallbmc = True From 93d4847763ef2ea732b67cace5401d8dad61b9b9 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Tue, 18 May 2021 12:28:22 -0400 Subject: [PATCH 07/12] Conditionally apply agent to sshutil Older OSes (RHEL7/SLES12) cannot do ssh-keygen with an agent. Degrade to classic confluent behavior when that happens. --- confluent_server/confluent/sshutil.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/confluent_server/confluent/sshutil.py b/confluent_server/confluent/sshutil.py index 141f2c2a..29ff4442 100644 --- a/confluent_server/confluent/sshutil.py +++ b/confluent_server/confluent/sshutil.py @@ -11,6 +11,16 @@ import tempfile agent_pid = None ready_keys = {} +_sshver = None + +def sshver(): + global _sshver + if _sshver is None: + p = subprocess.Popen(['ssh', '-V'], stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + _, output = p.communicate() + _sshver = float(output.split()[0].split(b'_')[1].split(b'p')[0]) + return _sshver def normalize_uid(): curruid = os.geteuid() @@ -23,6 +33,8 @@ def normalize_uid(): def assure_agent(): + if sshver() <= 7.6: + return global agent_pid if agent_pid is None: sai = subprocess.check_output(['ssh-agent']) @@ -41,6 +53,8 @@ def assure_agent(): os.environ[k] = v def get_passphrase(): + if sshver() <= 7.6: + return '' # convert the master key to base64 # for use in ssh passphrase context if cfm._masterkey is None: @@ -106,8 +120,9 @@ def sign_host_key(pubkey, nodename, principals=()): principals = set(principals) principals.add(nodename) principals = ','.join(sorted(principals)) + flags = '-Us' if sshver() > 7.6 else '-s' subprocess.check_call( - ['ssh-keygen', '-Us', '/etc/confluent/ssh/ca.pub', '-I', nodename, + ['ssh-keygen', flags, '/etc/confluent/ssh/ca.pub', '-I', nodename, '-n', principals, '-h', pkeyname]) certname = pkeyname.replace('.pub', '-cert.pub') with open(certname) as cert: From 49b69a2e19a5bb6e32e6dca09195c551155d0f30 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Tue, 25 May 2021 10:18:13 -0400 Subject: [PATCH 08/12] Explicitly provide content-length 0 on 204 For some vintages of eventlet+apache, this is required to avoid invalid responses from the server. --- confluent_server/confluent/selfservice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/confluent_server/confluent/selfservice.py b/confluent_server/confluent/selfservice.py index 117e5697..8a87b682 100644 --- a/confluent_server/confluent/selfservice.py +++ b/confluent_server/confluent/selfservice.py @@ -302,7 +302,7 @@ def handle_request(env, start_response): elif env['PATH_INFO'].startswith('/self/remoteconfig/status'): rst = runansible.running_status.get(nodename, None) if not rst: - start_response('204 Not Running', ()) + start_response('204 Not Running', (('Content-Length', '0'),)) yield '' return start_response('200 OK', ()) From d250a3e8508b13e43f04ad577fc27b0dbaa40f5c Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Tue, 25 May 2021 11:58:01 -0400 Subject: [PATCH 09/12] Fix typo in confetty --- confluent_client/bin/confetty | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/confluent_client/bin/confetty b/confluent_client/bin/confetty index f2cd044c..2388f2ed 100755 --- a/confluent_client/bin/confetty +++ b/confluent_client/bin/confetty @@ -722,7 +722,7 @@ def conserver_command(filehandle, localcommand): if localcommand[1] == 'o': # off sys.stdout.write("powering off...") sys.stdout.flush() - consume_termdata(session.conneection) + consume_termdata(session.connection) run_inline_command('/power/state', 'off', 'complete]') elif localcommand[1] == 's': # shutdown sys.stdout.write("shutting down...") From 66372bea0cf731d9195744a2ec1ac8876e219dc0 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Tue, 25 May 2021 14:26:49 -0400 Subject: [PATCH 10/12] Fix hang on ctrl-e,c,p,o Leave the function to consume termdata if data is available. --- confluent_client/bin/confetty | 1 - 1 file changed, 1 deletion(-) diff --git a/confluent_client/bin/confetty b/confluent_client/bin/confetty index 2388f2ed..fec114c7 100755 --- a/confluent_client/bin/confetty +++ b/confluent_client/bin/confetty @@ -722,7 +722,6 @@ def conserver_command(filehandle, localcommand): if localcommand[1] == 'o': # off sys.stdout.write("powering off...") sys.stdout.flush() - consume_termdata(session.connection) run_inline_command('/power/state', 'off', 'complete]') elif localcommand[1] == 's': # shutdown sys.stdout.write("shutting down...") From 74e011e9640aa5aad55182981bfd1e6355d42b1e Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Tue, 25 May 2021 15:46:22 -0400 Subject: [PATCH 11/12] Implement node name subtitution in nodeshell If wanting to ssh to 'altenative interfaces', provide -s to facilitate that behavior, with added flexibility compared to previous incarnations. --- confluent_client/bin/nodeshell | 20 ++++++++++++++++++-- confluent_client/doc/man/nodeshell.ronn | 9 ++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/confluent_client/bin/nodeshell b/confluent_client/bin/nodeshell index fd589e3a..ff201486 100755 --- a/confluent_client/bin/nodeshell +++ b/confluent_client/bin/nodeshell @@ -48,6 +48,8 @@ def run(): help='Do not prefix output with node names') argparser.add_option('-p', '--port', type='int', default=0, help='Specify a custom port for ssh') + argparser.add_option('-s', '--substitutename', + help='Use a different name other than the nodename for ssh') argparser.add_option('-m', '--maxnodes', type='int', help='Specify a maximum number of ' 'nodes to run remote ssh command to, ' @@ -71,6 +73,19 @@ def run(): exitcode = 0 c.stop_if_noderange_over(args[0], options.maxnodes) + nodemap = {} + if options.substitutename: + subname = options.substitutename + if '{' not in subname: + subname = '{node}' + subname + for exp in c.create('/noderange/{0}/attributes/expression'.format(args[0]), + {'expression': subname}): + if 'error' in exp: + sys.stderr.write(exp['error'] + '\n') + exitcode |= exp.get('errorcode', 1) + ex = exp.get('databynode', ()) + for node in ex: + nodemap[node] = ex[node]['value'] for exp in c.create('/noderange/{0}/attributes/expression'.format(args[0]), {'expression': cmdstr}): if 'error' in exp: @@ -79,12 +94,13 @@ def run(): ex = exp.get('databynode', ()) for node in ex: cmd = ex[node]['value'] + sshnode = nodemap.get(node, node) if not isinstance(cmd, str) and not isinstance(cmd, bytes): cmd = cmd.encode('utf-8') if options.port: - cmdv = ['ssh', '-p', '{0}'.format(options.port), node, cmd] + cmdv = ['ssh', '-p', '{0}'.format(options.port), sshnode, cmd] else: - cmdv = ['ssh', node, cmd] + cmdv = ['ssh', sshnode, cmd] if currprocs < concurrentprocs: currprocs += 1 run_cmdv(node, cmdv, all, pipedesc) diff --git a/confluent_client/doc/man/nodeshell.ronn b/confluent_client/doc/man/nodeshell.ronn index c77165df..3f9a6be1 100644 --- a/confluent_client/doc/man/nodeshell.ronn +++ b/confluent_client/doc/man/nodeshell.ronn @@ -26,7 +26,14 @@ as stderr, unlike psh which combines all stdout and stderr into stdout. * `-p PORT`, `--port=PORT` Specify a custom port for ssh - + +* `-s SUBSTITUTION`, `--substitutename=SUBSTITITUTION` + Specify a substitution name instead of the nodename. If no {} are in the substitution, + it is considered to be an append. For example, '-s -ib' would produce 'node1-ib' from 'node1'. + Full expression syntax is supported, in which case the substitution is considered to be the entire + new name. {node}-ib would be equivalent to -ib. For example, nodeshell -s {bmc} node1 + would ssh to the BMC instead of the node. + ## EXAMPLES * Running `echo hi` on for nodes: From a2edf46b8aba04bb8534ff98ccccaaa568c6757c Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Thu, 27 May 2021 16:10:06 -0400 Subject: [PATCH 12/12] Improve ctrl-c and other behaviors of osdeploy import More reliably delete an import attempt to avoid odd behaviors. --- confluent_server/bin/osdeploy | 34 ++++++++++++++------------- confluent_server/confluent/osimage.py | 1 + 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/confluent_server/bin/osdeploy b/confluent_server/bin/osdeploy index 779966ac..88697fce 100644 --- a/confluent_server/bin/osdeploy +++ b/confluent_server/bin/osdeploy @@ -361,22 +361,24 @@ def osimport(imagefile): print('Importing from {0} to {1}'.format(imagefile, rsp['target'])) else: print(repr(rsp)) - while importing: - for rsp in c.read('/deployment/importing/{0}'.format(shortname)): - if 'progress' in rsp: - sys.stdout.write('{0}: {1:.2f}% \r'.format(rsp['phase'], - rsp['progress'])) - if rsp['phase'] == 'complete': - importing = False - sys.stdout.write('\n') - for profile in rsp['profiles']: - print('Deployment profile created: {0}'.format(profile)) - sys.stdout.flush() - else: - print(repr(rsp)) - time.sleep(0.5) - if shortname: - list(c.delete('/deployment/importing/{0}'.format(shortname))) + try: + while importing: + for rsp in c.read('/deployment/importing/{0}'.format(shortname)): + if 'progress' in rsp: + sys.stdout.write('{0}: {1:.2f}% \r'.format(rsp['phase'], + rsp['progress'])) + if rsp['phase'] == 'complete': + importing = False + sys.stdout.write('\n') + for profile in rsp['profiles']: + print('Deployment profile created: {0}'.format(profile)) + sys.stdout.flush() + else: + print(repr(rsp)) + time.sleep(0.5) + finally: + if shortname: + list(c.delete('/deployment/importing/{0}'.format(shortname))) if __name__ == '__main__': main(sys.argv) diff --git a/confluent_server/confluent/osimage.py b/confluent_server/confluent/osimage.py index 52efacbb..af418e1d 100644 --- a/confluent_server/confluent/osimage.py +++ b/confluent_server/confluent/osimage.py @@ -669,6 +669,7 @@ class MediaImporter(object): targpath += '/' + identity['subname'] self.targpath = '/var/lib/confluent/distributions/' + targpath if os.path.exists(self.targpath): + del importing[importkey] raise Exception('{0} already exists'.format(self.targpath)) self.filename = os.path.abspath(media) self.medfile = medfile