From c8be82fa740c6a0d304f8cdd994a72714bab33e2 Mon Sep 17 00:00:00 2001 From: Andrew Walker Date: Mon, 16 Dec 2024 14:12:23 -0600 Subject: [PATCH] Add auditd rules to middleware configuration Configure auditd rules depending on middleware stig configuration. These are configured by writing symlinks to the auditd rules.d directory. The actual rules sets are defined in separate git repository and are not modified at runtime. This commit also adds some basic tests. --- .../middlewared/etc_files/audit_setup.py | 5 ++ src/middlewared/middlewared/plugins/etc.py | 7 +++ src/middlewared/middlewared/utils/auditd.py | 46 +++++++++++++++++++ tests/unit/test_auditd_rules.py | 40 ++++++++++++++++ 4 files changed, 98 insertions(+) create mode 100644 src/middlewared/middlewared/etc_files/audit_setup.py create mode 100644 src/middlewared/middlewared/utils/auditd.py create mode 100644 tests/unit/test_auditd_rules.py diff --git a/src/middlewared/middlewared/etc_files/audit_setup.py b/src/middlewared/middlewared/etc_files/audit_setup.py new file mode 100644 index 0000000000000..df8598fcd4c15 --- /dev/null +++ b/src/middlewared/middlewared/etc_files/audit_setup.py @@ -0,0 +1,5 @@ +from middlewared.utils.auditd import set_audit_rules + + +def render(service, middleware, render_ctx): + set_audit_rules(render_ctx['system.security.config']['enable_gpos_stig']) diff --git a/src/middlewared/middlewared/plugins/etc.py b/src/middlewared/middlewared/plugins/etc.py index b029b2da0987f..22ffed64ee0bb 100644 --- a/src/middlewared/middlewared/plugins/etc.py +++ b/src/middlewared/middlewared/plugins/etc.py @@ -68,6 +68,13 @@ async def render(self, path, ctx): class EtcService(Service): GROUPS = { + 'audit': { + 'ctx': [{'method': 'system.security.config'}], + 'entries': [ + {'type': 'py', 'path': 'audit_setup'}, + ] + + }, 'docker': [ {'type': 'py', 'path': 'docker/daemon.json'}, ], diff --git a/src/middlewared/middlewared/utils/auditd.py b/src/middlewared/middlewared/utils/auditd.py new file mode 100644 index 0000000000000..a200e1d9d7a2d --- /dev/null +++ b/src/middlewared/middlewared/utils/auditd.py @@ -0,0 +1,46 @@ +import enum +import os +import stat +import subprocess + + +AUDIT_DIR = '/etc/audit' +AUDIT_RULES_DIR = os.path.join(AUDIT_DIR, 'rules.d') +AUDIT_PLUGINS_DIR = os.path.join(AUDIT_DIR, 'plugins.d') +CONF_AUDIT_RULES_DIR = '/conf/audit_rules' + + +class AUDITRules(enum.StrEnum): + BASE = '10-base-config.rules' + STIG = '30-stig.rules' + PRIVILEGED = '31-privileged.rules' + MODULE = '43-module-load.rules' + FINALIZE = '99-finalize.rules' + + +STIG_AUDIT_RULES = frozenset([rules for rules in AUDITRules]) +NOSTIG_AUDIT_RULES = frozenset([AUDITRules.BASE]) + + +def set_audit_rules(gpos_stig_enabled: bool) -> None: + rules_set = STIG_AUDIT_RULES if gpos_stig_enabled else NOSTIG_AUDIT_RULES + + # first remove all files that shouldn't be there + for rules_file in os.listdir(AUDIT_RULES_DIR): + full_path = os.path.join(AUDIT_RULES_DIR, rules_file) + if rules_file not in rules_set: + os.unlink(full_path) + elif not stat.S_ISLNK(os.lstat(full_path).st_mode): + os.unlink(full_path) + elif os.readlink(full_path) != os.path.join(CONF_AUDIT_RULES_DIR, rules_file): + os.unlink(full_path) + + for rules_file in rules_set: + conf_path = os.path.join(CONF_AUDIT_RULES_DIR, rules_file) + audit_path = os.path.join(AUDIT_RULES_DIR, rules_file) + if os.path.exists(audit_path): + continue + + os.symlink(conf_path, audit_path) + + subprocess.run(['augenrules', '--load']) diff --git a/tests/unit/test_auditd_rules.py b/tests/unit/test_auditd_rules.py new file mode 100644 index 0000000000000..cd8d88a890639 --- /dev/null +++ b/tests/unit/test_auditd_rules.py @@ -0,0 +1,40 @@ +import os +import pytest +import subprocess + +from middlewared.utils import auditd + + +@pytest.fixture(scope='function') +def auditd_gpos_stig_enable(): + auditd.set_audit_rules(True) + try: + yield + finally: + auditd.set_audit_rules(False) + + +@pytest.fixture(scope='function') +def auditd_gpos_stig_disable(): + # make extra-sure we're disabled + auditd.set_audit_rules(False) + + +@pytest.mark.parametrize('ruleset', auditd.AUDITRules) +def test__auditd_conf_rules_exist(ruleset): + assert os.path.exists(os.path.join(auditd.CONF_AUDIT_RULES_DIR, ruleset)) + + +def test__auditd_enable_gpos_stig(auditd_gpos_stig_enable): + assert set(os.listdir(auditd.AUDIT_RULES_DIR)) == auditd.STIG_AUDIT_RULES + rules = subprocess.run(['auditctl', '-l'], capture_output=True) + data = rules.stdout.decode().strip() + assert data != 'No rules' + + +def test__auditd_disable_gpos_stig(auditd_gpos_stig_disable): + assert set(os.listdir(auditd.AUDIT_RULES_DIR)) == auditd.NOSTIG_AUDIT_RULES + + rules = subprocess.run(['auditctl', '-l'], capture_output=True) + data = rules.stdout.decode().strip() + assert data == 'No rules'