Blame SOURCES/scap-security-guide-0.1.42-rule_yml_platform_tag_support.patch

0cd8e1
commit 7a1fe8125480948e4a15db51b723436da6cd3a7a
0cd8e1
Author: Gabriel Becker <ggasparb@redhat.com>
0cd8e1
Date:   Fri Apr 5 09:48:15 2019 +0200
0cd8e1
0cd8e1
    Backport files so machine only tests can run.
0cd8e1
0cd8e1
diff --git a/example/product.yml b/example/product.yml
0cd8e1
new file mode 100644
0cd8e1
index 0000000..32538fa
0cd8e1
--- /dev/null
0cd8e1
+++ b/example/product.yml
0cd8e1
@@ -0,0 +1,13 @@
0cd8e1
+product: example
0cd8e1
+full_name: Example
0cd8e1
+type: platform
0cd8e1
+
0cd8e1
+benchmark_root: "../linux_os/guide"
0cd8e1
+
0cd8e1
+profiles_root: "./profiles"
0cd8e1
+
0cd8e1
+pkg_manager: "dnf"
0cd8e1
+
0cd8e1
+init_system: "systemd"
0cd8e1
+
0cd8e1
+cpes: []
0cd8e1
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
0cd8e1
index 61bde4d..355fca0 100644
0cd8e1
--- a/linux_os/guide/system/auditing/auditd_configure_rules/audit_rules_etc_group_openat.rule
0cd8e1
+++ b/linux_os/guide/system/auditing/auditd_configure_rules/audit_rules_etc_group_openat.rule
0cd8e1
@@ -34,3 +34,5 @@ warnings:
0cd8e1
         have been placed independent of other system calls. Grouping system calls related
0cd8e1
         to the same event is more efficient. See the following example:
0cd8e1
         
-a always,exit -F arch=b32 -S open,openat,open_by_handle_at -F a2&03 -F path=/etc/group -F auid>=1000 -F auid!=unset -F key=group-modify
0cd8e1
+
0cd8e1
+platform: machine
0cd8e1
diff --git a/linux_os/guide/system/auditing/group.yml b/linux_os/guide/system/auditing/group.yml
0cd8e1
index 5ea9fee..e052243 100644
0cd8e1
--- a/linux_os/guide/system/auditing/group.yml
0cd8e1
+++ b/linux_os/guide/system/auditing/group.yml
0cd8e1
@@ -101,3 +101,5 @@ description: |-
0cd8e1
     the process, which in this case, is <tt>exe="/usr/sbin/httpd"</tt>.
0cd8e1
     
0cd8e1
     
0cd8e1
+
0cd8e1
+platform: machine
0cd8e1
diff --git a/ssg/constants.py b/ssg/constants.py
0cd8e1
index 9bef085..da36007 100644
0cd8e1
--- a/ssg/constants.py
0cd8e1
+++ b/ssg/constants.py
0cd8e1
@@ -4,6 +4,10 @@ from __future__ import print_function
0cd8e1
 import datetime
0cd8e1
 import os.path
0cd8e1
 
0cd8e1
+product_directories = ['debian8', 'fedora', 'ol7', 'opensuse', 'rhel6',
0cd8e1
+                       'rhel7', 'sle11', 'sle12', 'ubuntu1404',
0cd8e1
+                       'ubuntu1604', 'wrlinux', 'rhel-osp7', 'chromium',
0cd8e1
+                       'eap6', 'firefox', 'fuse6', 'jre', 'example']
0cd8e1
 
0cd8e1
 JINJA_MACROS_BASE_DEFINITIONS = os.path.join(os.path.dirname(os.path.dirname(
0cd8e1
     __file__)), "shared", "macros.jinja")
0cd8e1
@@ -68,6 +72,11 @@ PKG_MANAGER_TO_SYSTEM = {
0cd8e1
     "apt_get": "dpkg",
0cd8e1
 }
0cd8e1
 
0cd8e1
+PKG_MANAGER_TO_CONFIG_FILE = {
0cd8e1
+    "yum": "/etc/yum.conf",
0cd8e1
+    "dnf": "/etc/dnf/dnf.conf",
0cd8e1
+}
0cd8e1
+
0cd8e1
 RHEL_CENTOS_CPE_MAPPING = {
0cd8e1
     "cpe:/o:redhat:enterprise_linux:6": "cpe:/o:centos:centos:6",
0cd8e1
     "cpe:/o:redhat:enterprise_linux:7": "cpe:/o:centos:centos:7",
0cd8e1
0cd8e1
commit 6c91ac3b8fbeebe7e8eeabddbf0430f66bd59a0e
0cd8e1
Author: Gabriel Becker <ggasparb@redhat.com>
0cd8e1
Date:   Thu Apr 4 17:38:28 2019 +0200
0cd8e1
0cd8e1
    Backport of platform support from https://github.com/ComplianceAsCode/content/pull/3576.
0cd8e1
0cd8e1
diff --git a/ssg/build_yaml.py b/ssg/build_yaml.py
0cd8e1
index ea6ffbe..7520a7c 100644
0cd8e1
--- a/ssg/build_yaml.py
0cd8e1
+++ b/ssg/build_yaml.py
0cd8e1
@@ -6,6 +6,7 @@ import os.path
0cd8e1
 import datetime
0cd8e1
 import sys
0cd8e1
 
0cd8e1
+from .constants import XCCDF_PLATFORM_TO_CPE
0cd8e1
 from .checks import is_cce_valid
0cd8e1
 from .yaml import open_and_expand, open_and_macro_expand
0cd8e1
 from .utils import required_key
0cd8e1
@@ -382,6 +383,7 @@ class Group(object):
0cd8e1
         self.values = {}
0cd8e1
         self.groups = {}
0cd8e1
         self.rules = {}
0cd8e1
+        self.platform = None
0cd8e1
 
0cd8e1
     @staticmethod
0cd8e1
     def from_yaml(yaml_file, env_yaml=None):
0cd8e1
@@ -397,6 +399,7 @@ class Group(object):
0cd8e1
         group.description = required_key(yaml_contents, "description")
0cd8e1
         del yaml_contents["description"]
0cd8e1
         group.warnings = yaml_contents.pop("warnings", [])
0cd8e1
+        group.platform = yaml_contents.pop("platform", None)
0cd8e1
 
0cd8e1
         for warning_list in group.warnings:
0cd8e1
             if len(warning_list) != 1:
0cd8e1
@@ -418,6 +421,14 @@ class Group(object):
0cd8e1
         add_sub_element(group, 'description', self.description)
0cd8e1
         add_warning_elements(group, self.warnings)
0cd8e1
 
0cd8e1
+        if self.platform:
0cd8e1
+            platform_el = ET.SubElement(group, "platform")
0cd8e1
+            try:
0cd8e1
+                platform_cpe = XCCDF_PLATFORM_TO_CPE[self.platform]
0cd8e1
+            except KeyError:
0cd8e1
+                raise ValueError("Unsupported platform '%s' in rule '%s'." % (self.platform, self.id_))
0cd8e1
+            platform_el.set("idref", platform_cpe)
0cd8e1
+
0cd8e1
         for _value in self.values.values():
0cd8e1
             group.append(_value.to_xml_element())
0cd8e1
         for _group in self.groups.values():
0cd8e1
@@ -440,11 +451,15 @@ class Group(object):
0cd8e1
     def add_group(self, group):
0cd8e1
         if group is None:
0cd8e1
             return
0cd8e1
+        if self.platform and not group.platform:
0cd8e1
+            group.platform = self.platform
0cd8e1
         self.groups[group.id_] = group
0cd8e1
 
0cd8e1
     def add_rule(self, rule):
0cd8e1
         if rule is None:
0cd8e1
             return
0cd8e1
+        if self.platform and not rule.platform:
0cd8e1
+            rule.platform = self.platform
0cd8e1
         self.rules[rule.id_] = rule
0cd8e1
 
0cd8e1
     def __str__(self):
0cd8e1
@@ -467,6 +482,7 @@ class Rule(object):
0cd8e1
         self.ocil = None
0cd8e1
         self.external_oval = None
0cd8e1
         self.warnings = []
0cd8e1
+        self.platform = None
0cd8e1
 
0cd8e1
     @staticmethod
0cd8e1
     def from_yaml(yaml_file, env_yaml=None):
0cd8e1
@@ -491,6 +507,7 @@ class Rule(object):
0cd8e1
         rule.ocil = yaml_contents.pop("ocil", None)
0cd8e1
         rule.external_oval = yaml_contents.pop("oval_external_content", None)
0cd8e1
         rule.warnings = yaml_contents.pop("warnings", [])
0cd8e1
+        rule.platform = yaml_contents.pop("platform", None)
0cd8e1
 
0cd8e1
         for warning_list in rule.warnings:
0cd8e1
             if len(warning_list) != 1:
0cd8e1
@@ -594,6 +611,14 @@ class Rule(object):
0cd8e1
 
0cd8e1
         add_warning_elements(rule, self.warnings)
0cd8e1
 
0cd8e1
+        if self.platform:
0cd8e1
+            platform_el = ET.SubElement(rule, "platform")
0cd8e1
+            try:
0cd8e1
+                platform_cpe = XCCDF_PLATFORM_TO_CPE[self.platform]
0cd8e1
+            except KeyError:
0cd8e1
+                raise ValueError("Unsupported platform '%s' in rule '%s'." % (self.platform, self.id_))
0cd8e1
+            platform_el.set("idref", platform_cpe)
0cd8e1
+
0cd8e1
         return rule
0cd8e1
 
0cd8e1
     def to_file(self, file_name):
0cd8e1
@@ -663,6 +688,8 @@ def add_from_directory(action, parent_group, guide_directory, profiles_dir,
0cd8e1
                                     profiles_dir, env_yaml, bash_remediation_fns)
0cd8e1
 
0cd8e1
     if group is not None:
0cd8e1
+        if parent_group:
0cd8e1
+            parent_group.add_group(group)
0cd8e1
         for value_yaml in values:
0cd8e1
             if action == "list-inputs":
0cd8e1
                 print(value_yaml)
0cd8e1
@@ -682,9 +709,7 @@ def add_from_directory(action, parent_group, guide_directory, profiles_dir,
0cd8e1
                 rule = Rule.from_yaml(rule_yaml, env_yaml)
0cd8e1
                 group.add_rule(rule)
0cd8e1
 
0cd8e1
-        if parent_group:
0cd8e1
-            parent_group.add_group(group)
0cd8e1
-        else:
0cd8e1
+        if not parent_group:
0cd8e1
             # We are on the top level!
0cd8e1
             # Lets dump the XCCDF group or benchmark to a file
0cd8e1
             if action == "build":
0cd8e1
diff --git a/ssg/constants.py b/ssg/constants.py
0cd8e1
index 54e5d61..9bef085 100644
0cd8e1
--- a/ssg/constants.py
0cd8e1
+++ b/ssg/constants.py
0cd8e1
@@ -194,5 +194,10 @@ OCILREFATTR_TO_TAG = {
0cd8e1
     "question_ref": "question",
0cd8e1
 }
0cd8e1
 
0cd8e1
+XCCDF_PLATFORM_TO_CPE = {
0cd8e1
+    "machine": "cpe:/a:machine",
0cd8e1
+    "container": "cpe:/a:container"
0cd8e1
+}
0cd8e1
+
0cd8e1
 # Application constants
0cd8e1
 DEFAULT_UID_MIN = 1000
0cd8e1
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
0cd8e1
index 5b791a2..ecaa6dc 100644
0cd8e1
--- a/tests/CMakeLists.txt
0cd8e1
+++ b/tests/CMakeLists.txt
0cd8e1
@@ -31,3 +31,8 @@ add_test(
0cd8e1
     NAME "max-path-len"
0cd8e1
     COMMAND "${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/ensure_paths_are_short.py"
0cd8e1
 )
0cd8e1
+
0cd8e1
+add_test(
0cd8e1
+    NAME "machine-only-rules"
0cd8e1
+    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}"
0cd8e1
+)
0cd8e1
diff --git a/tests/test_machine_only_rules.py b/tests/test_machine_only_rules.py
0cd8e1
new file mode 100644
0cd8e1
index 0000000..94a2e4e
0cd8e1
--- /dev/null
0cd8e1
+++ b/tests/test_machine_only_rules.py
0cd8e1
@@ -0,0 +1,111 @@
0cd8e1
+#!/usr/bin/python3
0cd8e1
+
0cd8e1
+import os
0cd8e1
+import argparse
0cd8e1
+import xml.etree.ElementTree as ET
0cd8e1
+import sys
0cd8e1
+import ssg.constants
0cd8e1
+import ssg.yaml
0cd8e1
+
0cd8e1
+machine_cpe = "cpe:/a:machine"
0cd8e1
+
0cd8e1
+
0cd8e1
+def main():
0cd8e1
+    args = parse_command_line_args()
0cd8e1
+    for product in ssg.constants.product_directories:
0cd8e1
+        product_dir = os.path.join(args.source_dir, product)
0cd8e1
+        product_yaml_path = os.path.join(product_dir, "product.yml")
0cd8e1
+        product_yaml = ssg.yaml.open_raw(product_yaml_path)
0cd8e1
+        guide_dir = os.path.abspath(
0cd8e1
+            os.path.join(product_dir, product_yaml['benchmark_root']))
0cd8e1
+        if not check_product(args.build_dir, product, guide_dir):
0cd8e1
+            sys.exit(1)
0cd8e1
+
0cd8e1
+
0cd8e1
+def check_product(build_dir, product, guide_dir):
0cd8e1
+    input_groups, input_rules = scan_rules_groups(guide_dir, False)
0cd8e1
+    ds_path = os.path.join(build_dir, "ssg-" + product + "-ds.xml")
0cd8e1
+    if not check_ds(ds_path, "groups", input_groups):
0cd8e1
+        return False
0cd8e1
+    return True
0cd8e1
+
0cd8e1
+
0cd8e1
+def check_ds(ds_path, what, input_elems):
0cd8e1
+    try:
0cd8e1
+        tree = ET.parse(ds_path)
0cd8e1
+    except IOError as e:
0cd8e1
+        sys.stderr.write("The product datastream '%s' hasn't been build, "
0cd8e1
+                         "skipping the test." % (ds_path))
0cd8e1
+        return True
0cd8e1
+    root = tree.getroot()
0cd8e1
+    if what == "groups":
0cd8e1
+        replacement = "xccdf_org.ssgproject.content_group_"
0cd8e1
+        xpath_query = ".//{%s}Group" % ssg.constants.XCCDF12_NS
0cd8e1
+    if what == "rules":
0cd8e1
+        replacement = "xccdf_org.ssgproject.content_rule_"
0cd8e1
+        xpath_query = ".//{%s}Rule" % ssg.constants.XCCDF12_NS
0cd8e1
+    benchmark = root.find(".//{%s}Benchmark" % ssg.constants.XCCDF12_NS)
0cd8e1
+    for elem in benchmark.findall(xpath_query):
0cd8e1
+        elem_id = elem.get("id")
0cd8e1
+        elem_short_id = elem_id.replace(replacement, "")
0cd8e1
+        if elem_short_id not in input_elems:
0cd8e1
+            continue
0cd8e1
+        platforms = elem.findall("{%s}platform" % ssg.constants.XCCDF12_NS)
0cd8e1
+        machine_platform = False
0cd8e1
+        for p in platforms:
0cd8e1
+            idref = p.get("idref")
0cd8e1
+            if idref == machine_cpe:
0cd8e1
+                machine_platform = True
0cd8e1
+        if not machine_platform:
0cd8e1
+            sys.stderr.write("%s %s in %s is missing <platform> element" %
0cd8e1
+                             (what, elem_short_id, ds_path))
0cd8e1
+            return False
0cd8e1
+    return True
0cd8e1
+
0cd8e1
+
0cd8e1
+def parse_command_line_args():
0cd8e1
+    parser = argparse.ArgumentParser(
0cd8e1
+        description="Tests if 'machine' CPEs are "
0cd8e1
+                    "propagated to the built datastream")
0cd8e1
+    parser.add_argument("--source_dir", required=True,
0cd8e1
+                        help="Content source directory path")
0cd8e1
+    parser.add_argument("--build_dir", required=True,
0cd8e1
+                        help="Build directory containing built datastreams")
0cd8e1
+    args = parser.parse_args()
0cd8e1
+    return args
0cd8e1
+
0cd8e1
+
0cd8e1
+def check_if_machine_only(dirpath, name, is_machine_only_group):
0cd8e1
+    if name in os.listdir(dirpath):
0cd8e1
+        if is_machine_only_group:
0cd8e1
+            return True
0cd8e1
+        yml_path = os.path.join(dirpath, name)
0cd8e1
+        with open(yml_path, "r") as yml_file:
0cd8e1
+            yml_file_contents = yml_file.read()
0cd8e1
+            if "platform: machine" in yml_file_contents:
0cd8e1
+                return True
0cd8e1
+    return False
0cd8e1
+
0cd8e1
+
0cd8e1
+def scan_rules_groups(dirpath, parent_machine_only):
0cd8e1
+    groups = set()
0cd8e1
+    rules = set()
0cd8e1
+    name = os.path.basename(dirpath)
0cd8e1
+    is_machine_only = False
0cd8e1
+    if check_if_machine_only(dirpath, "group.yml", parent_machine_only):
0cd8e1
+        groups.add(name)
0cd8e1
+        is_machine_only = True
0cd8e1
+    if check_if_machine_only(dirpath, "rule.yml", parent_machine_only):
0cd8e1
+        rules.add(name)
0cd8e1
+    for dir_item in os.listdir(dirpath):
0cd8e1
+        subdir_path = os.path.join(dirpath, dir_item)
0cd8e1
+        if os.path.isdir(subdir_path):
0cd8e1
+            subdir_groups, subdir_rules = scan_rules_groups(
0cd8e1
+                subdir_path, is_machine_only)
0cd8e1
+            groups |= subdir_groups
0cd8e1
+            rules |= subdir_rules
0cd8e1
+    return groups, rules
0cd8e1
+
0cd8e1
+
0cd8e1
+if __name__ == "__main__":
0cd8e1
+    main()