[RFC cip-dev][isar-cip-core][PATCH 08/14] Add class for validating the CIS options in the kernel config

Quirin Gylstorff <[email protected]> Fri, 24 Jul 2026 14:39:10 +0200
Newsgroups org.cip-project.lists.cip-dev
Message-ID <[email protected]>
From: Quirin Gylstorff <[email protected]>

If CIS hardening options have been configured that change the
kernel configuration, this class checks whether the correct options
are selected in the resulting kernel configuration in ROOTFSDIR.
A JSON report is generated in DEPLOY_DIR_IMAGE indicating whether
the CIS rules for the kernel options were correctly applied
(compliant: true/false).

Co-Authored-by: Clara Kowalsky <[email protected]>
Co-Authored-by: Felix Moessbauer <[email protected]>
Co-Authored-by: Christoph Steiger <[email protected]>
Signed-off-by: Quirin Gylstorff <[email protected]>
---
 .../cis-kernel-hardening-validation.bbclass   | 110 ++++++++++++++++++
 1 file changed, 110 insertions(+)
 create mode 100644 classes-recipe/cis-kernel-hardening-validation.bbclass

diff --git a/classes-recipe/cis-kernel-hardening-validation.bbclass b/classes-recipe/cis-kernel-hardening-validation.bbclass
new file mode 100644
index 0000000..184da7f
--- /dev/null
+++ b/classes-recipe/cis-kernel-hardening-validation.bbclass
@@ -0,0 +1,110 @@
+#
+# CIP Core, generic profile
+#
+# Copyright (c) Siemens AG, 2026
+#
+# Authors:
+#  Clara Kowalsky <[email protected]>
+#
+# SPDX-License-Identifier: MIT
+#
+# If CIS hardening options have been configured that change the
+# kernel configuration, this class checks whether the correct options
+# are selected in the resulting kernel configuration in ROOTFSDIR.
+# A JSON report is generated in DEPLOY_DIR_IMAGE indicating whether
+# the CIS rules for the kernel options were correctly applied
+# (compliant: true/false).
+#
+CIP_CIS_HARDENING_REPORT ??= "${DISTRO}-hardening-kernel-report.json"
+
+DEPLOYDIR_HARDENING = "${WORKDIR}/deploy-hardening"
+SSTATETASKS += "do_validate_kernel_config"
+
+do_validate_kernel_config[cleandirs] += "${DEPLOYDIR_HARDENING}"
+do_validate_kernel_config[sstate-inputdirs] = "${DEPLOYDIR_HARDENING}"
+do_validate_kernel_config[sstate-outputdirs] = "${DEPLOY_DIR_IMAGE}"
+do_validate_kernel_config[depends] += "cip-cis-rules-config:do_deploy_hardening"
+python do_validate_kernel_config() {
+    import os
+    import json
+    import re
+
+    deploy_dir_stage = d.getVar('DEPLOYDIR_HARDENING')
+    deploy_dir_image = d.getVar('DEPLOY_DIR_IMAGE')
+    boot_dir = os.path.join(d.getVar('ROOTFSDIR'), "boot")
+    kernel_config_path = None
+
+    if os.path.isdir(boot_dir):
+        for f in os.listdir(boot_dir):
+            if f.startswith("config-") and os.path.isfile(os.path.join(boot_dir, f)):
+                kernel_config_path = os.path.join(boot_dir, f)
+                break
+
+    if not kernel_config_path:
+        return
+
+    hardening_config_path = os.path.join(deploy_dir_image, d.getVar('CIP_CIS_HARDENING_KCONFIG'))
+    if not os.path.isfile(hardening_config_path):
+        return
+
+    report = os.path.join(deploy_dir_stage, d.getVar('CIP_CIS_HARDENING_REPORT'))
+
+    kernel_options = {}
+    with open(kernel_config_path, 'r') as f_kernel:
+        for line in f_kernel:
+            match = re.match(r"^(CONFIG_[A-Za-z0-9_]+)=(y|m)$", line.strip())
+            if match:
+                kernel_options[match.group(1)] = match.group(2)
+
+    report_rules = []
+    with open(hardening_config_path, 'r') as f_hardening:
+        hardening_lines = f_hardening.readlines()
+
+    i = 0
+    while i < len(hardening_lines):
+        line = hardening_lines[i].strip()
+        cis_match = re.match(r"^# CIS ([0-9.]+): (.*)$", line)
+        if cis_match:
+            cis_number = cis_match.group(1)
+            description = cis_match.group(2)
+            config_option = None
+
+            if i + 1 < len(hardening_lines):
+                next_line = hardening_lines[i+1].strip()
+                config_option_match = re.match(r"^# (CONFIG_[A-Za-z0-9_]*) is not set$", next_line)
+                if config_option_match:
+                    config_option = config_option_match.group(1)
+                    i += 1
+
+            is_compliant = not (config_option and kernel_options.get(config_option) in ['y', 'm'])
+
+            report_rules.append({
+                "rule": {
+                    "name": cis_number,
+                    "description": description,
+                    "compliant": is_compliant
+                }
+            })
+        i += 1
+
+    if not report_rules:
+        return
+
+    with open(report, 'w') as f:
+        json.dump({"rules": report_rules}, f, indent=2)
+
+    non_compliant = [r["rule"]["name"] for r in report_rules if not r["rule"]["compliant"]]
+    if non_compliant:
+        bb.fatal(
+            "CIS kernel hardening validation failed. The following CIS rules "
+            "have kernel options enabled that should be disabled: %s. "
+            "Details in %s." % (", ".join(non_compliant), report)
+        )
+}
+
+python do_validate_kernel_config_setscene () {
+    sstate_setscene(d)
+}
+addtask validate_kernel_config_setscene
+
+addtask validate_kernel_config before do_rootfs_finalize after do_rootfs_postprocess
-- 
2.53.0