diff --git a/shared/applicability/package.yml b/shared/applicability/package.yml
new file mode 100644
index 00000000000..7201e50ef6c
--- /dev/null
+++ b/shared/applicability/package.yml
@@ -0,0 +1,9 @@
+name: "cpe:/a:{arg}"
+title: "Package {arg} is installed"
+check_id: cond_package_{arg}
+template:
+ name: cond_package
+args:
+ ntp:
+ pkgname: ntp
+ title: NTP daemon and utilities
diff --git a/shared/templates/cond_package/oval.template b/shared/templates/cond_package/oval.template
new file mode 100644
index 00000000000..7f071d79392
--- /dev/null
+++ b/shared/templates/cond_package/oval.template
@@ -0,0 +1,11 @@
+
+
+ {{{ oval_metadata("The " + pkg_system|upper + " package " + PKGNAME + " should be installed.", affected_platforms=["multi_platform_all"]) }}}
+
+
+
+
+{{{ oval_test_package_installed(package=PKGNAME, test_id="cond_test_" + _RULE_ID + "_installed") }}}
+
diff --git a/shared/templates/cond_package/template.yml b/shared/templates/cond_package/template.yml
new file mode 100644
index 00000000000..2f6f2d2c7cb
--- /dev/null
+++ b/shared/templates/cond_package/template.yml
@@ -0,0 +1,2 @@
+supported_languages:
+ - oval
diff --git a/ssg/build_cpe.py b/ssg/build_cpe.py
index 0a694490ffe..293001f2ab7 100644
--- a/ssg/build_cpe.py
+++ b/ssg/build_cpe.py
@@ -13,7 +13,7 @@
from .xml import ElementTree as ET
from .boolean_expression import Algebra, Symbol, Function
from .boolean_expression import get_base_name_of_parametrized_platform
-from .entities.common import XCCDFEntity
+from .entities.common import XCCDFEntity, Templatable
from .yaml import convert_string_to_bool
@@ -133,7 +133,7 @@ def to_file(self, file_name, cpe_oval_file):
tree.write(file_name, encoding="utf-8")
-class CPEItem(XCCDFEntity):
+class CPEItem(XCCDFEntity, Templatable):
"""
Represents the cpe-item element from the CPE standard.
"""
@@ -144,8 +144,10 @@ class CPEItem(XCCDFEntity):
bash_conditional=lambda: "",
ansible_conditional=lambda: "",
is_product_cpe=lambda: False,
+ args=lambda: {},
** XCCDFEntity.KEYS
)
+ KEYS.update(**Templatable.KEYS)
MANDATORY_KEYS = [
"name",
@@ -264,10 +266,15 @@ def enrich_with_cpe_info(self, cpe_products):
def pass_parameters(self, product_cpes):
if self.arg:
- associated_cpe_item_as_dict = product_cpes.get_cpe(self.cpe_name).represent_as_dict()
+ cpe = product_cpes.get_cpe(self.cpe_name)
+ parameters = cpe.args[self.arg]
+ parameters.update(self.as_dict())
+ associated_cpe_item_as_dict = cpe.represent_as_dict()
new_associated_cpe_item_as_dict = apply_formatting_on_dict_values(
- associated_cpe_item_as_dict, self.as_dict())
+ associated_cpe_item_as_dict, parameters)
new_associated_cpe_item_as_dict["id_"] = self.as_id()
+ if cpe.is_templated():
+ new_associated_cpe_item_as_dict["template"]["vars"] = parameters
new_associated_cpe_item = CPEItem.get_instance_from_full_dict(
new_associated_cpe_item_as_dict)
product_cpes.add_cpe_item(new_associated_cpe_item)
diff --git a/ssg/build_yaml.py b/ssg/build_yaml.py
index c87ccf8444c..1737076c6e7 100644
--- a/ssg/build_yaml.py
+++ b/ssg/build_yaml.py
@@ -1512,14 +1512,13 @@ class Platform(XCCDFEntity):
def from_text(cls, expression, product_cpes):
if not product_cpes:
return None
- test = product_cpes.algebra.parse(
- expression, simplify=True)
- id = test.as_id()
- platform = cls(id)
+ test = product_cpes.algebra.parse(expression, simplify=True)
+ id_ = test.as_id()
+ platform = cls(id_)
platform.test = test
platform.test.pass_parameters(product_cpes)
platform.test.enrich_with_cpe_info(product_cpes)
- platform.name = id
+ platform.name = id_
platform.original_expression = expression
platform.xml_content = platform.get_xml()
platform.bash_conditional = platform.test.to_bash_conditional()
@@ -1529,8 +1528,8 @@ def from_text(cls, expression, product_cpes):
def get_xml(self):
cpe_platform = ET.Element("{%s}platform" % Platform.ns)
cpe_platform.set('id', self.name)
- # in case the platform contains only single CPE name, fake the logical test
- # we have to athere to CPE specification
+ # In case the platform contains only single CPE name, fake the logical test
+ # we have to adhere to CPE specification
if isinstance(self.test, CPEALFactRef):
cpe_test = ET.Element("{%s}logical-test" % CPEALLogicalTest.ns)
cpe_test.set('operator', 'AND')
@@ -1557,11 +1556,10 @@ def get_remediation_conditional(self, language):
def from_yaml(cls, yaml_file, env_yaml=None, product_cpes=None):
platform = super(Platform, cls).from_yaml(yaml_file, env_yaml)
platform.xml_content = ET.fromstring(platform.xml_content)
- # if we did receive a product_cpes, we can restore also the original test object
+ # If we received a product_cpes, we can restore also the original test object
# it can be later used e.g. for comparison
if product_cpes:
- platform.test = product_cpes.algebra.parse(
- platform.original_expression, simplify=True)
+ platform.test = product_cpes.algebra.parse(platform.original_expression, simplify=True)
return platform
def __eq__(self, other):
diff --git a/ssg/templates.py b/ssg/templates.py
index 268c30cabc9..27a221a7d18 100644
--- a/ssg/templates.py
+++ b/ssg/templates.py
@@ -207,6 +207,18 @@ def build_lang_for_templatable(self, templatable, lang):
filled_template = self.get_lang_contents_for_templatable(templatable, lang)
self.write_lang_contents_for_templatable(filled_template, lang, templatable)
+ def build_platform(self, platform):
+ """
+ Builds templated content of a given Platform (all CPEs/Symbols) for all available
+ languages, writing the output to the correct build directories.
+ """
+ for symbol in platform.test.get_symbols():
+ platform.test.pass_parameters(self.product_cpes)
+ cpe = self.product_cpes.get_cpe(symbol.as_id())
+ if cpe.is_templated():
+ for lang in self.get_resolved_langs_to_generate(cpe):
+ self.build_lang_for_templatable(cpe, lang)
+
def build_rule(self, rule):
"""
Builds templated content of a given Rule for all available languages,
@@ -230,6 +242,13 @@ def build_extra_ovals(self):
})
self.build_lang_for_templatable(rule, LANGUAGES["oval"])
+ def build_all_platforms(self):
+ for platform_file in sorted(os.listdir(self.platforms_dir)):
+ platform_path = os.path.join(self.platforms_dir, platform_file)
+ platform = ssg.build_yaml.Platform.from_yaml(platform_path, self.env_yaml,
+ self.product_cpes)
+ self.build_platform(platform)
+
def build_all_rules(self):
for rule_file in sorted(os.listdir(self.resolved_rules_dir)):
rule_path = os.path.join(self.resolved_rules_dir, rule_file)
@@ -252,3 +271,4 @@ def build(self):
self.build_extra_ovals()
self.build_all_rules()
+ self.build_all_platforms()
diff --git a/tests/unit/ssg-module/data/applicability/package.yml b/tests/unit/ssg-module/data/applicability/package.yml
index 4c842bfd164..38fbc270f7b 100644
--- a/tests/unit/ssg-module/data/applicability/package.yml
+++ b/tests/unit/ssg-module/data/applicability/package.yml
@@ -1,3 +1,9 @@
name: "cpe:/a:{arg}"
title: "Package {arg} is installed"
check_id: installed_env_has_{arg}_package
+template:
+ name: package_installed
+args:
+ ntp:
+ pkgname: ntp
+ title: NTP
diff --git a/tests/unit/ssg-module/data/package_ntp.yml b/tests/unit/ssg-module/data/package_ntp.yml
new file mode 100644
index 00000000000..cfcc7f7f258
--- /dev/null
+++ b/tests/unit/ssg-module/data/package_ntp.yml
@@ -0,0 +1,9 @@
+name: package_ntp
+original_expression: package[ntp]
+xml_content:
+bash_conditional: ( ( rpm --quiet -q ntp ) )
+ansible_conditional: ( ( "ntp" in ansible_facts.packages ) )
+definition_location: ''
+documentation_complete: true
+title: 'NTP Package'
diff --git a/tests/unit/ssg-module/test_build_yaml.py b/tests/unit/ssg-module/test_build_yaml.py
index d4229a93d85..b99f0d410f9 100644
--- a/tests/unit/ssg-module/test_build_yaml.py
+++ b/tests/unit/ssg-module/test_build_yaml.py
@@ -362,21 +362,22 @@ def test_platform_as_dict(product_cpes):
assert d["bash_conditional"] == "( rpm --quiet -q chrony )"
assert "xml_content" in d
+
def test_platform_get_invalid_conditional_language(product_cpes):
platform = ssg.build_yaml.Platform.from_text("ntp or chrony", product_cpes)
with pytest.raises(AttributeError):
assert platform.get_remediation_conditional("foo")
+
def test_parametrized_platform(product_cpes):
- platform = ssg.build_yaml.Platform.from_text("package[test]", product_cpes)
+ platform = ssg.build_yaml.Platform.from_text("package[ntp]", product_cpes)
assert platform.test.cpe_name != "cpe:/a:{arg}"
- assert platform.test.cpe_name == "cpe:/a:test"
+ assert platform.test.cpe_name == "cpe:/a:ntp"
cpe_item = product_cpes.get_cpe(platform.test.cpe_name)
- assert cpe_item.name == "cpe:/a:test"
- assert cpe_item.title == "Package test is installed"
- assert cpe_item.check_id == "installed_env_has_test_package"
-
-
+ assert cpe_item.id_ == "package_ntp"
+ assert cpe_item.name == "cpe:/a:ntp"
+ assert cpe_item.title == "Package ntp is installed"
+ assert cpe_item.check_id == "installed_env_has_ntp_package"
def test_derive_id_from_file_name():
diff --git a/tests/unit/ssg-module/test_templates.py b/tests/unit/ssg-module/test_templates.py
index 723e749df1a..6c21c6cf960 100644
--- a/tests/unit/ssg-module/test_templates.py
+++ b/tests/unit/ssg-module/test_templates.py
@@ -13,6 +13,7 @@
ssg_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
DATADIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "data"))
templates_dir = os.path.join(DATADIR, "templates")
+platforms_dir = os.path.join(DATADIR)
cpe_items_dir = os.path.join(DATADIR, "applicability")
build_config_yaml = os.path.join(ssg_root, "build", "build_config.yml")
@@ -33,6 +34,32 @@ def test_render_extra_ovals():
"title": oval_def_id,
"template": template,
})
- oval_content = builder.get_lang_contents_for_templatable(rule,
- ssg.templates.LANGUAGES["oval"])
- assert "
%s" % (oval_def_id,) in oval_content
+ oval_content = builder.get_lang_contents_for_templatable(
+ rule, ssg.templates.LANGUAGES["oval"])
+ assert 'id="package_%s_installed"' % (rule.template['vars']['pkgname']) \
+ in oval_content
+
+ assert "%s" % (oval_def_id,) \
+ in oval_content
+
+
+def test_platform_templates():
+ builder = ssg.templates.Builder(
+ env_yaml, '', templates_dir,
+ '', '', platforms_dir, cpe_items_dir)
+
+ platform_path = os.path.join(builder.platforms_dir, "package_ntp.yml")
+ platform = ssg.build_yaml.Platform.from_yaml(platform_path, builder.env_yaml,
+ builder.product_cpes)
+ for symbol in platform.test.get_symbols():
+ platform.test.pass_parameters(builder.product_cpes)
+ cpe = builder.product_cpes.get_cpe(symbol.as_id())
+ if cpe.is_templated():
+ oval_content = builder.get_lang_contents_for_templatable(
+ cpe, ssg.templates.LANGUAGES["oval"])
+
+ assert 'id="package_%s"' % (cpe.template['vars']['pkgname']) \
+ in oval_content
+
+ assert "Package %s is installed" % (cpe.template['vars']['pkgname'],) \
+ in oval_content