diff --git a/.gitignore b/.gitignore index 0cc4c97..5d035ff 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -SOURCES/sos-collector-1.4.tar.gz +SOURCES/sos-collector-1.5.tar.gz diff --git a/.sos-collector.metadata b/.sos-collector.metadata index dfd379c..cf067d7 100644 --- a/.sos-collector.metadata +++ b/.sos-collector.metadata @@ -1 +1 @@ -714f42935060879cb23ec320cbaf68fe6375790d SOURCES/sos-collector-1.4.tar.gz +681835509211b82bb11a4547b8aa648ec1e72d13 SOURCES/sos-collector-1.5.tar.gz diff --git a/SOURCES/sos-collector-bytes-conversion.patch b/SOURCES/sos-collector-bytes-conversion.patch new file mode 100644 index 0000000..2fb2f45 --- /dev/null +++ b/SOURCES/sos-collector-bytes-conversion.patch @@ -0,0 +1,36 @@ +From 69e481e0fc455f75fcfd2dc0fb2d94af0910445f Mon Sep 17 00:00:00 2001 +From: Jake Hunsaker +Date: Mon, 15 Oct 2018 18:02:37 -0400 +Subject: [PATCH] [sosnode] Handle bytes conversion in command output + +Converts bytes to strings for formatting command output + +Signed-off-by: Jake Hunsaker +--- + soscollector/sosnode.py | 10 ++++------ + 1 file changed, 4 insertions(+), 6 deletions(-) + +diff --git a/soscollector/sosnode.py b/soscollector/sosnode.py +index c9ae2ef..dd0664e 100644 +--- a/soscollector/sosnode.py ++++ b/soscollector/sosnode.py +@@ -138,12 +138,10 @@ class SosNode(): + + def _fmt_output(self, stdout=None, stderr=None, rc=0): + '''Formats the returned output from a command into a dict''' +- c = {} +- c['status'] = rc +- if isinstance(stdout, six.string_types): +- stdout = [str(stdout)] +- if isinstance(stderr, six.string_types): +- stderr = [str(stderr)] ++ if isinstance(stdout, (six.string_types, bytes)): ++ stdout = [stdout.decode('utf-8')] ++ if isinstance(stderr, (six.string_types, bytes)): ++ stderr = [stderr.decode('utf-8')] + if stdout: + stdout = ''.join(s for s in stdout) or True + if stderr: +-- +2.14.4 + diff --git a/SOURCES/sos-collector-case-id-prompt.patch b/SOURCES/sos-collector-case-id-prompt.patch new file mode 100644 index 0000000..6d53b07 --- /dev/null +++ b/SOURCES/sos-collector-case-id-prompt.patch @@ -0,0 +1,41 @@ +From c9157c5072aeeae23e2de25080deeea959ac7dc5 Mon Sep 17 00:00:00 2001 +From: Jake Hunsaker +Date: Wed, 17 Oct 2018 11:45:48 -0400 +Subject: [PATCH] [sos_collector] Configure initial sosreport command after + case id prompt + +We were previously configuring the initial sosreport command before the +case id prompt when a user didn't specify either --case-id or --batch on +the commandline, thus only the sos-collector archive would have the case +id and not the sosreports run on the nodes. No other options were +affected by this, and case ids are now properly set on all node +sosreports. + +Signed-off-by: Jake Hunsaker +--- + soscollector/sos_collector.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/soscollector/sos_collector.py b/soscollector/sos_collector.py +index 1e66de2..c0044ec 100644 +--- a/soscollector/sos_collector.py ++++ b/soscollector/sos_collector.py +@@ -264,7 +264,6 @@ this utility or remote systems that it connects to. + self.console.info("\nsos-collector (version %s)\n" % __version__) + intro_msg = self._fmt_msg(disclaimer % self.config['tmp_dir']) + self.console.info(intro_msg) +- self.configure_sos_cmd() + prompt = "\nPress ENTER to continue, or CTRL-C to quit\n" + if not self.config['batch']: + input(prompt) +@@ -332,6 +331,7 @@ this utility or remote systems that it connects to. + self.config['cluster'].modify_sos_cmd() + self.get_nodes() + self.intro() ++ self.configure_sos_cmd() + + def intro(self): + '''Prints initial messages and collects user and case if not +-- +2.14.4 + diff --git a/SOURCES/sos-collector-deb-support.patch b/SOURCES/sos-collector-deb-support.patch new file mode 100644 index 0000000..9e23741 --- /dev/null +++ b/SOURCES/sos-collector-deb-support.patch @@ -0,0 +1,113 @@ +From 998b7596c8aa7cccbcb94ebffe9fb72a4609bce4 Mon Sep 17 00:00:00 2001 +From: Bryan Quigley +Date: Mon, 15 Oct 2018 16:52:50 -0700 +Subject: [PATCH] [Hosts] Add Debian and Ubuntu as hosts + +Currently Debian and Ubuntu share a hosts file but we can further +split them out if needed. + +Needed to define both a package name and bin path variable +so that the differences between RH and U/D could be handled. + +Signed-off-by: Bryan Quigley +--- + soscollector/hosts/__init__.py | 2 ++ + soscollector/hosts/debian.py | 36 ++++++++++++++++++++++++++++++++++++ + soscollector/hosts/redhat.py | 2 ++ + soscollector/sosnode.py | 4 ++-- + 4 files changed, 42 insertions(+), 2 deletions(-) + create mode 100644 soscollector/hosts/debian.py + +diff --git a/soscollector/hosts/__init__.py b/soscollector/hosts/__init__.py +index 02b1d71..2e93094 100644 +--- a/soscollector/hosts/__init__.py ++++ b/soscollector/hosts/__init__.py +@@ -41,6 +41,8 @@ class SosHost(): + container_runtime = None + container_image = None + sos_path_strip = None ++ sos_pkg_name = None # package name in deb/rpm/etc ++ sos_bin_path = None # path to sosreport binary + + def __init__(self, address): + self.address = address +diff --git a/soscollector/hosts/debian.py b/soscollector/hosts/debian.py +new file mode 100644 +index 0000000..7b67c83 +--- /dev/null ++++ b/soscollector/hosts/debian.py +@@ -0,0 +1,36 @@ ++# Copyright Canonical 2018, Bryan Quigley ++# This program is free software; you can redistribute it and/or modify ++# it under the terms of the GNU General Public License as published by ++# the Free Software Foundation; either version 2 of the License, or ++# (at your option) any later version. ++ ++# This program is distributed in the hope that it will be useful, ++# but WITHOUT ANY WARRANTY; without even the implied warranty of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++# GNU General Public License for more details. ++ ++# You should have received a copy of the GNU General Public License along ++# with this program; if not, write to the Free Software Foundation, Inc., ++# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ++ ++from soscollector.hosts import SosHost ++ ++ ++class DebianHost(SosHost): ++ '''Base class for defining Debian based systems''' ++ ++ distribution = 'Debian' ++ releases = ['ubuntu', 'debian'] ++ package_manager = { ++ 'name': 'dpkg', ++ 'query': "dpkg-query -W -f='${Package}-${Version}\\\n' " ++ } ++ sos_pkg_name = 'sosreport' ++ sos_bin_path = '/usr/bin/sosreport' ++ ++ def check_enabled(self, rel_string): ++ for release in self.releases: ++ if release in rel_string: ++ return True ++ return False ++# vim:ts=4 et sw=4 +diff --git a/soscollector/hosts/redhat.py b/soscollector/hosts/redhat.py +index 20db16a..f077d03 100644 +--- a/soscollector/hosts/redhat.py ++++ b/soscollector/hosts/redhat.py +@@ -26,6 +26,8 @@ class RedHatHost(SosHost): + 'name': 'rpm', + 'query': 'rpm -q' + } ++ sos_pkg_name = 'sos' ++ sos_bin_path = '/usr/sbin/sosreport' + + def check_enabled(self, rel_string): + for release in self.releases: +diff --git a/soscollector/sosnode.py b/soscollector/sosnode.py +index 98f1765..fc674b0 100644 +--- a/soscollector/sosnode.py ++++ b/soscollector/sosnode.py +@@ -154,7 +154,7 @@ class SosNode(): + def _load_sos_info(self): + '''Queries the node for information about the installed version of sos + ''' +- cmd = self.host.prefix + self.host.pkg_query('sos') ++ cmd = self.host.prefix + self.host.pkg_query(self.host.sos_pkg_name) + res = self.run_command(cmd) + if res['status'] == 0: + ver = res['stdout'].splitlines()[-1].split('-')[1] +@@ -275,7 +275,7 @@ class SosNode(): + def run_command(self, cmd, timeout=180, get_pty=False, need_root=False): + '''Runs a given cmd, either via the SSH session or locally''' + if cmd.startswith('sosreport'): +- cmd = cmd.replace('sosreport', '/usr/sbin/sosreport') ++ cmd = cmd.replace('sosreport', self.host.sos_bin_path) + need_root = True + if need_root: + get_pty = True +-- +2.14.4 + diff --git a/SOURCES/sos-collector-fix-non-root-local.patch b/SOURCES/sos-collector-fix-non-root-local.patch new file mode 100644 index 0000000..0bb540f --- /dev/null +++ b/SOURCES/sos-collector-fix-non-root-local.patch @@ -0,0 +1,74 @@ +From 8c0b8f944be6eb05ce5a23c85fe8c19abdbe0c32 Mon Sep 17 00:00:00 2001 +From: Jake Hunsaker +Date: Mon, 15 Oct 2018 18:51:05 -0400 +Subject: [PATCH] [sosnode] Fix non-root local execution and collection of + sosreport + +Fixes an issue where a local non-root run of sosreport could fail due to +incorrect process communication with the sosreport process started by +sos-collector. Additionally, corrects local copying of generated +sosreports to the sos-collector archive. + +Signed-off-by: Jake Hunsaker +--- + soscollector/sosnode.py | 23 ++++++++++++++++------- + 1 file changed, 16 insertions(+), 7 deletions(-) + +diff --git a/soscollector/sosnode.py b/soscollector/sosnode.py +index dd0664e..98f1765 100644 +--- a/soscollector/sosnode.py ++++ b/soscollector/sosnode.py +@@ -305,11 +305,16 @@ class SosNode(): + raise socket.timeout + else: + proc = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE) +- stdout, stderr = proc.communicate() +- if self.config['become_root']: +- proc.communicate(input=self.config['root_password'] + '\n') +- if self.config['need_sudo']: +- proc.communicate(input=self.config['sude_pw'] + '\n') ++ if self.config['become_root'] and need_root: ++ stdout, stderr = proc.communicate( ++ input=self.config['root_password'] + '\n' ++ ) ++ elif self.config['need_sudo'] and need_root: ++ stdout, stderr = proc.communicate( ++ input=self.config['sudo_pw'] + '\n' ++ ) ++ else: ++ stdout, stderr = proc.communicate() + rc = proc.returncode + if stdout: + sout = (stdout or True) +@@ -591,7 +596,7 @@ class SosNode(): + return False + else: + self.log_debug("Moving %s to %s" % (path, destdir)) +- shutil.move(path, dest) ++ shutil.copy(path, dest) + return True + except Exception as err: + self.log_debug("Failed to retrieve %s: %s" % (path, err)) +@@ -628,6 +633,10 @@ class SosNode(): + except Exception: + self.log_error('Failed to make archive readable') + return False ++ try: ++ self.make_archive_readable(self.sos_path + '.md5') ++ except Exception: ++ self.log_debug('Failed to make md5 readable') + self.logger.info('Retrieving sosreport from %s' % self.address) + self.log_info('Retrieving sosreport...') + ret = self.retrieve_file(self.sos_path) +@@ -698,7 +707,7 @@ class SosNode(): + + This is only used when we're not connecting as root. + ''' +- cmd = 'chmod +r %s' % filepath ++ cmd = 'chmod o+r %s' % filepath + res = self.run_command(cmd, timeout=10, need_root=True) + if res['status'] == 0: + return True +-- +2.14.4 + diff --git a/SOURCES/sos-collector-fix-options-reporting.patch b/SOURCES/sos-collector-fix-options-reporting.patch new file mode 100644 index 0000000..8466569 --- /dev/null +++ b/SOURCES/sos-collector-fix-options-reporting.patch @@ -0,0 +1,44 @@ +From a777e0713172080b4acdcd601dba4fcb3965925d Mon Sep 17 00:00:00 2001 +From: Jake Hunsaker +Date: Wed, 7 Nov 2018 15:47:46 -0500 +Subject: [PATCH] [sos_collector] Fix cluster option reporting + +No longer call a removed method that handled loading cluster profiles +before that was moved into the Configuration() class. + +Signed-off-by: Jake Hunsaker +--- + soscollector/sos_collector.py | 4 +--- + 1 file changed, 1 insertion(+), 3 deletions(-) + +diff --git a/soscollector/sos_collector.py b/soscollector/sos_collector.py +index e7f3ea7..70a3dc8 100644 +--- a/soscollector/sos_collector.py ++++ b/soscollector/sos_collector.py +@@ -48,13 +48,13 @@ class SosCollector(): + self.master = False + self.retrieved = 0 + self.need_local_sudo = False ++ self.clusters = self.config['cluster_types'] + if not self.config['list_options']: + try: + if not self.config['tmp_dir']: + self.create_tmp_dir() + self._setup_logging() + self.log_debug('Executing %s' % ' '.join(s for s in sys.argv)) +- self.clusters = self.config['cluster_types'] + self.log_debug("Found cluster profiles: %s" + % self.clusters.keys()) + self.log_debug("Found supported host types: %s" +@@ -63,8 +63,6 @@ class SosCollector(): + self.prep() + except KeyboardInterrupt: + self._exit('Exiting on user cancel', 130) +- else: +- self._load_clusters() + + def _setup_logging(self): + # behind the scenes logging +-- +2.14.4 + diff --git a/SOURCES/sos-collector-quote-all-options.patch b/SOURCES/sos-collector-quote-all-options.patch new file mode 100644 index 0000000..741ab87 --- /dev/null +++ b/SOURCES/sos-collector-quote-all-options.patch @@ -0,0 +1,247 @@ +From c09e36927f74f955a325381a7d611ea270d04b65 Mon Sep 17 00:00:00 2001 +From: Jake Hunsaker +Date: Tue, 13 Nov 2018 12:30:45 -0500 +Subject: [PATCH] [global] Quote options locally everywhere + +An earlier commit passed cluster option values through pipes.quote() in +an effort to prevent command injection on the remote nodes, however this +did not capture every avenue through which a maliciously designed option +value or sos command could take. + +Now, quote the option values at the time of use, and also quote any sos +option that isn't an on/off toggle. + +Additionally, re-work how the ovirt/rhv database query is quoted and +filter out cluster/datacenter values that are likely to contain SQL +code, since we cannot rely on the driver to do this since we don't have +an actual connection to the database. + +Original discovery of issues and patch work thanks to Riccardo Schirone. + +Signed-off-by: Jake Hunsaker +--- + soscollector/clusters/kubernetes.py | 3 ++- + soscollector/clusters/ovirt.py | 32 ++++++++++++++++++++++------- + soscollector/configuration.py | 2 +- + soscollector/sos_collector.py | 19 +++++++++-------- + soscollector/sosnode.py | 13 ++++++------ + 5 files changed, 46 insertions(+), 23 deletions(-) + +diff --git a/soscollector/clusters/kubernetes.py b/soscollector/clusters/kubernetes.py +index 6c519f3..c5c2094 100644 +--- a/soscollector/clusters/kubernetes.py ++++ b/soscollector/clusters/kubernetes.py +@@ -13,6 +13,7 @@ + # with this program; if not, write to the Free Software Foundation, Inc., + # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + ++from pipes import quote + from soscollector.clusters import Cluster + + +@@ -32,7 +33,7 @@ class kubernetes(Cluster): + def get_nodes(self): + self.cmd += ' get nodes' + if self.get_option('label'): +- self.cmd += ' -l %s ' % self.get_option('label') ++ self.cmd += ' -l %s ' % quote(self.get_option('label')) + res = self.exec_master_cmd(self.cmd) + if res['status'] == 0: + nodes = [] +diff --git a/soscollector/clusters/ovirt.py b/soscollector/clusters/ovirt.py +index 1c44c97..0a074ca 100644 +--- a/soscollector/clusters/ovirt.py ++++ b/soscollector/clusters/ovirt.py +@@ -16,6 +16,7 @@ + import os + import fnmatch + ++from pipes import quote + from soscollector.clusters import Cluster + from getpass import getpass + +@@ -31,6 +32,22 @@ class ovirt(Cluster): + ('no-hypervisors', False, 'Do not collect from hypervisors') + ] + ++ def _sql_scrub(self, val): ++ ''' ++ Manually sanitize SQL queries since we can't leave this up to the ++ driver since we do not have an actual DB connection ++ ''' ++ if not val: ++ return '%' ++ ++ invalid_chars = ['\x00', '\\', '\n', '\r', '\032', '"', '\''] ++ if any(x in invalid_chars for x in val): ++ self.log_warn("WARNING: Cluster option \'%s\' contains invalid " ++ "characters. Using '%%' instead." % val) ++ return '%' ++ ++ return val ++ + def setup(self): + self.pg_pass = False + if not self.get_option('no-database'): +@@ -38,13 +55,14 @@ class ovirt(Cluster): + self.format_db_cmd() + + def format_db_cmd(self): +- cluster = self.get_option('cluster') or '%' +- datacenter = self.get_option('datacenter') or '%' +- self.dbcmd = '/usr/share/ovirt-engine/dbscripts/engine-psql.sh -c \"' +- self.dbcmd += ("select host_name from vds_static where cluster_id in " +- "(select cluster_id from cluster where name like \'%s\'" +- " and storage_pool_id in (select id from storage_pool " +- "where name like \'%s\'))\"" % (cluster, datacenter)) ++ cluster = self._sql_scrub(self.get_option('cluster')) ++ datacenter = self._sql_scrub(self.get_option('datacenter')) ++ query = ("select host_name from vds_static where cluster_id in " ++ "(select cluster_id from cluster where name like '%s'" ++ " and storage_pool_id in (select id from storage_pool " ++ "where name like '%s'))" % (cluster, datacenter)) ++ self.dbcmd = ('/usr/share/ovirt-engine/dbscripts/engine-psql.sh ' ++ '-c {}'.format(quote(query))) + self.log_debug('Query command for ovirt DB set to: %s' % self.dbcmd) + + def get_nodes(self): +diff --git a/soscollector/configuration.py b/soscollector/configuration.py +index ea33835..7f2c3c7 100644 +--- a/soscollector/configuration.py ++++ b/soscollector/configuration.py +@@ -138,7 +138,7 @@ class Configuration(dict): + try: + # there are no instances currently where any cluster option + # should contain a legitimate space. +- value = pipes.quote(option.split('=')[1].split()[0]) ++ value = option.split('=')[1].split()[0] + except IndexError: + # conversion to boolean is handled during validation + value = 'True' +diff --git a/soscollector/sos_collector.py b/soscollector/sos_collector.py +index 70a3dc8..7331f63 100644 +--- a/soscollector/sos_collector.py ++++ b/soscollector/sos_collector.py +@@ -32,6 +32,7 @@ from concurrent.futures import ThreadPoolExecutor + from .sosnode import SosNode + from distutils.sysconfig import get_python_lib + from getpass import getpass ++from pipes import quote + from six.moves import input + from textwrap import fill + from soscollector import __version__ +@@ -360,32 +361,34 @@ this utility or remote systems that it connects to. + def configure_sos_cmd(self): + '''Configures the sosreport command that is run on the nodes''' + if self.config['sos_opt_line']: +- filt = ['&', '|', '>', '<'] ++ filt = ['&', '|', '>', '<', ';'] + if any(f in self.config['sos_opt_line'] for f in filt): + self.log_warn('Possible shell script found in provided sos ' + 'command. Ignoring --sos-cmd option entirely.') + self.config['sos_opt_line'] = None + else: + self.config['sos_cmd'] = '%s %s' % ( +- self.config['sos_cmd'], self.config['sos_opt_line']) ++ self.config['sos_cmd'], quote(self.config['sos_opt_line'])) + self.log_debug("User specified manual sosreport command. " + "Command set to %s" % self.config['sos_cmd']) + return True + if self.config['case_id']: +- self.config['sos_cmd'] += ' --case-id=%s' % self.config['case_id'] ++ self.config['sos_cmd'] += ' --case-id=%s' % ( ++ quote(self.config['case_id'])) + if self.config['alloptions']: + self.config['sos_cmd'] += ' --alloptions' + if self.config['verify']: + self.config['sos_cmd'] += ' --verify' + if self.config['log_size']: + self.config['sos_cmd'] += (' --log-size=%s' +- % self.config['log_size']) ++ % quote(self.config['log_size'])) + if self.config['sysroot']: +- self.config['sos_cmd'] += ' -s %s' % self.config['sysroot'] ++ self.config['sos_cmd'] += ' -s %s' % quote(self.config['sysroot']) + if self.config['chroot']: +- self.config['sos_cmd'] += ' -c %s' % self.config['chroot'] ++ self.config['sos_cmd'] += ' -c %s' % quote(self.config['chroot']) + if self.config['compression']: +- self.config['sos_cmd'] += ' -z %s' % self.config['compression'] ++ self.config['sos_cmd'] += ' -z %s' % ( ++ quote(self.config['compression'])) + self.log_debug('Initial sos cmd set to %s' % self.config['sos_cmd']) + + def connect_to_master(self): +@@ -408,7 +411,7 @@ this utility or remote systems that it connects to. + can still be run if the user sets a --cluster-type manually + ''' + checks = list(self.clusters.values()) +- for cluster in checks: ++ for cluster in self.clusters.values(): + checks.remove(cluster) + cluster.master = self.master + if cluster.check_enabled(): +diff --git a/soscollector/sosnode.py b/soscollector/sosnode.py +index fde1e6a..3790433 100644 +--- a/soscollector/sosnode.py ++++ b/soscollector/sosnode.py +@@ -26,6 +26,7 @@ import six + import time + + from distutils.version import LooseVersion ++from pipes import quote + from subprocess import Popen, PIPE + + +@@ -450,7 +451,7 @@ class SosNode(): + + label = self.determine_sos_label() + if label: +- self.sos_cmd = ' %s %s' % (self.sos_cmd, label) ++ self.sos_cmd = ' %s %s' % (self.sos_cmd, quote(label)) + + if self.config['sos_opt_line']: + return True +@@ -464,7 +465,7 @@ class SosNode(): + 'enabled but do not exist' % not_only) + only = self._fmt_sos_opt_list(self.config['only_plugins']) + if only: +- self.sos_cmd += ' --only-plugins=%s' % only ++ self.sos_cmd += ' --only-plugins=%s' % quote(only) + return True + + if self.config['skip_plugins']: +@@ -477,7 +478,7 @@ class SosNode(): + 'already not enabled' % not_skip) + skipln = self._fmt_sos_opt_list(skip) + if skipln: +- self.sos_cmd += ' --skip-plugins=%s' % skipln ++ self.sos_cmd += ' --skip-plugins=%s' % quote(skipln) + + if self.config['enable_plugins']: + # only run enable for plugins that are disabled +@@ -490,18 +491,18 @@ class SosNode(): + 'are already enabled or do not exist' % not_on) + enable = self._fmt_sos_opt_list(opts) + if enable: +- self.sos_cmd += ' --enable-plugins=%s' % enable ++ self.sos_cmd += ' --enable-plugins=%s' % quote(enable) + + if self.config['plugin_options']: + opts = [o for o in self.config['plugin_options'] + if self._plugin_exists(o.split('.')[0]) + and self._plugin_option_exists(o.split('=')[0])] + if opts: +- self.sos_cmd += ' -k %s' % ','.join(o for o in opts) ++ self.sos_cmd += ' -k %s' % quote(','.join(o for o in opts)) + + if self.config['preset']: + if self._preset_exists(self.config['preset']): +- self.sos_cmd += ' --preset=%s' % self.config['preset'] ++ self.sos_cmd += ' --preset=%s' % quote(self.config['preset']) + else: + self.log_debug('Requested to enable preset %s but preset does ' + 'not exist on node' % self.config['preset']) +-- +2.17.2 + diff --git a/SOURCES/sos-collector-race-condition-cluster-loading.patch b/SOURCES/sos-collector-race-condition-cluster-loading.patch new file mode 100644 index 0000000..4cf0442 --- /dev/null +++ b/SOURCES/sos-collector-race-condition-cluster-loading.patch @@ -0,0 +1,66 @@ +From 79a29ec5111e65013020bf6fd34e962e923d8698 Mon Sep 17 00:00:00 2001 +From: Jake Hunsaker +Date: Tue, 6 Nov 2018 12:17:07 -0500 +Subject: [PATCH] [soscollector] Fix race condition in loading cluster profiles + +Fixes a race condition where a layered cluster profile would not be +properly loaded because the base profile would be checked first. + +Now, once a profile is matched, we check to see if there are any layered +profiles built on top of it. If there are, those are checked and if +their enablement check returns True, the layered profile is used instead +of the base profile. If the layered profile's check returns False, or if +no layered profiles are found, the base profile is still used. + +Resolves: #12 + +Signed-off-by: Jake Hunsaker +--- + soscollector/sos_collector.py | 31 +++++++++++++++++++++++++------ + 1 file changed, 25 insertions(+), 6 deletions(-) + +diff --git a/soscollector/sos_collector.py b/soscollector/sos_collector.py +index 60daa2e..e7f3ea7 100644 +--- a/soscollector/sos_collector.py ++++ b/soscollector/sos_collector.py +@@ -409,12 +409,31 @@ this utility or remote systems that it connects to. + If a list of nodes is given, this is not run, however the cluster + can still be run if the user sets a --cluster-type manually + ''' +- +- for clus in self.clusters: +- self.clusters[clus].master = self.master +- if self.clusters[clus].check_enabled(): +- self.config['cluster'] = self.clusters[clus] +- name = str(self.clusters[clus].__class__.__name__).lower() ++ checks = list(self.clusters.values()) ++ for cluster in checks: ++ checks.remove(cluster) ++ cluster.master = self.master ++ if cluster.check_enabled(): ++ cname = cluster.__class__.__name__ ++ self.log_debug("Installation matches %s, checking for layered " ++ "profiles" % cname) ++ for remaining in checks: ++ if issubclass(remaining.__class__, cluster.__class__): ++ rname = remaining.__class__.__name__ ++ self.log_debug("Layered profile %s found. " ++ "Checking installation" ++ % rname) ++ remaining.master = self.master ++ if remaining.check_enabled(): ++ self.log_debug("Installation matches both layered " ++ "profile %s and base profile %s, " ++ "setting cluster type to layered " ++ "profile" % (rname, cname)) ++ cluster = remaining ++ break ++ ++ self.config['cluster'] = cluster ++ name = str(cluster.__class__.__name__).lower() + self.config['cluster_type'] = name + self.log_info( + 'Cluster type set to %s' % self.config['cluster_type']) +-- +2.17.2 + diff --git a/SPECS/sos-collector.spec b/SPECS/sos-collector.spec index a6f7177..f23d2b3 100644 --- a/SPECS/sos-collector.spec +++ b/SPECS/sos-collector.spec @@ -1,6 +1,6 @@ Summary: Capture sosreports from multiple nodes simultaneously Name: sos-collector -Version: 1.4 +Version: 1.5 Release: 3%{?dist} Source0: http://people.redhat.com/jhunsake/sos-collector/%{name}-%{version}.tar.gz License: GPLv2 @@ -10,6 +10,13 @@ Requires: sos >= 3.0 Obsoletes: clustersos < 1.2.2-2 Provides: clustersos = %{version}-%{release} +Patch0: sos-collector-bytes-conversion.patch +Patch1: sos-collector-fix-non-root-local.patch +Patch2: sos-collector-deb-support.patch +Patch3: sos-collector-case-id-prompt.patch +Patch4: sos-collector-fix-options-reporting.patch +Patch5: sos-collector-race-condition-cluster-loading.patch +Patch6: sos-collector-quote-all-options.patch %if 0%{?rhel} BuildRequires: python-devel @@ -33,6 +40,13 @@ is run on the nodes. %prep %setup -q +%patch0 -p1 +%patch1 -p1 +%patch2 -p1 +%patch3 -p1 +%patch4 -p1 +%patch5 -p1 +%patch6 -p1 %build %if 0%{?rhel} @@ -71,5 +85,18 @@ install -p -m644 man/en/sos-collector.1 ${RPM_BUILD_ROOT}%{_mandir}/man1/ %doc LICENSE %changelog +* Tue Nov 13 2018 Jake Hunsaker - 1.5-3 +- Resolve race condition in cluster profile loading +- Quote all options globally +- RHBZ#1633515 +- RHBZ#1647955 + +* Wed Nov 07 2018 Jake Hunsaker - 1.5-2 +- Fix cluster option reporting + +* Mon Oct 22 2018 Jake Hunsaker - 1.5-1 +- Update to version 1.5 +- Resolves CVE-2018-14650 + * Wed Aug 01 2018 Jake Hunsaker 1.4-3 - Initial RHEL 7 release