From 457f1fe30b66c97e7a8c3bbaaefcba89ab2ea0c1 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Thu, 6 Oct 2016 15:51:07 -0400 Subject: [PATCH 1/3] Provide resource to allow clients to expand custom expressions Clients may now format a string as if it were to be an expression for an attribute, and have the server evaluate it using the same engine without passing through the attribute engine. This makes it easier, for example, to do nodeexec n1-n4 ipmitool -H {hardwaremanagement.manager} --- confluent_server/confluent/config/configmanager.py | 8 ++++++++ confluent_server/confluent/core.py | 1 + .../confluent/plugins/configuration/attributes.py | 14 ++++++++++++++ 3 files changed, 23 insertions(+) diff --git a/confluent_server/confluent/config/configmanager.py b/confluent_server/confluent/config/configmanager.py index c5043ab9..63d8aa4a 100644 --- a/confluent_server/confluent/config/configmanager.py +++ b/confluent_server/confluent/config/configmanager.py @@ -761,6 +761,14 @@ class ConfigManager(object): decrypt=self.decrypt) return nodeobj + def expand_attrib_expression(self, nodelist, expression): + if type(nodelist) in (unicode, str): + nodelist = (nodelist,) + for node in nodelist: + cfgobj = self._cfgstore['nodes'][node] + fmt = _ExpressionFormat(cfgobj, node) + yield (node, fmt.format(expression)) + def get_node_attributes(self, nodelist, attributes=(), decrypt=None): if decrypt is None: decrypt = self.decrypt diff --git a/confluent_server/confluent/core.py b/confluent_server/confluent/core.py index 9aa0adaf..608e8206 100644 --- a/confluent_server/confluent/core.py +++ b/confluent_server/confluent/core.py @@ -122,6 +122,7 @@ def _init_core(): 'attributes': { 'all': PluginRoute({'handler': 'attributes'}), 'current': PluginRoute({'handler': 'attributes'}), + 'expression': PluginRoute({'handler': 'attributes'}), }, 'boot': { 'nextdevice': PluginRoute({ diff --git a/confluent_server/confluent/plugins/configuration/attributes.py b/confluent_server/confluent/plugins/configuration/attributes.py index 9ee0f149..10aa3307 100644 --- a/confluent_server/confluent/plugins/configuration/attributes.py +++ b/confluent_server/confluent/plugins/configuration/attributes.py @@ -152,6 +152,20 @@ def update_nodegroup(group, element, configmanager, inputdata): return retrieve_nodegroup(group, element, configmanager, inputdata) +def _expand_expression(nodes, configmanager, inputdata): + expression = inputdata.get_attributes(list(nodes)[0]) + if type(expression) is dict: + expression = expression['expression'] + if type(expression) is dict: + expression = expression['expression'] + for expanded in configmanager.expand_attrib_expression(nodes, expression): + yield msg.KeyValueData({'value': expanded[1]}, expanded[0]) + + +def create(nodes, element, configmanager, inputdata): + if nodes is not None and element[-1] == 'expression': + return _expand_expression(nodes, configmanager, inputdata) + def update_nodes(nodes, element, configmanager, inputdata): updatedict = {} for node in nodes: From 05a66641651a437145ad326450877e82ac5d1797 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Thu, 6 Oct 2016 16:30:29 -0400 Subject: [PATCH 2/3] Add a noderun command This command enables running various commands against the nodes. While I was at it, fix permissions on some files in git. --- confluent_client/bin/nodeconsole | 0 confluent_client/bin/nodeeventlog | 0 confluent_client/bin/nodefirmware | 0 confluent_client/bin/nodeinventory | 0 confluent_client/bin/nodelist | 0 confluent_client/bin/noderun | 86 ++++++++++++++++++++++++++++++ 6 files changed, 86 insertions(+) mode change 100644 => 100755 confluent_client/bin/nodeconsole mode change 100644 => 100755 confluent_client/bin/nodeeventlog mode change 100644 => 100755 confluent_client/bin/nodefirmware mode change 100644 => 100755 confluent_client/bin/nodeinventory mode change 100644 => 100755 confluent_client/bin/nodelist create mode 100755 confluent_client/bin/noderun diff --git a/confluent_client/bin/nodeconsole b/confluent_client/bin/nodeconsole old mode 100644 new mode 100755 diff --git a/confluent_client/bin/nodeeventlog b/confluent_client/bin/nodeeventlog old mode 100644 new mode 100755 diff --git a/confluent_client/bin/nodefirmware b/confluent_client/bin/nodefirmware old mode 100644 new mode 100755 diff --git a/confluent_client/bin/nodeinventory b/confluent_client/bin/nodeinventory old mode 100644 new mode 100755 diff --git a/confluent_client/bin/nodelist b/confluent_client/bin/nodelist old mode 100644 new mode 100755 diff --git a/confluent_client/bin/noderun b/confluent_client/bin/noderun new file mode 100755 index 00000000..4a614c12 --- /dev/null +++ b/confluent_client/bin/noderun @@ -0,0 +1,86 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2016 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 optparse +import os +import select +import shlex +import subprocess +import sys + +path = os.path.dirname(os.path.realpath(__file__)) +path = os.path.realpath(os.path.join(path, '..', 'lib', 'python')) +if path.startswith('/opt'): + sys.path.append(path) + +import confluent.client as client + + +argparser = optparse.OptionParser( + usage="Usage: %prog node commandexpression", + epilog="Expressions are the same as in attributes, e.g. " + "'ipmitool -H {hardwaremanagement.manager}' will be expanded.") +argparser.disable_interspersed_args() +(options, args) = argparser.parse_args() +if len(args) < 2: + argparser.print_help() + sys.exit(1) +c = client.Command() +cmdstr = " ".join(args[1:]) + +nodeforpopen = {} +popens = [] +for exp in c.create('/noderange/{0}/attributes/expression'.format(args[0]), + {'expression': cmdstr}): + ex = exp['databynode'] + for node in ex: + cmd = ex[node]['value'].encode('utf-8') + cmdv = shlex.split(cmd) + nopen = subprocess.Popen( + cmdv, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + popens.append(nopen) + nodeforpopen[nopen] = node + +all = set([]) +pipedesc = {} +exitcode = 0 +for pop in popens: + node = nodeforpopen[pop] + pipedesc[pop.stdout] = { 'node': node, 'popen': pop, 'type': 'stdout'} + pipedesc[pop.stderr] = {'node': node, 'popen': pop, 'type': 'stderr'} + all.add(pop.stdout) + all.add(pop.stderr) +rdy, _, _ = select.select(all, [], [], 10) +while all and rdy: + for r in rdy: + data = r.readline() + desc = pipedesc[r] + if data: + node = desc['node'] + if desc['type'] == 'stdout': + sys.stdout.write('{0}: {1}'.format(node,data)) + else: + sys.stderr.write('{0}: {1}'.format(node, data)) + else: + pop = desc['popen'] + ret = pop.poll() + if ret is not None: + exitcode = exitcode | ret + all.discard(r) + if all: + rdy, _, _ = select.select(all, [], [], 10) +sys.exit(exitcode) \ No newline at end of file From 92fa2bf4d95ee524da81b974378c460d15df5231 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Thu, 13 Oct 2016 11:08:05 -0400 Subject: [PATCH 3/3] Add a number of security headers There are a number of headers security scanners expect. Explicitly declare how strict browser should be with responses. --- confluent_server/confluent/httpapi.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/confluent_server/confluent/httpapi.py b/confluent_server/confluent/httpapi.py index 323d55fd..ec921939 100644 --- a/confluent_server/confluent/httpapi.py +++ b/confluent_server/confluent/httpapi.py @@ -369,7 +369,12 @@ def resourcehandler_backend(env, start_response): """Function to handle new wsgi requests """ mimetype, extension = _pick_mimetype(env) - headers = [('Content-Type', mimetype), ('Cache-Control', 'no-cache')] + headers = [('Content-Type', mimetype), ('Cache-Control', 'no-cache'), + ('X-Content-Type-Options', 'nosniff'), + ('Content-Security-Policy', "default-src 'self'"), + ('X-XSS-Protection', '1'), ('X-Frame-Options', 'deny'), + ('Strict-Transport-Security', 'max-age=86400'), + ('X-Permitted-Cross-Domain-Policies', 'none')] reqbody = None reqtype = None if 'CONTENT_LENGTH' in env and int(env['CONTENT_LENGTH']) > 0: