From 93efdd6ce3499c6e5ed977d44e8819d7068dfe61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C4=9Bj=20T=C3=BD=C4=8D?= Date: Wed, 27 Jul 2022 18:00:35 +0200 Subject: [PATCH 01/22] Refactor autotailor for future exension --- utils/autotailor | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/utils/autotailor b/utils/autotailor index 12f88cd22a..bffb671bcc 100755 --- a/utils/autotailor +++ b/utils/autotailor @@ -52,6 +52,11 @@ class Tailoring: self.rules_to_select = [] self.rules_to_unselect = [] + self.refine_rule = [] + self.refine_value = [] + + self.set_value = [] + def _full_id(self, string, el_type): if is_valid_xccdf_id(string): return string @@ -71,6 +76,23 @@ class Tailoring: def add_value_change(self, varname, value): self.value_changes.append((varname, value)) + def _add_rule_select_operations(self, container_element): + for rule_id in self.rules_to_select: + change = ET.SubElement(container_element, "xccdf-1.2:select") + change.set("idref", self._full_rule_id(rule_id)) + change.set("selected", "true") + + for rule_id in self.rules_to_unselect: + change = ET.SubElement(container_element, "xccdf-1.2:select") + change.set("idref", self._full_rule_id(rule_id)) + change.set("selected", "false") + + def _add_value_selections(self, container_element): + for varname, value in self.value_changes: + change = ET.SubElement(container_element, "xccdf-1.2:set-value") + change.set("idref", self._full_var_id(varname)) + change.text = str(value) + def to_xml(self, location=None): root = ET.Element("xccdf-1.2:Tailoring") root.set("xmlns:xccdf-1.2", ALL_NS["xccdf-1.2"]) @@ -96,20 +118,8 @@ class Tailoring: title.set("override", "false") title.text = self.profile_title - for rule_id in self.rules_to_select: - change = ET.SubElement(profile, "xccdf-1.2:select") - change.set("idref", self._full_rule_id(rule_id)) - change.set("selected", "true") - - for rule_id in self.rules_to_unselect: - change = ET.SubElement(profile, "xccdf-1.2:select") - change.set("idref", self._full_rule_id(rule_id)) - change.set("selected", "false") - - for varname, value in self.value_changes: - change = ET.SubElement(profile, "xccdf-1.2:set-value") - change.set("idref", self._full_var_id(varname)) - change.text = str(value) + self._add_rule_select_operations(profile) + self._add_value_selections(profile) root_str = ET.tostring(root) pretty_xml = xml.dom.minidom.parseString(root_str).toprettyxml() From 2aacfbde236a4e3d5d17cd6d92d4842b42c6fefd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C4=9Bj=20T=C3=BD=C4=8D?= Date: Sun, 4 Sep 2022 12:55:18 +0200 Subject: [PATCH 02/22] Add autotailor unit test stub. --- tests/utils/test_autotailor.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 tests/utils/test_autotailor.py diff --git a/tests/utils/test_autotailor.py b/tests/utils/test_autotailor.py new file mode 100644 index 0000000000..5cf31b542f --- /dev/null +++ b/tests/utils/test_autotailor.py @@ -0,0 +1,12 @@ +import importlib + + +def import_arbitrary_file_as_module(path, module_name): + spec = importlib.util.spec_from_loader( + module_name, importlib.machinery.SourceFileLoader(module_name, path)) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +autotailor = import_arbitrary_file_as_module("utils/autotailor", "autotailor") From 0f4c498c666e98db962afd9e5da9db2863255ab4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Mon, 2 Oct 2023 11:50:29 +0200 Subject: [PATCH 03/22] Use a single constant definition Instead of repeating the smae string all over the place, let's have a single constant defined and then used. --- utils/autotailor | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/utils/autotailor b/utils/autotailor index bffb671bcc..6d12f5473a 100755 --- a/utils/autotailor +++ b/utils/autotailor @@ -143,8 +143,10 @@ def parse_args(): "--title", default="", help="Title of the new profile.") parser.add_argument( - "--id-namespace", default="org.ssgproject.content", - help="The reverse-DNS style string that is part of entities IDs in the corresponding data stream. If left out, the default value 'org.ssgproject.content' is used.") + "--id-namespace", default=Tailoring.ID_NAMESPACE, + help="The reverse-DNS style string that is part of entities IDs in " + "the corresponding data stream. If left out, the default value " + "'{reverse_dns}' is used.".format(reverse_dns=Tailoring.ID_NAMESPACE)) parser.add_argument( "-v", "--var-value", metavar="VAR=VALUE", action="append", default=[], help="Specify modification of the XCCDF value in form =. " From 52b77acd0e181a3c30756f11f7b6ef878d97db25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Mon, 2 Oct 2023 14:03:47 +0200 Subject: [PATCH 04/22] Append '_customized' to the customized profile ID If the `--new-profile-id` isn't provided by the user, we will create the ID of the customized profile ID by appending the `_customized` suffix to the base profile ID. This change makes the behavior according to the help text of the `--new-profile-id` option: > If left out, the new ID will be obtained by appending '_customized' > to the tailored profile ID. --- utils/autotailor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/autotailor b/utils/autotailor index 6d12f5473a..9490547b67 100755 --- a/utils/autotailor +++ b/utils/autotailor @@ -194,7 +194,7 @@ if __name__ == "__main__": if args.new_profile_id: t.profile_name = args.new_profile_id else: - t.profile_name = args.profile + t.profile_name = args.profile + "_customized" t.original_ds_filename = args.datastream for change in args.var_value: varname, value = assignment_to_tuple(change) From 48e40697c43b9b0bca68c059b1764ba54b22a955 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Mon, 2 Oct 2023 14:39:35 +0200 Subject: [PATCH 05/22] Reconcile the ID_NAMESPACE variable --- utils/autotailor | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/utils/autotailor b/utils/autotailor index 9490547b67..63139a3ec7 100755 --- a/utils/autotailor +++ b/utils/autotailor @@ -31,6 +31,7 @@ ALL_NS = { "xccdf-1.2": "http://checklists.nist.gov/xccdf/1.2", } DEFAULT_PROFILE_SUFFIX = "_customized" +DEFAULT_REVERSE_DNS = "org.ssgproject.content" def is_valid_xccdf_id(string): @@ -38,10 +39,9 @@ def is_valid_xccdf_id(string): class Tailoring: - ID_NAMESPACE = "org.ssgproject.content" - def __init__(self): self.id_suffix = "auto_tailoring_default" + self.reverse_dns = DEFAULT_REVERSE_DNS self.version = 1 self.profile_name = "" self.extends = "" @@ -61,7 +61,7 @@ class Tailoring: if is_valid_xccdf_id(string): return string default_prefix = "xccdf_{namespace}_{el_type}_".format( - namespace=self.ID_NAMESPACE, el_type=el_type) + namespace=self.reverse_dns, el_type=el_type) return default_prefix + string def _full_profile_id(self, string): @@ -143,10 +143,10 @@ def parse_args(): "--title", default="", help="Title of the new profile.") parser.add_argument( - "--id-namespace", default=Tailoring.ID_NAMESPACE, + "--id-namespace", default=DEFAULT_REVERSE_DNS, help="The reverse-DNS style string that is part of entities IDs in " "the corresponding data stream. If left out, the default value " - "'{reverse_dns}' is used.".format(reverse_dns=Tailoring.ID_NAMESPACE)) + "'{reverse_dns}' is used.".format(reverse_dns=DEFAULT_REVERSE_DNS)) parser.add_argument( "-v", "--var-value", metavar="VAR=VALUE", action="append", default=[], help="Specify modification of the XCCDF value in form =. " @@ -189,7 +189,7 @@ if __name__ == "__main__": ET.register_namespace(prefix, uri) t = Tailoring() - t.ID_NAMESPACE = args.id_namespace + t.reverse_dns = args.id_namespace t.extends = args.profile if args.new_profile_id: t.profile_name = args.new_profile_id From c3e32108cf70ecd7f91c2b58a1af23ec4281b52d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Mon, 2 Oct 2023 15:13:30 +0200 Subject: [PATCH 06/22] Add unit test for `is_valid_xccdf_id` and fix the failed asserts --- tests/utils/test_autotailor.py | 13 +++++++++++++ utils/autotailor | 5 ++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/utils/test_autotailor.py b/tests/utils/test_autotailor.py index 5cf31b542f..d2bd374643 100644 --- a/tests/utils/test_autotailor.py +++ b/tests/utils/test_autotailor.py @@ -10,3 +10,16 @@ def import_arbitrary_file_as_module(path, module_name): autotailor = import_arbitrary_file_as_module("utils/autotailor", "autotailor") + + +def test_is_valid_xccdf_id(): + assert autotailor.is_valid_xccdf_id( + "xccdf_com.example.www_profile_abcd") + assert autotailor.is_valid_xccdf_id( + "xccdf_com.example.www_rule_selinux_state") + assert not autotailor.is_valid_xccdf_id("") + assert not autotailor.is_valid_xccdf_id("foo") + assert not autotailor.is_valid_xccdf_id( + "xccdf_com_example_www_rule_selinux_state") + assert not autotailor.is_valid_xccdf_id("xccdf_rule_selinux_state") + assert not autotailor.is_valid_xccdf_id("xccdf_com.example.www_rule_") diff --git a/utils/autotailor b/utils/autotailor index 63139a3ec7..e2c813af76 100755 --- a/utils/autotailor +++ b/utils/autotailor @@ -35,7 +35,10 @@ DEFAULT_REVERSE_DNS = "org.ssgproject.content" def is_valid_xccdf_id(string): - return re.match(r"^xccdf_", string) is not None + return re.match( + r"^xccdf_[a-zA-Z0-9.-]+_(benchmark|profile|rule|group|value|" + r"testresult|tailoring)_.+", + string) is not None class Tailoring: From fbc49d388bfea63508452d482a224f977ac2ce65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Mon, 2 Oct 2023 15:40:23 +0200 Subject: [PATCH 07/22] Add unit tests for _full_id_* methods --- tests/utils/test_autotailor.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/utils/test_autotailor.py b/tests/utils/test_autotailor.py index d2bd374643..065cfb8ae5 100644 --- a/tests/utils/test_autotailor.py +++ b/tests/utils/test_autotailor.py @@ -23,3 +23,22 @@ def test_is_valid_xccdf_id(): "xccdf_com_example_www_rule_selinux_state") assert not autotailor.is_valid_xccdf_id("xccdf_rule_selinux_state") assert not autotailor.is_valid_xccdf_id("xccdf_com.example.www_rule_") + + +def test_full_id(): + t = autotailor.Tailoring() + assert t._full_rule_id("accounts_tmout") == \ + "xccdf_org.ssgproject.content_rule_accounts_tmout" + assert t._full_rule_id( + "xccdf_org.ssgproject.content_rule_accounts_tmout") == \ + "xccdf_org.ssgproject.content_rule_accounts_tmout" + assert t._full_profile_id("stig") == \ + "xccdf_org.ssgproject.content_profile_stig" + assert t._full_profile_id( + "xccdf_org.ssgproject.content_profile_stig") == \ + "xccdf_org.ssgproject.content_profile_stig" + assert t._full_var_id("var_crypto_policy") == \ + "xccdf_org.ssgproject.content_value_var_crypto_policy" + assert t._full_var_id( + "xccdf_org.ssgproject.content_value_var_crypto_policy") == \ + "xccdf_org.ssgproject.content_value_var_crypto_policy" From b22b3402ad3283c07aae634b7f8d0cbb1767db32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Mon, 2 Oct 2023 16:42:45 +0200 Subject: [PATCH 08/22] Move the logic to inside the Tailoring class We will move the condition that determines the ID of the customized profile into the `Tailoring` class. This move helps encapsulate code and also allows easier unit testing of this feature which we immediately use to write a unit test in this commit as well :) --- tests/utils/test_autotailor.py | 8 ++++++++ utils/autotailor | 20 ++++++++++++++------ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/tests/utils/test_autotailor.py b/tests/utils/test_autotailor.py index 065cfb8ae5..b6cd608803 100644 --- a/tests/utils/test_autotailor.py +++ b/tests/utils/test_autotailor.py @@ -42,3 +42,11 @@ def test_full_id(): assert t._full_var_id( "xccdf_org.ssgproject.content_value_var_crypto_policy") == \ "xccdf_org.ssgproject.content_value_var_crypto_policy" + + +def test_customized_profile_id(): + t = autotailor.Tailoring() + t.extends = "stig" + assert t.profile_id == "stig_customized" + t.profile_id = "my_cool_profile" + assert t.profile_id == "my_cool_profile" diff --git a/utils/autotailor b/utils/autotailor index e2c813af76..fb198c7500 100755 --- a/utils/autotailor +++ b/utils/autotailor @@ -46,7 +46,7 @@ class Tailoring: self.id_suffix = "auto_tailoring_default" self.reverse_dns = DEFAULT_REVERSE_DNS self.version = 1 - self.profile_name = "" + self._profile_id = None self.extends = "" self.original_ds_filename = "" self.profile_title = "" @@ -60,6 +60,17 @@ class Tailoring: self.set_value = [] + @property + def profile_id(self): + if self._profile_id is not None: + return self._profile_id + else: + return self.extends + "_customized" + + @profile_id.setter + def profile_id(self, new_profile_id): + self._profile_id = new_profile_id + def _full_id(self, string, el_type): if is_valid_xccdf_id(string): return string @@ -110,7 +121,7 @@ class Tailoring: version.text = str(self.version) profile = ET.SubElement(root, "xccdf-1.2:Profile") - profile.set("id", self._full_profile_id(self.profile_name)) + profile.set("id", self._full_profile_id(self.profile_id)) profile.set("extends", self._full_profile_id(self.extends)) # Title has to be there due to the schema definition. @@ -194,10 +205,7 @@ if __name__ == "__main__": t = Tailoring() t.reverse_dns = args.id_namespace t.extends = args.profile - if args.new_profile_id: - t.profile_name = args.new_profile_id - else: - t.profile_name = args.profile + "_customized" + t.profile_id = args.new_profile_id t.original_ds_filename = args.datastream for change in args.var_value: varname, value = assignment_to_tuple(change) From 4699f910e982d2bdaaa971440b493a1828175747 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Mon, 2 Oct 2023 18:10:30 +0200 Subject: [PATCH 09/22] Add ability to refine rule This patch adds two new command line options `--rule-role` and `--rule-severity` that will allow users to refine rule role and rule seveirty in their customized profile. Using these options will generate `refine-rule` elements within the output tailoring file. Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=2058168 --- tests/utils/test_autotailor.py | 44 ++++++++++++++ utils/autotailor | 101 +++++++++++++++++++++++++++++++-- 2 files changed, 140 insertions(+), 5 deletions(-) diff --git a/tests/utils/test_autotailor.py b/tests/utils/test_autotailor.py index b6cd608803..8d71e24e12 100644 --- a/tests/utils/test_autotailor.py +++ b/tests/utils/test_autotailor.py @@ -1,5 +1,8 @@ import importlib +import pytest +import xml.etree.ElementTree as ET +NS = "http://checklists.nist.gov/xccdf/1.2" def import_arbitrary_file_as_module(path, module_name): spec = importlib.util.spec_from_loader( @@ -50,3 +53,44 @@ def test_customized_profile_id(): assert t.profile_id == "stig_customized" t.profile_id = "my_cool_profile" assert t.profile_id == "my_cool_profile" + + +def test_refine_rule(): + t = autotailor.Tailoring() + with pytest.raises(ValueError) as e: + t.refine_rule("selinux_state", "severity", "high") + assert str(e.value) == "Rule id 'selinux_state' is invalid!" + with pytest.raises(ValueError) as e: + t.refine_rule( + "xccdf_org.ssgproject.content_rule_accounts_tmout", "foo", "bar") + assert str(e.value) == "Unsupported refine-rule attribute foo" + with pytest.raises(ValueError) as e: + t.refine_rule( + "xccdf_org.ssgproject.content_rule_accounts_tmout", + "role", "mnau") + assert str(e.value) == ( + "Can't refine role of rule 'xccdf_org.ssgproject.content_rule_accounts" + "_tmout' to 'mnau'. Allowed role values are: \"full\", \"unscored\", " + "\"unchecked\".") + with pytest.raises(ValueError) as e: + t.refine_rule( + "xccdf_org.ssgproject.content_rule_accounts_tmout", + "severity", "mnau") + assert str(e.value) == ( + "Can't refine severity of rule 'xccdf_org.ssgproject.content_rule_" + "accounts_tmout' to 'mnau'. Allowed severity values are: \"unknown\", " + "\"info\", \"low\", \"medium\", \"high\".") + fav = "xccdf_org.ssgproject.content_rule_accounts_tmout" + t.refine_rule(fav, "severity", "high") + assert t.rule_refinements(fav, "severity") == "high" + t.refine_rule(fav, "role", "full") + assert t.rule_refinements(fav, "severity") == "high" + assert t.rule_refinements(fav, "role") == "full" + with pytest.raises(ValueError) as e: + t.refine_rule(fav, "severity", "low") + assert str(e.value) == ( + "Can't refine severity of rule 'xccdf_org.ssgproject.content_rule_" + "accounts_tmout' to 'low'. This rule severity is already refined to " + "'high'.") + assert t.rule_refinements(fav, "severity") == "high" + assert t.rule_refinements(fav, "role") == "full" diff --git a/utils/autotailor b/utils/autotailor index fb198c7500..0e9667a7a3 100755 --- a/utils/autotailor +++ b/utils/autotailor @@ -18,6 +18,7 @@ # Boston, MA 02110-1301 USA import argparse +import collections import datetime import re import sys @@ -32,6 +33,12 @@ ALL_NS = { } DEFAULT_PROFILE_SUFFIX = "_customized" DEFAULT_REVERSE_DNS = "org.ssgproject.content" +ROLES = ["full", "unscored", "unchecked"] +SEVERITIES = ["unknown", "info", "low", "medium", "high"] + + +def quote(string): + return "\"" + string + "\"" def is_valid_xccdf_id(string): @@ -54,11 +61,7 @@ class Tailoring: self.value_changes = [] self.rules_to_select = [] self.rules_to_unselect = [] - - self.refine_rule = [] - self.refine_value = [] - - self.set_value = [] + self._rule_refinements = collections.defaultdict(dict) @property def profile_id(self): @@ -71,6 +74,67 @@ class Tailoring: def profile_id(self, new_profile_id): self._profile_id = new_profile_id + def rule_refinements(self, rule_id, attribute): + return self._rule_refinements[rule_id][attribute] + + @staticmethod + def _find_enumeration(attribute): + if attribute == "role": + enumeration = ROLES + elif attribute == "severity": + enumeration = SEVERITIES + else: + msg = ( + "Unsupported refine-rule attribute {attribute}".format( + attribute=attribute)) + raise ValueError(msg) + return enumeration + + @staticmethod + def _validate_rule_refinement_params(rule_id, attribute, value): + if not is_valid_xccdf_id(rule_id): + msg ="Rule id '{rule_id}' is invalid!".format(rule_id=rule_id) + raise ValueError(msg) + enumeration = Tailoring._find_enumeration(attribute) + if value in enumeration: + return + allowed = ", ".join(map(quote, enumeration)) + msg = ( + "Can't refine {attribute} of rule '{rule_id}' to '{value}'. " + "Allowed {attribute} values are: {allowed}.".format( + attribute=attribute, rule_id=rule_id, value=value, + allowed=allowed)) + raise ValueError(msg) + + def _prevent_duplicate_rule_refinement(self, attribute, rule_id, value): + refinements = self._rule_refinements[rule_id] + if attribute not in refinements: + return + current = refinements[attribute] + msg = ( + "Can't refine {attribute} of rule '{rule_id}' to '{value}'. " + "This rule {attribute} is already refined to '{current}'.".format( + attribute=attribute, rule_id=rule_id, value=value, + current=current)) + raise ValueError(msg) + + def refine_rule(self, rule_id, attribute, value): + Tailoring._validate_rule_refinement_params(rule_id, attribute, value) + self._prevent_duplicate_rule_refinement(attribute, rule_id, value) + self._rule_refinements[rule_id][attribute] = value + + def change_attributes(self, assignements, attribute): + for change in assignements: + rule_id, value = assignment_to_tuple(change) + full_rule_id = self._full_rule_id(rule_id) + self.refine_rule(full_rule_id, attribute, value) + + def change_roles(self, assignements): + self.change_attributes(assignements, "role") + + def change_severities(self, assignements): + self.change_attributes(assignements, "severity") + def _full_id(self, string, el_type): if is_valid_xccdf_id(string): return string @@ -107,6 +171,13 @@ class Tailoring: change.set("idref", self._full_var_id(varname)) change.text = str(value) + def rule_refinements_to_xml(self, profile_el): + for rule_id, refinements in self._rule_refinements.items(): + ref_rule_el = ET.SubElement(profile_el, "xccdf-1.2:refine-rule") + ref_rule_el.set("idref", rule_id) + for attr, val in refinements.items(): + ref_rule_el.set(attr, val) + def to_xml(self, location=None): root = ET.Element("xccdf-1.2:Tailoring") root.set("xmlns:xccdf-1.2", ALL_NS["xccdf-1.2"]) @@ -134,6 +205,7 @@ class Tailoring: self._add_rule_select_operations(profile) self._add_value_selections(profile) + self.rule_refinements_to_xml(profile) root_str = ET.tostring(root) pretty_xml = xml.dom.minidom.parseString(root_str).toprettyxml() @@ -167,6 +239,23 @@ def parse_args(): "Name of the variable can be either its full name, or the suffix, in which case " "the 'xccdf__value' prefix will be prepended internally. " "Specify the argument multiple times if needed.") + parser.add_argument( + "-r", "--rule-role", metavar="RULE=ROLE", action="append", default=[], + help="Specify refinement of the XCCDF rule role in form " + "=. Name of the rule can be either its full name, or " + "the suffix, in which case the 'xccdf__rule_' prefix " + "will be prepended internally. The value of can be one of " + "{options}. Specify the argument multiple times if " + "needed.".format(options=", ".join(ROLES))) + parser.add_argument( + "-e", "--rule-severity", metavar="RULE=SEVERITY", action="append", + default=[], + help="Specify refinement of the XCCDF rule severity in form " + "=. Name of the rule can be either its full name, " + "or the suffix, in which case the 'xccdf__rule_' " + "prefix will be prepended internally. The value of can be " + "one of {options}. Specify the argument multiple times if " + "needed.".format(options=", ".join(SEVERITIES))) parser.add_argument( "-s", "--select", metavar="RULE_ID", action="append", default=[], help="Specify what rules to select. " @@ -210,6 +299,8 @@ if __name__ == "__main__": for change in args.var_value: varname, value = assignment_to_tuple(change) t.add_value_change(varname, value) + t.change_roles(args.rule_role) + t.change_severities(args.rule_severity) t.profile_title = args.title From 07b5ae72e14aa5cdd488fce4c449c35f013b5356 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Tue, 3 Oct 2023 10:26:25 +0200 Subject: [PATCH 10/22] Fix PEP 8 problems --- utils/autotailor | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/utils/autotailor b/utils/autotailor index 0e9667a7a3..ae4683b809 100755 --- a/utils/autotailor +++ b/utils/autotailor @@ -93,7 +93,7 @@ class Tailoring: @staticmethod def _validate_rule_refinement_params(rule_id, attribute, value): if not is_valid_xccdf_id(rule_id): - msg ="Rule id '{rule_id}' is invalid!".format(rule_id=rule_id) + msg = "Rule id '{rule_id}' is invalid!".format(rule_id=rule_id) raise ValueError(msg) enumeration = Tailoring._find_enumeration(attribute) if value in enumeration: @@ -184,7 +184,8 @@ class Tailoring: root.set("id", "xccdf_" + self.id_suffix) benchmark = ET.SubElement(root, "xccdf-1.2:benchmark") - datastream_uri = pathlib.Path(self.original_ds_filename).absolute().as_uri() + datastream_uri = pathlib.Path( + self.original_ds_filename).absolute().as_uri() benchmark.set("href", datastream_uri) version = ET.SubElement(root, "xccdf-1.2:version") @@ -222,9 +223,9 @@ def parse_args(): help="The tailored data stream filename.") parser.add_argument( "profile", metavar="BASE_PROFILE_ID", - help="Specify ID of the base profile. " - "ID of the profile can be either its full ID, or the suffix, in which case " - "the 'xccdf__profile' prefix will be prepended internally.") + help="Specify ID of the base profile. ID of the profile can be " + "either its full ID, or the suffix, in which case the " + "'xccdf__profile' prefix will be prepended internally.") parser.add_argument( "--title", default="", help="Title of the new profile.") @@ -235,10 +236,11 @@ def parse_args(): "'{reverse_dns}' is used.".format(reverse_dns=DEFAULT_REVERSE_DNS)) parser.add_argument( "-v", "--var-value", metavar="VAR=VALUE", action="append", default=[], - help="Specify modification of the XCCDF value in form =. " - "Name of the variable can be either its full name, or the suffix, in which case " - "the 'xccdf__value' prefix will be prepended internally. " - "Specify the argument multiple times if needed.") + help="Specify modification of the XCCDF value in form " + "=. Name of the variable can be either its full name, " + "or the suffix, in which case the 'xccdf__value' prefix " + "will be prepended internally. Specify the argument multiple times " + "if needed.") parser.add_argument( "-r", "--rule-role", metavar="RULE=ROLE", action="append", default=[], help="Specify refinement of the XCCDF rule role in form " @@ -269,14 +271,15 @@ def parse_args(): parser.add_argument( "-p", "--new-profile-id", help="Specify the ID of the tailored profile. " - "The ID of the new profile can be either its full ID, or the suffix, in which case " - "the 'xccdf__profile_' prefix will be prepended internally. " - "If left out, the new ID will be obtained " + "The ID of the new profile can be either its full ID, or the suffix, " + "in which case the 'xccdf__profile_' prefix will be " + "prepended internally. If left out, the new ID will be obtained " "by appending '{suffix}' to the tailored profile ID." .format(suffix=DEFAULT_PROFILE_SUFFIX)) parser.add_argument( "-o", "--output", default="-", - help="Where to save the tailoring file. If not supplied, write to standard output.") + help="Where to save the tailoring file. If not supplied, write to " + "standard output.") args = parser.parse_args() return args From a8ce89a092e5fd435ec22aa88784bc37c47b5a7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Tue, 3 Oct 2023 10:38:30 +0200 Subject: [PATCH 11/22] Update man page --- utils/autotailor.8 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/autotailor.8 b/utils/autotailor.8 index bea35debf1..98e1275847 100644 --- a/utils/autotailor.8 +++ b/utils/autotailor.8 @@ -5,7 +5,7 @@ autotailor \- CLI tool for tailoring of SCAP data streams. autotailor produces tailoring files that SCAP-compliant scanners can use to complement SCAP data streams. A tailoring file adds a new profile, which is supposed to extend a profile that is already present in the data stream. -Tailoring can add or remove rules, and it can redefine contents of XCCDF variables. +Tailoring can add or remove rules, refine rules, and it can redefine contents of XCCDF variables. The tool requires data stream location and ID of the base profile as inputs. Note however, that the referenced data stream is not opened, and the validity of tailoring is not checked against it. From 42569566d89a01a1f8813fd7affaf158eca51377 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Tue, 3 Oct 2023 11:11:47 +0200 Subject: [PATCH 12/22] Move XCCDF Value assignment to Tailoring class This patch moves the logic for assigning the XCCDF Values for outer space inside the Tailoring class by defining a method. --- utils/autotailor | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/utils/autotailor b/utils/autotailor index ae4683b809..0f91e987dd 100755 --- a/utils/autotailor +++ b/utils/autotailor @@ -135,6 +135,11 @@ class Tailoring: def change_severities(self, assignements): self.change_attributes(assignements, "severity") + def change_values(self, assignements): + for change in assignements: + varname, value = assignment_to_tuple(change) + t.add_value_change(varname, value) + def _full_id(self, string, el_type): if is_valid_xccdf_id(string): return string @@ -299,9 +304,7 @@ if __name__ == "__main__": t.extends = args.profile t.profile_id = args.new_profile_id t.original_ds_filename = args.datastream - for change in args.var_value: - varname, value = assignment_to_tuple(change) - t.add_value_change(varname, value) + t.change_values(args.var_value) t.change_roles(args.rule_role) t.change_severities(args.rule_severity) From a46b7714102b056a8a0ec4cc3099fed306228067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Tue, 3 Oct 2023 11:22:04 +0200 Subject: [PATCH 13/22] Use a standard way of handling XML namespaces Explicitly setting namespaces of elements is the recommended way of using namespaces. It helps prevent namespace errors. Also, it simplifies unit testing of code that works with XML elements. --- utils/autotailor | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/utils/autotailor b/utils/autotailor index 0f91e987dd..82885ce3d7 100755 --- a/utils/autotailor +++ b/utils/autotailor @@ -27,10 +27,7 @@ import xml.etree.ElementTree as ET import xml.dom.minidom -ALL_NS = { - "xccdf-1.1": "http://checklists.nist.gov/xccdf/1.1", - "xccdf-1.2": "http://checklists.nist.gov/xccdf/1.2", -} +NS = "http://checklists.nist.gov/xccdf/1.2" DEFAULT_PROFILE_SUFFIX = "_customized" DEFAULT_REVERSE_DNS = "org.ssgproject.content" ROLES = ["full", "unscored", "unchecked"] @@ -161,48 +158,47 @@ class Tailoring: def _add_rule_select_operations(self, container_element): for rule_id in self.rules_to_select: - change = ET.SubElement(container_element, "xccdf-1.2:select") + change = ET.SubElement(container_element, "{%s}select" % NS) change.set("idref", self._full_rule_id(rule_id)) change.set("selected", "true") for rule_id in self.rules_to_unselect: - change = ET.SubElement(container_element, "xccdf-1.2:select") + change = ET.SubElement(container_element, "{%s}select" % NS) change.set("idref", self._full_rule_id(rule_id)) change.set("selected", "false") def _add_value_selections(self, container_element): for varname, value in self.value_changes: - change = ET.SubElement(container_element, "xccdf-1.2:set-value") + change = ET.SubElement(container_element, "{%s}set-value" % NS) change.set("idref", self._full_var_id(varname)) change.text = str(value) def rule_refinements_to_xml(self, profile_el): for rule_id, refinements in self._rule_refinements.items(): - ref_rule_el = ET.SubElement(profile_el, "xccdf-1.2:refine-rule") + ref_rule_el = ET.SubElement(profile_el, "{%s}refine-rule" % NS) ref_rule_el.set("idref", rule_id) for attr, val in refinements.items(): ref_rule_el.set(attr, val) def to_xml(self, location=None): - root = ET.Element("xccdf-1.2:Tailoring") - root.set("xmlns:xccdf-1.2", ALL_NS["xccdf-1.2"]) + root = ET.Element("{%s}Tailoring" % NS) root.set("id", "xccdf_" + self.id_suffix) - benchmark = ET.SubElement(root, "xccdf-1.2:benchmark") + benchmark = ET.SubElement(root, "{%s}benchmark" % NS) datastream_uri = pathlib.Path( self.original_ds_filename).absolute().as_uri() benchmark.set("href", datastream_uri) - version = ET.SubElement(root, "xccdf-1.2:version") + version = ET.SubElement(root, "{%s}version" % NS) version.set("time", datetime.datetime.now().isoformat()) version.text = str(self.version) - profile = ET.SubElement(root, "xccdf-1.2:Profile") + profile = ET.SubElement(root, "{%s}Profile" % NS) profile.set("id", self._full_profile_id(self.profile_id)) profile.set("extends", self._full_profile_id(self.extends)) # Title has to be there due to the schema definition. - title = ET.SubElement(profile, "xccdf-1.2:title") + title = ET.SubElement(profile, "{%s}title" % NS) if self.profile_title: title.set("override", "true") else: @@ -296,8 +292,6 @@ def assignment_to_tuple(assignment): if __name__ == "__main__": args = parse_args() - for prefix, uri in ALL_NS.items(): - ET.register_namespace(prefix, uri) t = Tailoring() t.reverse_dns = args.id_namespace From ae3874e87fa2887f130415fea1186fbb53769c59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Tue, 3 Oct 2023 11:30:54 +0200 Subject: [PATCH 14/22] Rename and merge a variable The new name of the variable better describes its actual contents. --- utils/autotailor | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/autotailor b/utils/autotailor index 82885ce3d7..4ea915bdc4 100755 --- a/utils/autotailor +++ b/utils/autotailor @@ -47,7 +47,7 @@ def is_valid_xccdf_id(string): class Tailoring: def __init__(self): - self.id_suffix = "auto_tailoring_default" + self.id = "xccdf_auto_tailoring_default" self.reverse_dns = DEFAULT_REVERSE_DNS self.version = 1 self._profile_id = None @@ -182,7 +182,7 @@ class Tailoring: def to_xml(self, location=None): root = ET.Element("{%s}Tailoring" % NS) - root.set("id", "xccdf_" + self.id_suffix) + root.set("id", self.id) benchmark = ET.SubElement(root, "{%s}benchmark" % NS) datastream_uri = pathlib.Path( From ae878985908cbb0d07ed671e9f2c08968cd8ff4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Tue, 3 Oct 2023 14:25:35 +0200 Subject: [PATCH 15/22] Add an integration test for the autotailor tool This commit adds an integration test for autotailor. The goal of the test is to verify if the tailoring produced by autotailor can be loaded and consumed by oscap and if the generated tailoring leads to the intended behavior of the profile evaluation. --- tests/utils/CMakeLists.txt | 1 + tests/utils/autotailor_integration_test.sh | 103 ++++++++++++++++++++ tests/utils/data_stream.xml | 104 +++++++++++++++++++++ 3 files changed, 208 insertions(+) create mode 100755 tests/utils/autotailor_integration_test.sh create mode 100644 tests/utils/data_stream.xml diff --git a/tests/utils/CMakeLists.txt b/tests/utils/CMakeLists.txt index 0912a4a640..b90298a54e 100644 --- a/tests/utils/CMakeLists.txt +++ b/tests/utils/CMakeLists.txt @@ -1 +1,2 @@ +add_oscap_test("autotailor_integration_test.sh") add_oscap_test("test_utils_args.sh") diff --git a/tests/utils/autotailor_integration_test.sh b/tests/utils/autotailor_integration_test.sh new file mode 100755 index 0000000000..6d835c3a26 --- /dev/null +++ b/tests/utils/autotailor_integration_test.sh @@ -0,0 +1,103 @@ +#!/usr/bin/env bash + +. $builddir/tests/test_common.sh + +set -e -o pipefail + +autotailor="$top_srcdir/utils/autotailor" +tailoring="$(mktemp)" +ds="$srcdir/data_stream.xml" +stdout="$(mktemp)" +original_profile="P1" +result="$(mktemp)" + +# the original profile P1 selects rules R1 and R2 + +# select additional rule R3 +python3 $autotailor --id-namespace "com.example.www" --select R3 $ds $original_profile > $tailoring +$OSCAP xccdf eval --profile P1_customized --progress --tailoring-file $tailoring --results $result $ds +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R1"]/result[text()="pass"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R2"]/result[text()="pass"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R3"]/result[text()="pass"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R4"]/result[text()="notselected"]' + +# select additional rules R3, R4 +python3 $autotailor --id-namespace "com.example.www" --select R3 --select R4 $ds $original_profile > $tailoring +$OSCAP xccdf eval --profile P1_customized --progress --tailoring-file $tailoring --results $result $ds +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R1"]/result[text()="pass"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R2"]/result[text()="pass"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R3"]/result[text()="pass"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R4"]/result[text()="pass"]' + +# unselect rule R2 +python3 $autotailor --id-namespace "com.example.www" --unselect R2 $ds $original_profile > $tailoring +$OSCAP xccdf eval --profile P1_customized --progress --tailoring-file $tailoring --results $result $ds +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R1"]/result[text()="pass"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R2"]/result[text()="notselected"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R3"]/result[text()="notselected"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R4"]/result[text()="notselected"]' + +# unselect rule R2 and select R4 +python3 $autotailor --id-namespace "com.example.www" --unselect R2 --select R4 $ds $original_profile > $tailoring +$OSCAP xccdf eval --profile P1_customized --progress --tailoring-file $tailoring --results $result $ds +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R1"]/result[text()="pass"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R2"]/result[text()="notselected"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R3"]/result[text()="notselected"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R4"]/result[text()="pass"]' + +# select additional rule R3 and change its severity to high +python3 $autotailor --id-namespace "com.example.www" --select R3 --rule-severity R3=high $ds $original_profile > $tailoring +$OSCAP xccdf eval --profile P1_customized --progress --tailoring-file $tailoring --results $result $ds +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R1"]/result[text()="pass"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R1" and @severity="unknown"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R2"]/result[text()="pass"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R2" and @severity="unknown"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R3"]/result[text()="pass"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R3" and @severity="high"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R4"]/result[text()="notselected"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R4" and @severity="unknown"]' + +# don't select rules, don't unselect rules, but change severity of all rules to high +python3 $autotailor --id-namespace "com.example.www" --rule-severity R1=high --rule-severity R2=high --rule-severity R3=high --rule-severity R4=high $ds $original_profile > $tailoring +$OSCAP xccdf eval --profile P1_customized --progress --tailoring-file $tailoring --results $result $ds +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R1"]/result[text()="pass"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R1" and @severity="high"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R2"]/result[text()="pass"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R2" and @severity="high"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R3"]/result[text()="notselected"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R3" and @severity="high"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R4"]/result[text()="notselected"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R4" and @severity="high"]' + + +# select additional rule R4 and change its role to "unchecked" +python3 $autotailor --id-namespace "com.example.www" --select R4 --rule-role R4=unchecked $ds $original_profile > $tailoring +$OSCAP xccdf eval --profile P1_customized --progress --tailoring-file $tailoring --results $result $ds +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R1"]/result[text()="pass"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R1" and @role="full"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R2"]/result[text()="pass"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R2" and @role="full"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R3"]/result[text()="notselected"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R3" and @role="full"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R4"]/result[text()="notchecked"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R4" and @role="unchecked"]' + + +# select additional rule R3; the customized profile will have a special profile ID +customized_profile="xccdf_com.pink.elephant_profile_pineapple" +python3 $autotailor --new-profile-id $customized_profile --id-namespace "com.example.www" --select R3 $ds $original_profile > $tailoring +$OSCAP xccdf eval --profile $customized_profile --progress --tailoring-file $tailoring --results $result $ds +assert_exists 1 '/Benchmark/TestResult[@id="xccdf_org.open-scap_testresult_'$customized_profile'"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R1"]/result[text()="pass"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R2"]/result[text()="pass"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R3"]/result[text()="pass"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R4"]/result[text()="notselected"]' + +# refine value v1 to 30 +python3 $autotailor --id-namespace "com.example.www" --var-value V1=thirty $ds $original_profile > $tailoring +$OSCAP xccdf eval --profile P1_customized --progress --tailoring-file $tailoring --results $result $ds +assert_exists 1 '/Benchmark/TestResult/set-value[@idref="xccdf_com.example.www_value_V1" and text()="thirty"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R1"]/result[text()="pass"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R2"]/result[text()="pass"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R3"]/result[text()="notselected"]' +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R4"]/result[text()="notselected"]' \ No newline at end of file diff --git a/tests/utils/data_stream.xml b/tests/utils/data_stream.xml new file mode 100644 index 0000000000..7f748d839f --- /dev/null +++ b/tests/utils/data_stream.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + 5.11.2 + 2021-02-01T08:07:06+01:00 + + + + + PASS + pass + + + + + + + + + + + + + + oval:x:var:1 + + + + + 100 + + + + + + + accepted + Test Benchmark + Description + 1.0 + + OpenSCAP + OpenSCAP + OpenSCAP + http://scap.nist.gov + + + xccdf_test_profile + This profile is for testing. + + + + value + cccc + ssss + 5 + 30 + + + Rule R1 + Description + + + + + + Rule R2 + Description + + + + + + Rule R3 + Description + + + + + + Rule R4 + Description + + + + + + + From 48c013b3503bc4d8ee95740699b42ca121ec117d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Tue, 3 Oct 2023 14:43:25 +0200 Subject: [PATCH 16/22] Add autotailor unit tests to CTest The result will be that the autotailor unit tests will be executed during the CTest which is run in our CI. --- tests/utils/CMakeLists.txt | 5 +++++ tests/utils/test_autotailor.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/utils/CMakeLists.txt b/tests/utils/CMakeLists.txt index b90298a54e..b7eb6aa115 100644 --- a/tests/utils/CMakeLists.txt +++ b/tests/utils/CMakeLists.txt @@ -1,2 +1,7 @@ add_oscap_test("autotailor_integration_test.sh") add_oscap_test("test_utils_args.sh") + +add_test( + NAME "autotailor-unit-tests" + COMMAND ${PYTHON_EXECUTABLE} -m pytest -v "${CMAKE_CURRENT_SOURCE_DIR}/test_autotailor.py" +) diff --git a/tests/utils/test_autotailor.py b/tests/utils/test_autotailor.py index 8d71e24e12..f16a29a862 100644 --- a/tests/utils/test_autotailor.py +++ b/tests/utils/test_autotailor.py @@ -12,7 +12,7 @@ def import_arbitrary_file_as_module(path, module_name): return module -autotailor = import_arbitrary_file_as_module("utils/autotailor", "autotailor") +autotailor = import_arbitrary_file_as_module("../../../utils/autotailor", "autotailor") def test_is_valid_xccdf_id(): From 4afcbaeb52746ad6ae23f8a500835fa18af98c82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Tue, 3 Oct 2023 15:14:12 +0200 Subject: [PATCH 17/22] Add pytest for the CI dependencies --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4f9a9c4d9d..d4f1c5d248 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,7 +34,7 @@ jobs: - name: Install Deps run: | sudo apt-get update - sudo apt-get -y install lcov swig xsltproc rpm-common lua5.3 libpcre2-dev libyaml-dev libapt-pkg-dev libdbus-1-dev libdbus-glib-1-dev libcurl4-openssl-dev libgcrypt-dev libselinux1-dev libgconf2-dev libacl1-dev libblkid-dev libcap-dev libxml2-dev libxslt1-dev libxml-parser-perl libxml-xpath-perl libperl-dev librpm-dev librtmp-dev libxmlsec1-dev libxmlsec1-openssl python3-dbusmock + sudo apt-get -y install lcov swig xsltproc rpm-common lua5.3 libpcre2-dev libyaml-dev libapt-pkg-dev libdbus-1-dev libdbus-glib-1-dev libcurl4-openssl-dev libgcrypt-dev libselinux1-dev libgconf2-dev libacl1-dev libblkid-dev libcap-dev libxml2-dev libxslt1-dev libxml-parser-perl libxml-xpath-perl libperl-dev librpm-dev librtmp-dev libxmlsec1-dev libxmlsec1-openssl python3-dbusmock python3-pytest sudo apt-get -y remove rpm # Runs a set of commands using the runners shell @@ -57,7 +57,7 @@ jobs: image: fedora:latest steps: - name: Install Deps - run: dnf install -y cmake git dbus-devel GConf2-devel libacl-devel libblkid-devel libcap-devel libcurl-devel libgcrypt-devel libselinux-devel libxml2-devel libxslt-devel libattr-devel make openldap-devel pcre2-devel perl-XML-Parser perl-XML-XPath perl-devel python3-devel python3-dbusmock rpm-devel swig bzip2-devel gcc-c++ libyaml-devel xmlsec1-devel xmlsec1-openssl-devel hostname bzip2 lua rpm-build which strace apt-devel + run: dnf install -y cmake git dbus-devel GConf2-devel libacl-devel libblkid-devel libcap-devel libcurl-devel libgcrypt-devel libselinux-devel libxml2-devel libxslt-devel libattr-devel make openldap-devel pcre2-devel perl-XML-Parser perl-XML-XPath perl-devel python3-devel python3-dbusmock rpm-devel swig bzip2-devel gcc-c++ libyaml-devel xmlsec1-devel xmlsec1-openssl-devel hostname bzip2 lua rpm-build which strace apt-devel python3-pytest - name: Checkout uses: actions/checkout@v3 with: From 9596edb6a7c2a0f9e7dd300cdd263d3285faff6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Tue, 3 Oct 2023 15:14:24 +0200 Subject: [PATCH 18/22] Remove unused imports --- tests/utils/test_autotailor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/utils/test_autotailor.py b/tests/utils/test_autotailor.py index f16a29a862..7a594b6c88 100644 --- a/tests/utils/test_autotailor.py +++ b/tests/utils/test_autotailor.py @@ -1,6 +1,5 @@ import importlib import pytest -import xml.etree.ElementTree as ET NS = "http://checklists.nist.gov/xccdf/1.2" From bd68511cfa2c07015985d1c6236ac1cfff573a45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Tue, 3 Oct 2023 15:20:57 +0200 Subject: [PATCH 19/22] Fix PEP 8 problems --- tests/utils/test_autotailor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/utils/test_autotailor.py b/tests/utils/test_autotailor.py index 7a594b6c88..202d5b9281 100644 --- a/tests/utils/test_autotailor.py +++ b/tests/utils/test_autotailor.py @@ -3,6 +3,7 @@ NS = "http://checklists.nist.gov/xccdf/1.2" + def import_arbitrary_file_as_module(path, module_name): spec = importlib.util.spec_from_loader( module_name, importlib.machinery.SourceFileLoader(module_name, path)) From d0c1de5129217edc90138b2bca0fe60f2cfc9165 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Thu, 5 Oct 2023 13:25:24 +0200 Subject: [PATCH 20/22] Add a missing newline --- tests/utils/autotailor_integration_test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/utils/autotailor_integration_test.sh b/tests/utils/autotailor_integration_test.sh index 6d835c3a26..92ef0a2288 100755 --- a/tests/utils/autotailor_integration_test.sh +++ b/tests/utils/autotailor_integration_test.sh @@ -100,4 +100,4 @@ assert_exists 1 '/Benchmark/TestResult/set-value[@idref="xccdf_com.example.www_v assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R1"]/result[text()="pass"]' assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R2"]/result[text()="pass"]' assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R3"]/result[text()="notselected"]' -assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R4"]/result[text()="notselected"]' \ No newline at end of file +assert_exists 1 '/Benchmark/TestResult/rule-result[@idref="xccdf_com.example.www_rule_R4"]/result[text()="notselected"]' From 015f91800cb4d990b066a379cdb8b324a674e977 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Thu, 5 Oct 2023 13:30:19 +0200 Subject: [PATCH 21/22] Improve wording of the man page --- utils/autotailor.8 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/autotailor.8 b/utils/autotailor.8 index 98e1275847..c2299f4bae 100644 --- a/utils/autotailor.8 +++ b/utils/autotailor.8 @@ -5,7 +5,7 @@ autotailor \- CLI tool for tailoring of SCAP data streams. autotailor produces tailoring files that SCAP-compliant scanners can use to complement SCAP data streams. A tailoring file adds a new profile, which is supposed to extend a profile that is already present in the data stream. -Tailoring can add or remove rules, refine rules, and it can redefine contents of XCCDF variables. +Tailoring can add, remove or refine rules, and it also can redefine contents of XCCDF variables. The tool requires data stream location and ID of the base profile as inputs. Note however, that the referenced data stream is not opened, and the validity of tailoring is not checked against it. From e1d1458df7751124645242048ebd5a6b023e925c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= Date: Thu, 5 Oct 2023 13:32:59 +0200 Subject: [PATCH 22/22] Replace format methods by F-strings --- utils/autotailor | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/utils/autotailor b/utils/autotailor index 4ea915bdc4..df91a6bf3f 100755 --- a/utils/autotailor +++ b/utils/autotailor @@ -81,26 +81,22 @@ class Tailoring: elif attribute == "severity": enumeration = SEVERITIES else: - msg = ( - "Unsupported refine-rule attribute {attribute}".format( - attribute=attribute)) + msg = f"Unsupported refine-rule attribute {attribute}" raise ValueError(msg) return enumeration @staticmethod def _validate_rule_refinement_params(rule_id, attribute, value): if not is_valid_xccdf_id(rule_id): - msg = "Rule id '{rule_id}' is invalid!".format(rule_id=rule_id) + msg = f"Rule id '{rule_id}' is invalid!" raise ValueError(msg) enumeration = Tailoring._find_enumeration(attribute) if value in enumeration: return allowed = ", ".join(map(quote, enumeration)) msg = ( - "Can't refine {attribute} of rule '{rule_id}' to '{value}'. " - "Allowed {attribute} values are: {allowed}.".format( - attribute=attribute, rule_id=rule_id, value=value, - allowed=allowed)) + f"Can't refine {attribute} of rule '{rule_id}' to '{value}'. " + f"Allowed {attribute} values are: {allowed}.") raise ValueError(msg) def _prevent_duplicate_rule_refinement(self, attribute, rule_id, value): @@ -109,10 +105,8 @@ class Tailoring: return current = refinements[attribute] msg = ( - "Can't refine {attribute} of rule '{rule_id}' to '{value}'. " - "This rule {attribute} is already refined to '{current}'.".format( - attribute=attribute, rule_id=rule_id, value=value, - current=current)) + f"Can't refine {attribute} of rule '{rule_id}' to '{value}'. " + f"This rule {attribute} is already refined to '{current}'.") raise ValueError(msg) def refine_rule(self, rule_id, attribute, value): @@ -140,9 +134,7 @@ class Tailoring: def _full_id(self, string, el_type): if is_valid_xccdf_id(string): return string - default_prefix = "xccdf_{namespace}_{el_type}_".format( - namespace=self.reverse_dns, el_type=el_type) - return default_prefix + string + return f"xccdf_{self.reverse_dns}_{el_type}_{string}" def _full_profile_id(self, string): return self._full_id(string, "profile")