diff --git a/nixos/modules/security/audit.nix b/nixos/modules/security/audit.nix index 16d750c8fbed4..4bcc6c62d1cb8 100644 --- a/nixos/modules/security/audit.nix +++ b/nixos/modules/security/audit.nix @@ -6,7 +6,6 @@ }: let cfg = config.security.audit; - enabled = cfg.enable == "lock" || cfg.enable; failureModes = { silent = 0; @@ -14,43 +13,14 @@ let panic = 2; }; - disableScript = pkgs.writeScript "audit-disable" '' - #!${pkgs.runtimeShell} -eu - # Explicitly disable everything, as otherwise journald might start it. - auditctl -D - auditctl -e 0 -a task,never - ''; - - # TODO: it seems like people like their rules to be somewhat secret, yet they will not be if - # put in the store like this. At the same time, it doesn't feel like a huge deal and working - # around that is a pain so I'm leaving it like this for now. - startScript = pkgs.writeScript "audit-start" '' - #!${pkgs.runtimeShell} -eu - # Clear out any rules we may start with - auditctl -D - - # Put the rules in a temporary file owned and only readable by root - rulesfile="$(mktemp)" - ${lib.concatMapStrings (x: "echo '${x}' >> $rulesfile\n") cfg.rules} - - # Apply the requested rules - auditctl -R "$rulesfile" - - # Enable and configure auditing - auditctl \ - -e ${if cfg.enable == "lock" then "2" else "1"} \ - -b ${toString cfg.backlogLimit} \ - -f ${toString failureModes.${cfg.failureMode}} \ - -r ${toString cfg.rateLimit} - ''; - - stopScript = pkgs.writeScript "audit-stop" '' - #!${pkgs.runtimeShell} -eu - # Clear the rules - auditctl -D - - # Disable auditing - auditctl -e 0 + # The order of the fixed rules is determined by augenrules(8) + rules = pkgs.writeTextDir "audit.rules" '' + -D + -b ${toString cfg.backlogLimit} + -f ${toString failureModes.${cfg.failureMode}} + -r ${toString cfg.rateLimit} + ${lib.concatLines cfg.rules} + -e ${if cfg.enable == "lock" then "2" else "1"} ''; in { @@ -110,23 +80,35 @@ in }; }; - config = { - systemd.services.audit = { - description = "Kernel Auditing"; - wantedBy = [ "basic.target" ]; + config = lib.mkIf (cfg.enable == "lock" || cfg.enable) { + systemd.services.audit-rules = { + description = "Load Audit Rules"; + wantedBy = [ "sysinit.target" ]; + before = [ + "sysinit.target" + "shutdown.target" + ]; + conflicts = [ "shutdown.target" ]; unitConfig = { + DefaultDependencies = false; ConditionVirtualization = "!container"; - ConditionSecurity = [ "audit" ]; + ConditionKernelCommandLine = [ + "!audit=0" + "!audit=off" + ]; }; - path = [ pkgs.audit ]; - serviceConfig = { Type = "oneshot"; RemainAfterExit = true; - ExecStart = "@${if enabled then startScript else disableScript} audit-start"; - ExecStop = "@${stopScript} audit-stop"; + ExecStart = "${lib.getExe' pkgs.audit "auditctl"} -R ${rules}/audit.rules"; + ExecStopPost = [ + # Disable auditing + "${lib.getExe' pkgs.audit "auditctl"} -e 0" + # Delete all rules + "${lib.getExe' pkgs.audit "auditctl"} -D" + ]; }; }; }; diff --git a/nixos/modules/security/auditd.nix b/nixos/modules/security/auditd.nix index a281c96165fc2..45ea1d9b2af09 100644 --- a/nixos/modules/security/auditd.nix +++ b/nixos/modules/security/auditd.nix @@ -9,12 +9,13 @@ options.security.auditd.enable = lib.mkEnableOption "the Linux Audit daemon"; config = lib.mkIf config.security.auditd.enable { - boot.kernelParams = [ "audit=1" ]; + # Starting auditd should also enable loading the audit rules.. + security.audit.enable = lib.mkDefault true; environment.systemPackages = [ pkgs.audit ]; systemd.services.auditd = { - description = "Linux Audit daemon"; + description = "Security Audit Logging Service"; documentation = [ "man:auditd(8)" ]; wantedBy = [ "sysinit.target" ]; after = [ @@ -28,16 +29,26 @@ conflicts = [ "shutdown.target" ]; unitConfig = { - ConditionVirtualization = "!container"; - ConditionSecurity = [ "audit" ]; DefaultDependencies = false; + RefuseManualStop = true; + ConditionVirtualization = "!container"; + ConditionKernelCommandLine = [ + "!audit=0" + "!audit=off" + ]; }; - path = [ pkgs.audit ]; - serviceConfig = { - ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p /var/log/audit"; + LogsDirectory = "audit"; ExecStart = "${pkgs.audit}/bin/auditd -l -n -s nochange"; + Restart = "on-failure"; + # Do not restart for intentional exits. See EXIT CODES section in auditd(8). + RestartPreventExitStatus = "2 4 6"; + + # Upstream hardening settings + MemoryDenyWriteExecute = true; + LockPersonality = true; + RestrictRealtime = true; }; }; }; diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index b24bc4d7d7af0..efc42900d364f 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -226,6 +226,7 @@ in atticd = runTest ./atticd.nix; atuin = runTest ./atuin.nix; ax25 = runTest ./ax25.nix; + audit = runTest ./audit.nix; audiobookshelf = runTest ./audiobookshelf.nix; auth-mysql = runTest ./auth-mysql.nix; authelia = runTest ./authelia.nix; diff --git a/nixos/tests/audit.nix b/nixos/tests/audit.nix new file mode 100644 index 0000000000000..b99a0f98f0ad4 --- /dev/null +++ b/nixos/tests/audit.nix @@ -0,0 +1,37 @@ +{ + + name = "audit"; + + nodes = { + machine = + { lib, pkgs, ... }: + { + security.audit = { + enable = true; + rules = [ + "-a always,exit -F exe=${lib.getExe pkgs.hello} -k nixos-test" + ]; + }; + security.auditd.enable = true; + + environment.systemPackages = [ pkgs.hello ]; + }; + }; + + testScript = '' + machine.wait_for_unit("audit-rules.service") + machine.wait_for_unit("auditd.service") + + with subtest("Audit subsystem gets enabled"): + assert "enabled 1" in machine.succeed("auditctl -s") + + with subtest("Custom rule produces audit traces"): + machine.succeed("hello") + print(machine.succeed("ausearch -k nixos-test -sc exit_group")) + + with subtest("Stopping audit-rules.service disables the audit subsystem"): + machine.succeed("systemctl stop audit-rules.service") + assert "enabled 0" in machine.succeed("auditctl -s") + ''; + +} diff --git a/pkgs/by-name/au/audit/package.nix b/pkgs/by-name/au/audit/package.nix index 65fa3348f82e6..177bf7212c057 100644 --- a/pkgs/by-name/au/audit/package.nix +++ b/pkgs/by-name/au/audit/package.nix @@ -17,6 +17,7 @@ enablePython ? stdenv.hostPlatform == stdenv.buildPlatform, nix-update-script, testers, + nixosTests, }: stdenv.mkDerivation (finalAttrs: { pname = "audit"; @@ -90,6 +91,7 @@ stdenv.mkDerivation (finalAttrs: { tests = { musl = pkgsCross.musl64.audit; pkg-config = testers.testMetaPkgConfig finalAttrs.finalPackage; + audit = nixosTests.audit; }; };