From 844be904d8de624abe9bbe620d7a06417dfff842 Mon Sep 17 00:00:00 2001 From: Watson Sato Date: Thu, 27 Aug 2020 13:19:01 +0200 Subject: [PATCH 1/5] Align Ansible task applicability with CPE platform Adds a when clause to Ansible snippets of rules with Package CPE platform. If the when clause is added, a fact_packages Task needs to added as well. --- ssg/build_remediations.py | 52 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/ssg/build_remediations.py b/ssg/build_remediations.py index a9ef3014ac..597aed5889 100644 --- a/ssg/build_remediations.py +++ b/ssg/build_remediations.py @@ -6,8 +6,7 @@ import os.path import re import codecs -from collections import defaultdict, namedtuple - +from collections import defaultdict, namedtuple, OrderedDict import ssg.yaml from . import build_yaml @@ -343,11 +342,46 @@ def _get_rule_reference(self, ref_class): else: return [] + def inject_package_facts_task(self, parsed_snippet): + """ Injects a package_facts task only if + the snippet has a task with a when clause with ansible_facts.packages, + and the snippet doesn't already have an package_facts task + """ + has_package_facts_task = False + has_ansible_facts_packages_clause = False + + for p_task in parsed_snippet: + # We are only interested in the OrderedDicts, which represent Ansible tasks + if not isinstance(p_task, dict): + continue + + if "package_facts" in p_task: + has_package_facts_task = True + + if "ansible_facts.packages" in p_task.get("when", ""): + has_ansible_facts_packages_clause = True + + if has_ansible_facts_packages_clause and not has_package_facts_task: + facts_task = OrderedDict({'name': 'Gather the package facts', + 'package_facts': {'manager': 'auto'}}) + parsed_snippet.insert(0, facts_task) + def update_when_from_rule(self, to_update): additional_when = "" - if self.associated_rule.platform == "machine": - additional_when = ('ansible_virtualization_role != "guest" ' - 'or ansible_virtualization_type != "docker"') + rule_platform = self.associated_rule.platform + if rule_platform == "machine": + additional_when = 'ansible_virtualization_type not in ["docker", "lxc", "openvz"]' + elif rule_platform is not None: + # Assume any other platform is a Package CPE + + # It doesn't make sense to add a conditional on the task that + # gathers data for the conditional + if "package_facts" in to_update: + return + + additional_when = '"' + rule_platform + '" in ansible_facts.packages' + # After adding the conditional, we need to make sure package_facts are collected. + # This is done via inject_package_facts_task() to_update.setdefault("when", "") new_when = ssg.yaml.update_yaml_list_or_string(to_update["when"], additional_when) if not new_when: @@ -355,10 +390,21 @@ def update_when_from_rule(self, to_update): to_update["when"] = new_when def update(self, parsed, config): + # We split the remediation update in three steps + + # 1. Update the when clause for p in parsed: if not isinstance(p, dict): continue self.update_when_from_rule(p) + + # 2. Inject any extra task necessary + self.inject_package_facts_task(parsed) + + # 3. Add tags to all tasks, including the ones we have injected + for p in parsed: + if not isinstance(p, dict): + continue self.update_tags_from_config(p, config) self.update_tags_from_rule(p) From 60e5723e0e35ec8d79bafdd113f04691e61738e7 Mon Sep 17 00:00:00 2001 From: Watson Sato Date: Thu, 27 Aug 2020 17:09:06 +0200 Subject: [PATCH 2/5] Add inherited_platform to Rule This field is exported to the rule when it is resolved. --- ssg/build_yaml.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ssg/build_yaml.py b/ssg/build_yaml.py index 4ba114eee4..fe290ffc05 100644 --- a/ssg/build_yaml.py +++ b/ssg/build_yaml.py @@ -832,6 +832,7 @@ class Rule(object): "conflicts": lambda: list(), "requires": lambda: list(), "platform": lambda: None, + "inherited_platforms": lambda: list(), "template": lambda: None, } @@ -851,6 +852,7 @@ def __init__(self, id_): self.requires = [] self.conflicts = [] self.platform = None + self.inherited_platforms = [] # platforms inherited from the group self.template = None @classmethod @@ -1293,6 +1295,9 @@ def _process_rules(self): continue self.all_rules.add(rule) self.loaded_group.add_rule(rule) + + rule.inherited_platforms.append(self.loaded_group.platform) + if self.resolved_rules_dir: output_for_rule = os.path.join( self.resolved_rules_dir, "{id_}.yml".format(id_=rule.id_)) From 3a0bb0d2981670e90a8eaca53b28e1a6f7cc29d6 Mon Sep 17 00:00:00 2001 From: Watson Sato Date: Thu, 27 Aug 2020 17:21:35 +0200 Subject: [PATCH 3/5] Add when clauses for inherited platforms too Consider the Rule's Group platform while including 'when' clauses to Ansible snippets. Some rules have two platforms, a machine platform and a package platform. One of them is represented of the Rule, and the other is represented in the Rule's Group. The platforms are organized like this to due limiation in XCCDF, multiple platforms in a Rule are ORed, not ANDed. --- ssg/build_remediations.py | 44 ++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/ssg/build_remediations.py b/ssg/build_remediations.py index 597aed5889..a2a996d0af 100644 --- a/ssg/build_remediations.py +++ b/ssg/build_remediations.py @@ -358,8 +358,13 @@ def inject_package_facts_task(self, parsed_snippet): if "package_facts" in p_task: has_package_facts_task = True - if "ansible_facts.packages" in p_task.get("when", ""): - has_ansible_facts_packages_clause = True + # When clause of the task can be string or a list, lets normalize to list + task_when = p_task.get("when", "") + if type(task_when) is str: + task_when = [ task_when ] + for when in task_when: + if "ansible_facts.packages" in when: + has_ansible_facts_packages_clause = True if has_ansible_facts_packages_clause and not has_package_facts_task: facts_task = OrderedDict({'name': 'Gather the package facts', @@ -367,21 +372,26 @@ def inject_package_facts_task(self, parsed_snippet): parsed_snippet.insert(0, facts_task) def update_when_from_rule(self, to_update): - additional_when = "" - rule_platform = self.associated_rule.platform - if rule_platform == "machine": - additional_when = 'ansible_virtualization_type not in ["docker", "lxc", "openvz"]' - elif rule_platform is not None: - # Assume any other platform is a Package CPE - - # It doesn't make sense to add a conditional on the task that - # gathers data for the conditional - if "package_facts" in to_update: - return - - additional_when = '"' + rule_platform + '" in ansible_facts.packages' - # After adding the conditional, we need to make sure package_facts are collected. - # This is done via inject_package_facts_task() + additional_when = [] + + rule_platforms = set([self.associated_rule.platform] + + self.associated_rule.inherited_platforms) + + for platform in rule_platforms: + if platform == "machine": + additional_when.append('ansible_virtualization_type not in ["docker", "lxc", "openvz"]') + elif platform is not None: + # Assume any other platform is a Package CPE + + # It doesn't make sense to add a conditional on the task that + # gathers data for the conditional + if "package_facts" in to_update: + continue + + additional_when.append('"' + platform + '" in ansible_facts.packages') + # After adding the conditional, we need to make sure package_facts are collected. + # This is done via inject_package_facts_task() + to_update.setdefault("when", "") new_when = ssg.yaml.update_yaml_list_or_string(to_update["when"], additional_when) if not new_when: From 99c92e39bccc3fcfadca41096e66ca146137b207 Mon Sep 17 00:00:00 2001 From: Watson Sato Date: Mon, 31 Aug 2020 16:06:14 +0200 Subject: [PATCH 4/5] Improve inherihted and rule's platforms handling Add a quick comment too. --- ssg/build_remediations.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ssg/build_remediations.py b/ssg/build_remediations.py index a2a996d0af..9e622ef740 100644 --- a/ssg/build_remediations.py +++ b/ssg/build_remediations.py @@ -374,8 +374,9 @@ def inject_package_facts_task(self, parsed_snippet): def update_when_from_rule(self, to_update): additional_when = [] - rule_platforms = set([self.associated_rule.platform] + - self.associated_rule.inherited_platforms) + # There can be repeated inherited platforms and rule platforms + rule_platforms = set(self.associated_rule.inherited_platforms) + rule_platforms.add(self.associated_rule.platform) for platform in rule_platforms: if platform == "machine": From 596da9993edfbd244cbaa6d797abbd68b2e82185 Mon Sep 17 00:00:00 2001 From: Watson Sato Date: Mon, 31 Aug 2020 16:10:53 +0200 Subject: [PATCH 5/5] Code style and grammar changes --- ssg/build_remediations.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ssg/build_remediations.py b/ssg/build_remediations.py index 9e622ef740..866450dd8c 100644 --- a/ssg/build_remediations.py +++ b/ssg/build_remediations.py @@ -345,7 +345,7 @@ def _get_rule_reference(self, ref_class): def inject_package_facts_task(self, parsed_snippet): """ Injects a package_facts task only if the snippet has a task with a when clause with ansible_facts.packages, - and the snippet doesn't already have an package_facts task + and the snippet doesn't already have a package_facts task """ has_package_facts_task = False has_ansible_facts_packages_clause = False @@ -361,7 +361,7 @@ def inject_package_facts_task(self, parsed_snippet): # When clause of the task can be string or a list, lets normalize to list task_when = p_task.get("when", "") if type(task_when) is str: - task_when = [ task_when ] + task_when = [task_when] for when in task_when: if "ansible_facts.packages" in when: has_ansible_facts_packages_clause = True