commit 7a1fe8125480948e4a15db51b723436da6cd3a7a Author: Gabriel Becker 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:
-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
+ +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 exe="/usr/sbin/httpd". + +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 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 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()