From 645b9c98dc8f509839e37055050be4f39d6161d2 Mon Sep 17 00:00:00 2001 From: Jake Hunsaker Date: Thu, 14 Nov 2019 16:10:50 -0500 Subject: [Plugin] Allow selectively disabling postprocessing Adds two mechanisms by which users can choose to disable postprocessing of collected information. First, is a global method exposed via the `--no-postproc` option. Using this option will skip postprocessing for all plugins. Second, is a per-plugin option exposed via a new 'postproc' plugin option. This is set to _True_ by default (meaning yes, perform postprocessing), which users can set to False or off to disable postprocessing for that plugin only; e.g. `-k podman.postproc=off` Closes: #286 Resolves: #1862 Signed-off-by: Jake Hunsaker --- man/en/sosreport.1 | 11 +++++++++++ sos/__init__.py | 6 ++++-- sos/plugins/__init__.py | 10 +++++++--- sos/sosreport.py | 25 +++++++++++++++++++------ tests/plugin_tests.py | 22 ++++++++++++++++++++++ 5 files changed, 63 insertions(+), 11 deletions(-) diff --git a/man/en/sosreport.1 b/man/en/sosreport.1 index a885d563..9033b8fc 100644 --- a/man/en/sosreport.1 +++ b/man/en/sosreport.1 @@ -10,6 +10,7 @@ sosreport \- Collect and package diagnostic and support data [-a|--alloptions] [-v|--verbose]\fR [-k plug.opt|--plugin-option plug.opt]\fR [--no-report] [--config-file conf]\fR + [--no-postproc]\fR [--preset preset] [--add-preset add_preset]\fR [--del-preset del_preset] [--desc description]\fR [--batch] [--build] [--debug] [--dry-run]\fR @@ -82,6 +83,16 @@ Disable HTML report writing. .B \--config-file CONFIG Specify alternate configuration file. .TP +.B \-\-no-postproc +Disable postprocessing globally for all plugins. This will mean data is not +obfuscated/sanitized from the archive during collection. + +Note that this means data such as password, SSH keys, certificates, etc... +will be collected in plain text. + +To selectively disable postprocessing on a per-plugin basis, use the 'postproc' +plugin option available to all plugins, e.g. '-k podman.postproc=off'. +.TP .B \--preset PRESET Specify an existing preset to use for sos options. diff --git a/sos/__init__.py b/sos/__init__.py index 16cb35b4..5f8e407b 100644 --- a/sos/__init__.py +++ b/sos/__init__.py @@ -55,8 +55,9 @@ _arg_names = [ 'debug', 'del_preset', 'dry_run', 'enableplugins', 'encrypt_key', 'encrypt_pass', 'experimental', 'label', 'list_plugins', 'list_presets', 'list_profiles', 'log_size', 'noplugins', 'noreport', 'no_env_vars', - 'note', 'onlyplugins', 'plugin_timeout', 'plugopts', 'preset', 'profiles', - 'quiet', 'since', 'sysroot', 'threads', 'tmp_dir', 'verbosity', 'verify' + 'no_postproc', 'note', 'onlyplugins', 'plugin_timeout', 'plugopts', + 'preset', 'profiles', 'quiet', 'since', 'sysroot', 'threads', 'tmp_dir', + 'verbosity', 'verify' ] #: Arguments with non-zero default values @@ -182,6 +183,7 @@ class SoSOptions(object): self.noreport = False self.allow_system_changes = False self.no_env_vars = False + self.no_postproc = False self.note = "" self.onlyplugins = [] self.plugin_timeout = None diff --git a/sos/plugins/__init__.py b/sos/plugins/__init__.py index b7a47b6a..d4e1f3a4 100644 --- a/sos/plugins/__init__.py +++ b/sos/plugins/__init__.py @@ -329,6 +329,11 @@ class Plugin(object): # Default predicates predicate = None cmd_predicate = None + _default_plug_opts = [ + ('timeout', 'Timeout in seconds for plugin', 'fast', -1), + ('postproc', 'Enable post-processing collected plugin data', 'fast', + True) + ] def __init__(self, commons): if not getattr(self, "option_list", False): @@ -352,9 +357,8 @@ class Plugin(object): self.soslog = self.commons['soslog'] if 'soslog' in self.commons \ else logging.getLogger('sos') - # add the 'timeout' plugin option automatically - self.option_list.append(('timeout', 'timeout in seconds for plugin', - 'fast', -1)) + # add the default plugin opts + self.option_list.extend(self._default_plug_opts) # get the option list into a dictionary for opt in self.option_list: diff --git a/sos/sosreport.py b/sos/sosreport.py index 2f365fad..afacf816 100644 --- a/sos/sosreport.py +++ b/sos/sosreport.py @@ -199,6 +199,9 @@ def _get_parser(): parser.add_argument("--no-env-vars", action="store_true", default=False, dest="no_env_vars", help="Do not collect environment variables") + parser.add_argument("--no-postproc", default=False, dest="no_postproc", + action="store_true", + help="Disable all post-processing") parser.add_argument("--note", type=str, action="store", default="", help="Behaviour notes for new preset") parser.add_argument("-o", "--only-plugins", action="extend", @@ -754,12 +757,15 @@ class SoSReport(object): self.ui_log.info("") if self.all_options: + self.ui_log.info(_("The following options are available for ALL " + "plugins:")) + for opt in self.all_options[0][0]._default_plug_opts: + self.ui_log.info(" %-25s %-15s %s" % (opt[0], opt[3], opt[1])) + self.ui_log.info("") + self.ui_log.info(_("The following plugin options are available:")) - self.ui_log.info(_("\n Option 'timeout' available to all plugins -" - " time in seconds to allow plugin to run, use 0" - " for no timeout\n")) for (plug, plugname, optname, optparm) in self.all_options: - if optname == 'timeout': + if optname in ('timeout', 'postproc'): continue # format option value based on its type (int or bool) if type(optparm["enabled"]) == bool: @@ -1163,7 +1169,11 @@ class SoSReport(object): def postproc(self): for plugname, plug in self.loaded_plugins: try: - plug.postproc() + if plug.get_option('postproc'): + plug.postproc() + else: + self.soslog.info("Skipping postproc for plugin %s" + % plugname) except (OSError, IOError) as e: if e.errno in fatal_fs_errors: self.ui_log.error("") @@ -1355,7 +1365,10 @@ class SoSReport(object): self.collect_env_vars() if not self.opts.noreport: self.generate_reports() - self.postproc() + if not self.opts.no_postproc: + self.postproc() + else: + self.ui_log.info("Skipping postprocessing of collected data") self.version() return self.final_work() diff --git a/tests/plugin_tests.py b/tests/plugin_tests.py index 6522fe14..9f03afa2 100644 --- a/tests/plugin_tests.py +++ b/tests/plugin_tests.py @@ -75,6 +75,18 @@ class NamedMockPlugin(Plugin): pass +class PostprocMockPlugin(Plugin): + + did_postproc = False + + def setup(self): + pass + + def postproc(self): + if self.get_option('postproc'): + self.did_postproc = True + + class ForbiddenMockPlugin(Plugin): """This plugin has a description.""" @@ -97,6 +109,7 @@ class MockOptions(object): since = None log_size = 25 allow_system_changes = False + no_postproc = False class PluginToolTests(unittest.TestCase): @@ -239,6 +252,15 @@ class PluginTests(unittest.TestCase): p.collect() self.assertEquals(p.archive.m, {}) + def test_postproc_default_on(self): + p = PostprocMockPlugin({ + 'cmdlineopts': MockOptions(), + 'sysroot': self.sysroot, + 'policy': LinuxPolicy() + }) + p.postproc() + self.assertTrue(p.did_postproc) + class AddCopySpecTests(unittest.TestCase): -- cgit