Blob Blame History Raw
commit 7a1fe8125480948e4a15db51b723436da6cd3a7a
Author: Gabriel Becker <ggasparb@redhat.com>
Date:   Fri Apr 5 09:48:15 2019 +0200

    Backport files so machine only tests can run.

diff --git a/example/product.yml b/example/product.yml
new file mode 100644
index 0000000..32538fa
--- /dev/null
+++ b/example/product.yml
@@ -0,0 +1,13 @@
+product: example
+full_name: Example
+type: platform
+
+benchmark_root: "../linux_os/guide"
+
+profiles_root: "./profiles"
+
+pkg_manager: "dnf"
+
+init_system: "systemd"
+
+cpes: []
diff --git a/linux_os/guide/system/auditing/auditd_configure_rules/audit_rules_etc_group_openat.rule b/linux_os/guide/system/auditing/auditd_configure_rules/audit_rules_etc_group_openat.rule
index 61bde4d..355fca0 100644
--- a/linux_os/guide/system/auditing/auditd_configure_rules/audit_rules_etc_group_openat.rule
+++ b/linux_os/guide/system/auditing/auditd_configure_rules/audit_rules_etc_group_openat.rule
@@ -34,3 +34,5 @@ warnings:
         have been placed independent of other system calls. Grouping system calls related
         to the same event is more efficient. See the following example:
         <pre>-a always,exit -F arch=b32 -S open,openat,open_by_handle_at -F a2&amp;03 -F path=/etc/group -F auid>=1000 -F auid!=unset -F key=group-modify</pre>
+
+platform: machine
diff --git a/linux_os/guide/system/auditing/group.yml b/linux_os/guide/system/auditing/group.yml
index 5ea9fee..e052243 100644
--- a/linux_os/guide/system/auditing/group.yml
+++ b/linux_os/guide/system/auditing/group.yml
@@ -101,3 +101,5 @@ description: |-
     the process, which in this case, is <tt>exe="/usr/sbin/httpd"</tt>.
     </li></ul>
     </li></ul>
+
+platform: machine
diff --git a/ssg/constants.py b/ssg/constants.py
index 9bef085..da36007 100644
--- a/ssg/constants.py
+++ b/ssg/constants.py
@@ -4,6 +4,10 @@ from __future__ import print_function
 import datetime
 import os.path
 
+product_directories = ['debian8', 'fedora', 'ol7', 'opensuse', 'rhel6',
+                       'rhel7', 'sle11', 'sle12', 'ubuntu1404',
+                       'ubuntu1604', 'wrlinux', 'rhel-osp7', 'chromium',
+                       'eap6', 'firefox', 'fuse6', 'jre', 'example']
 
 JINJA_MACROS_BASE_DEFINITIONS = os.path.join(os.path.dirname(os.path.dirname(
     __file__)), "shared", "macros.jinja")
@@ -68,6 +72,11 @@ PKG_MANAGER_TO_SYSTEM = {
     "apt_get": "dpkg",
 }
 
+PKG_MANAGER_TO_CONFIG_FILE = {
+    "yum": "/etc/yum.conf",
+    "dnf": "/etc/dnf/dnf.conf",
+}
+
 RHEL_CENTOS_CPE_MAPPING = {
     "cpe:/o:redhat:enterprise_linux:6": "cpe:/o:centos:centos:6",
     "cpe:/o:redhat:enterprise_linux:7": "cpe:/o:centos:centos:7",

commit 6c91ac3b8fbeebe7e8eeabddbf0430f66bd59a0e
Author: Gabriel Becker <ggasparb@redhat.com>
Date:   Thu Apr 4 17:38:28 2019 +0200

    Backport of platform support from https://github.com/ComplianceAsCode/content/pull/3576.

diff --git a/ssg/build_yaml.py b/ssg/build_yaml.py
index ea6ffbe..7520a7c 100644
--- a/ssg/build_yaml.py
+++ b/ssg/build_yaml.py
@@ -6,6 +6,7 @@ import os.path
 import datetime
 import sys
 
+from .constants import XCCDF_PLATFORM_TO_CPE
 from .checks import is_cce_valid
 from .yaml import open_and_expand, open_and_macro_expand
 from .utils import required_key
@@ -382,6 +383,7 @@ class Group(object):
         self.values = {}
         self.groups = {}
         self.rules = {}
+        self.platform = None
 
     @staticmethod
     def from_yaml(yaml_file, env_yaml=None):
@@ -397,6 +399,7 @@ class Group(object):
         group.description = required_key(yaml_contents, "description")
         del yaml_contents["description"]
         group.warnings = yaml_contents.pop("warnings", [])
+        group.platform = yaml_contents.pop("platform", None)
 
         for warning_list in group.warnings:
             if len(warning_list) != 1:
@@ -418,6 +421,14 @@ class Group(object):
         add_sub_element(group, 'description', self.description)
         add_warning_elements(group, self.warnings)
 
+        if self.platform:
+            platform_el = ET.SubElement(group, "platform")
+            try:
+                platform_cpe = XCCDF_PLATFORM_TO_CPE[self.platform]
+            except KeyError:
+                raise ValueError("Unsupported platform '%s' in rule '%s'." % (self.platform, self.id_))
+            platform_el.set("idref", platform_cpe)
+
         for _value in self.values.values():
             group.append(_value.to_xml_element())
         for _group in self.groups.values():
@@ -440,11 +451,15 @@ class Group(object):
     def add_group(self, group):
         if group is None:
             return
+        if self.platform and not group.platform:
+            group.platform = self.platform
         self.groups[group.id_] = group
 
     def add_rule(self, rule):
         if rule is None:
             return
+        if self.platform and not rule.platform:
+            rule.platform = self.platform
         self.rules[rule.id_] = rule
 
     def __str__(self):
@@ -467,6 +482,7 @@ class Rule(object):
         self.ocil = None
         self.external_oval = None
         self.warnings = []
+        self.platform = None
 
     @staticmethod
     def from_yaml(yaml_file, env_yaml=None):
@@ -491,6 +507,7 @@ class Rule(object):
         rule.ocil = yaml_contents.pop("ocil", None)
         rule.external_oval = yaml_contents.pop("oval_external_content", None)
         rule.warnings = yaml_contents.pop("warnings", [])
+        rule.platform = yaml_contents.pop("platform", None)
 
         for warning_list in rule.warnings:
             if len(warning_list) != 1:
@@ -594,6 +611,14 @@ class Rule(object):
 
         add_warning_elements(rule, self.warnings)
 
+        if self.platform:
+            platform_el = ET.SubElement(rule, "platform")
+            try:
+                platform_cpe = XCCDF_PLATFORM_TO_CPE[self.platform]
+            except KeyError:
+                raise ValueError("Unsupported platform '%s' in rule '%s'." % (self.platform, self.id_))
+            platform_el.set("idref", platform_cpe)
+
         return rule
 
     def to_file(self, file_name):
@@ -663,6 +688,8 @@ def add_from_directory(action, parent_group, guide_directory, profiles_dir,
                                     profiles_dir, env_yaml, bash_remediation_fns)
 
     if group is not None:
+        if parent_group:
+            parent_group.add_group(group)
         for value_yaml in values:
             if action == "list-inputs":
                 print(value_yaml)
@@ -682,9 +709,7 @@ def add_from_directory(action, parent_group, guide_directory, profiles_dir,
                 rule = Rule.from_yaml(rule_yaml, env_yaml)
                 group.add_rule(rule)
 
-        if parent_group:
-            parent_group.add_group(group)
-        else:
+        if not parent_group:
             # We are on the top level!
             # Lets dump the XCCDF group or benchmark to a file
             if action == "build":
diff --git a/ssg/constants.py b/ssg/constants.py
index 54e5d61..9bef085 100644
--- a/ssg/constants.py
+++ b/ssg/constants.py
@@ -194,5 +194,10 @@ OCILREFATTR_TO_TAG = {
     "question_ref": "question",
 }
 
+XCCDF_PLATFORM_TO_CPE = {
+    "machine": "cpe:/a:machine",
+    "container": "cpe:/a:container"
+}
+
 # Application constants
 DEFAULT_UID_MIN = 1000
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 5b791a2..ecaa6dc 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -31,3 +31,8 @@ add_test(
     NAME "max-path-len"
     COMMAND "${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/ensure_paths_are_short.py"
 )
+
+add_test(
+    NAME "machine-only-rules"
+    COMMAND env "PYTHONPATH=$ENV{PYTHONPATH}" "${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/test_machine_only_rules.py" --source_dir "${CMAKE_SOURCE_DIR}" --build_dir "${CMAKE_BINARY_DIR}"
+)
diff --git a/tests/test_machine_only_rules.py b/tests/test_machine_only_rules.py
new file mode 100644
index 0000000..94a2e4e
--- /dev/null
+++ b/tests/test_machine_only_rules.py
@@ -0,0 +1,111 @@
+#!/usr/bin/python3
+
+import os
+import argparse
+import xml.etree.ElementTree as ET
+import sys
+import ssg.constants
+import ssg.yaml
+
+machine_cpe = "cpe:/a:machine"
+
+
+def main():
+    args = parse_command_line_args()
+    for product in ssg.constants.product_directories:
+        product_dir = os.path.join(args.source_dir, product)
+        product_yaml_path = os.path.join(product_dir, "product.yml")
+        product_yaml = ssg.yaml.open_raw(product_yaml_path)
+        guide_dir = os.path.abspath(
+            os.path.join(product_dir, product_yaml['benchmark_root']))
+        if not check_product(args.build_dir, product, guide_dir):
+            sys.exit(1)
+
+
+def check_product(build_dir, product, guide_dir):
+    input_groups, input_rules = scan_rules_groups(guide_dir, False)
+    ds_path = os.path.join(build_dir, "ssg-" + product + "-ds.xml")
+    if not check_ds(ds_path, "groups", input_groups):
+        return False
+    return True
+
+
+def check_ds(ds_path, what, input_elems):
+    try:
+        tree = ET.parse(ds_path)
+    except IOError as e:
+        sys.stderr.write("The product datastream '%s' hasn't been build, "
+                         "skipping the test." % (ds_path))
+        return True
+    root = tree.getroot()
+    if what == "groups":
+        replacement = "xccdf_org.ssgproject.content_group_"
+        xpath_query = ".//{%s}Group" % ssg.constants.XCCDF12_NS
+    if what == "rules":
+        replacement = "xccdf_org.ssgproject.content_rule_"
+        xpath_query = ".//{%s}Rule" % ssg.constants.XCCDF12_NS
+    benchmark = root.find(".//{%s}Benchmark" % ssg.constants.XCCDF12_NS)
+    for elem in benchmark.findall(xpath_query):
+        elem_id = elem.get("id")
+        elem_short_id = elem_id.replace(replacement, "")
+        if elem_short_id not in input_elems:
+            continue
+        platforms = elem.findall("{%s}platform" % ssg.constants.XCCDF12_NS)
+        machine_platform = False
+        for p in platforms:
+            idref = p.get("idref")
+            if idref == machine_cpe:
+                machine_platform = True
+        if not machine_platform:
+            sys.stderr.write("%s %s in %s is missing <platform> element" %
+                             (what, elem_short_id, ds_path))
+            return False
+    return True
+
+
+def parse_command_line_args():
+    parser = argparse.ArgumentParser(
+        description="Tests if 'machine' CPEs are "
+                    "propagated to the built datastream")
+    parser.add_argument("--source_dir", required=True,
+                        help="Content source directory path")
+    parser.add_argument("--build_dir", required=True,
+                        help="Build directory containing built datastreams")
+    args = parser.parse_args()
+    return args
+
+
+def check_if_machine_only(dirpath, name, is_machine_only_group):
+    if name in os.listdir(dirpath):
+        if is_machine_only_group:
+            return True
+        yml_path = os.path.join(dirpath, name)
+        with open(yml_path, "r") as yml_file:
+            yml_file_contents = yml_file.read()
+            if "platform: machine" in yml_file_contents:
+                return True
+    return False
+
+
+def scan_rules_groups(dirpath, parent_machine_only):
+    groups = set()
+    rules = set()
+    name = os.path.basename(dirpath)
+    is_machine_only = False
+    if check_if_machine_only(dirpath, "group.yml", parent_machine_only):
+        groups.add(name)
+        is_machine_only = True
+    if check_if_machine_only(dirpath, "rule.yml", parent_machine_only):
+        rules.add(name)
+    for dir_item in os.listdir(dirpath):
+        subdir_path = os.path.join(dirpath, dir_item)
+        if os.path.isdir(subdir_path):
+            subdir_groups, subdir_rules = scan_rules_groups(
+                subdir_path, is_machine_only)
+            groups |= subdir_groups
+            rules |= subdir_rules
+    return groups, rules
+
+
+if __name__ == "__main__":
+    main()