From c05cce1a4a5eb95be857b07948fda0c95cdaa106 Mon Sep 17 00:00:00 2001 From: Watson Sato Date: Tue, 8 Sep 2020 14:36:07 +0200 Subject: [PATCH 1/5] Align Bash applicability with CPE platform Wraps the remediation of rules with Packager CPE Platform with an if condition that checks for the respective platforms's package. --- ssg/build_remediations.py | 45 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/ssg/build_remediations.py b/ssg/build_remediations.py index ccbdf9fc1f..2d4a805e78 100644 --- a/ssg/build_remediations.py +++ b/ssg/build_remediations.py @@ -27,6 +27,13 @@ 'kubernetes': '.yml' } +PKG_MANAGER_TO_PACKAGE_CHECK_COMMAND = { + 'apt_get': 'dpkg-query -s {} &>/dev/null', + 'dnf': 'rpm --quiet -q {}', + 'yum': 'rpm --quiet -q {}', + 'zypper': 'rpm --quiet -q {}', +} + FILE_GENERATED_HASH_COMMENT = '# THIS FILE IS GENERATED' REMEDIATION_CONFIG_KEYS = ['complexity', 'disruption', 'platform', 'reboot', @@ -262,6 +269,44 @@ class BashRemediation(Remediation): def __init__(self, file_path): super(BashRemediation, self).__init__(file_path, "bash") + def parse_from_file_with_jinja(self, env_yaml): + self.local_env_yaml.update(env_yaml) + result = super(BashRemediation, self).parse_from_file_with_jinja(self.local_env_yaml) + + # There can be repeated inherited platforms and rule platforms + rule_platforms = set(self.associated_rule.inherited_platforms) + rule_platforms.add(self.associated_rule.platform) + + platform_conditionals = [] + for platform in rule_platforms: + if platform == "machine": + # Based on check installed_env_is_a_container + platform_conditionals.append('[ ! -f /.dockerenv -a ! -f /run/.containerenv ]') + elif platform is not None: + # Assume any other platform is a Package CPE + + # Some package names are different from the platform names + if platform in self.local_env_yaml["platform_package_overrides"]: + platform = self.local_env_yaml["platform_package_overrides"].get(platform) + + # Adjust package check command according to the pkg_manager + pkg_manager = self.local_env_yaml["pkg_manager"] + pkg_check_command = PKG_MANAGER_TO_PACKAGE_CHECK_COMMAND[pkg_manager] + platform_conditionals.append(pkg_check_command.format(platform)) + + if platform_conditionals: + platform_fix_text = "# Remediation is applicable only in certain platforms\n" + + cond = platform_conditionals.pop(0) + platform_fix_text += "if {}".format(cond) + for cond in platform_conditionals: + platform_fix_text += " && {}".format(cond) + platform_fix_text += '; then\n{}\nelse\necho "Remediation is not applicable, nothing was done"\nfi'.format(result.contents) + + remediation = namedtuple('remediation', ['contents', 'config']) + result = remediation(contents=platform_fix_text, config=result.config) + + return result class AnsibleRemediation(Remediation): def __init__(self, file_path): From 19e0c3b709e091159655d37b8ce5d693750f0a81 Mon Sep 17 00:00:00 2001 From: Watson Sato Date: Tue, 8 Sep 2020 14:41:01 +0200 Subject: [PATCH 2/5] Handle Bash platform wrapping in xccdf expansion Adjust expansion of subs and variables not to remove the whole beginning of the fix test. This was removing the package conditional wrapping. --- ssg/build_remediations.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/ssg/build_remediations.py b/ssg/build_remediations.py index 2d4a805e78..49ec557000 100644 --- a/ssg/build_remediations.py +++ b/ssg/build_remediations.py @@ -736,14 +736,16 @@ def expand_xccdf_subs(fix, remediation_type, remediation_functions): patcomp = re.compile(pattern, re.DOTALL) fixparts = re.split(patcomp, fix.text) if fixparts[0] is not None: - # Split the portion of fix.text from fix start to first call of - # remediation function, keeping only the third part: - # * tail to hold part of the fix.text after inclusion, - # but before first call of remediation function + # Split the portion of fix.text at the string remediation_functions, + # and remove preceeding comment whenever it is there. + # * head holds part of the fix.text before + # remediation_functions string + # * tail holds part of the fix.text after the + # remediation_functions string try: - rfpattern = '(.*remediation_functions)(.*)' - rfpatcomp = re.compile(rfpattern, re.DOTALL) - _, _, tail, _ = re.split(rfpatcomp, fixparts[0], maxsplit=2) + rfpattern = r'((?:# Include source function library\.\n)?.*remediation_functions)' + rfpatcomp = re.compile(rfpattern) + head, _, tail = re.split(rfpatcomp, fixparts[0], maxsplit=1) except ValueError: sys.stderr.write("Processing fix.text for: %s rule\n" % fix.get('rule')) @@ -751,9 +753,10 @@ def expand_xccdf_subs(fix, remediation_type, remediation_functions): "after inclusion of remediation functions." " Aborting..\n") sys.exit(1) - # If the 'tail' is not empty, make it new fix.text. + # If the 'head' is not empty, make it new fix.text. # Otherwise use '' - fix.text = tail if tail is not None else '' + fix.text = head if head is not None else '' + fix.text += tail if tail is not None else '' # Drop the first element of 'fixparts' since it has been processed fixparts.pop(0) # Perform sanity check on new 'fixparts' list content (to continue From 1292b93dc35a9a308464f1effb7f10f8de6db457 Mon Sep 17 00:00:00 2001 From: Watson Sato Date: Tue, 8 Sep 2020 20:56:17 +0200 Subject: [PATCH 3/5] Check if remediation has associated rule before use --- ssg/build_remediations.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ssg/build_remediations.py b/ssg/build_remediations.py index 49ec557000..85f7139d8f 100644 --- a/ssg/build_remediations.py +++ b/ssg/build_remediations.py @@ -273,9 +273,11 @@ def parse_from_file_with_jinja(self, env_yaml): self.local_env_yaml.update(env_yaml) result = super(BashRemediation, self).parse_from_file_with_jinja(self.local_env_yaml) - # There can be repeated inherited platforms and rule platforms - rule_platforms = set(self.associated_rule.inherited_platforms) - rule_platforms.add(self.associated_rule.platform) + rule_platforms = set() + if self.associated_rule: + # There can be repeated inherited platforms and rule platforms + rule_platforms.update(self.associated_rule.inherited_platforms) + rule_platforms.add(self.associated_rule.platform) platform_conditionals = [] for platform in rule_platforms: From 7953a02e61bb56b501c56f46972247751292dcbb Mon Sep 17 00:00:00 2001 From: Watson Sato Date: Thu, 10 Sep 2020 10:59:43 +0200 Subject: [PATCH 4/5] Fix python2 compat and improve code readability --- ssg/build_remediations.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/ssg/build_remediations.py b/ssg/build_remediations.py index 85f7139d8f..673d6d0cc6 100644 --- a/ssg/build_remediations.py +++ b/ssg/build_remediations.py @@ -28,10 +28,10 @@ } PKG_MANAGER_TO_PACKAGE_CHECK_COMMAND = { - 'apt_get': 'dpkg-query -s {} &>/dev/null', - 'dnf': 'rpm --quiet -q {}', - 'yum': 'rpm --quiet -q {}', - 'zypper': 'rpm --quiet -q {}', + 'apt_get': 'dpkg-query -s {0} &>/dev/null', + 'dnf': 'rpm --quiet -q {0}', + 'yum': 'rpm --quiet -q {0}', + 'zypper': 'rpm --quiet -q {0}', } FILE_GENERATED_HASH_COMMENT = '# THIS FILE IS GENERATED' @@ -297,16 +297,23 @@ def parse_from_file_with_jinja(self, env_yaml): platform_conditionals.append(pkg_check_command.format(platform)) if platform_conditionals: - platform_fix_text = "# Remediation is applicable only in certain platforms\n" + wrapped_fix_text = ["# Remediation is applicable only in certain platforms"] - cond = platform_conditionals.pop(0) - platform_fix_text += "if {}".format(cond) - for cond in platform_conditionals: - platform_fix_text += " && {}".format(cond) - platform_fix_text += '; then\n{}\nelse\necho "Remediation is not applicable, nothing was done"\nfi'.format(result.contents) + all_conditions = " && ".join(platform_conditionals) + wrapped_fix_text.append("if {0}; then".format(all_conditions)) + + # Avoid adding extra blank line + if not result.contents.startswith("\n"): + wrapped_fix_text.append("") + + wrapped_fix_text.append("{0}".format(result.contents)) + wrapped_fix_text.append("") + wrapped_fix_text.append("else") + wrapped_fix_text.append(" >&2 echo 'Remediation is not applicable, nothing was done'") + wrapped_fix_text.append("fi") remediation = namedtuple('remediation', ['contents', 'config']) - result = remediation(contents=platform_fix_text, config=result.config) + result = remediation(contents="\n".join(wrapped_fix_text), config=result.config) return result From 0bd3912651367c64789bb3d67b44c3b8848708c0 Mon Sep 17 00:00:00 2001 From: Watson Sato Date: Thu, 10 Sep 2020 17:25:27 +0200 Subject: [PATCH 5/5] Document the perils of indenting wrapped Bash fixes --- ssg/build_remediations.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ssg/build_remediations.py b/ssg/build_remediations.py index 673d6d0cc6..f269d4d2d6 100644 --- a/ssg/build_remediations.py +++ b/ssg/build_remediations.py @@ -306,6 +306,9 @@ def parse_from_file_with_jinja(self, env_yaml): if not result.contents.startswith("\n"): wrapped_fix_text.append("") + # It is possible to indent the original body of the remediation with textwrap.indent(), + # however, it is not supported by python2, and there is a risk of breaking remediations + # For example, remediations with a here-doc block could be affected. wrapped_fix_text.append("{0}".format(result.contents)) wrapped_fix_text.append("") wrapped_fix_text.append("else")