{ config, lib, pkgs, ... }: let cfg = config.security.auditd; settingsType = with lib.types; nullOr (oneOf [ bool nonEmptyStr path int ]); prepareConfigValue = v: if lib.isBool v then (if v then "yes" else "no") else if lib.isList v then lib.concatStringsSep " " (map prepareConfigValue v) else builtins.toString v; prepareConfigText = conf: lib.concatLines ( lib.mapAttrsToList (k: v: if v == null then "#${k} =" else "${k} = ${prepareConfigValue v}") conf ); in { options.security.auditd = { enable = lib.mkEnableOption "the Linux Audit daemon"; settings = lib.mkOption { type = lib.types.submodule { freeformType = lib.types.attrsOf settingsType; options = { # space_left needs to be larger than admin_space_left, yet they default to be the same if left open. space_left = lib.mkOption { type = lib.types.either lib.types.int (lib.types.strMatching "[0-9]+%"); default = 75; description = '' If the free space in the filesystem containing log_file drops below this value, the audit daemon takes the action specified by {option}`space_left_action`. If the value of {option}`space_left` is specified as a whole number, it is interpreted as an absolute size in mebibytes (MiB). If the value is specified as a number between 1 and 99 followed by a percentage sign (e.g., 5%), the audit daemon calculates the absolute size in megabytes based on the size of the filesystem containing {option}`log_file`. (E.g., if the filesystem containing {option}`log_file` is 2 gibibytes in size, and {option}`space_left` is set to 25%, then the audit daemon sets {option}`space_left` to approximately 500 mebibytes. ::: {.note} This calculation is performed when the audit daemon starts, so if you resize the filesystem containing {option}`log_file` while the audit daemon is running, you should send the audit daemon SIGHUP to re-read the configuration file and recalculate the correct per‐ centage. ::: ''; }; admin_space_left = lib.mkOption { type = lib.types.either lib.types.int (lib.types.strMatching "[0-9]+%"); default = 50; description = '' This is a numeric value in mebibytes (MiB) that tells the audit daemon when to perform a configurable action because the system is running low on disk space. This should be considered the last chance to do something before running out of disk space. The numeric value for this parameter should be lower than the number for {option}`space_left`. You may also append a percent sign (e.g. 1%) to the number to have the audit daemon calculate the number based on the disk partition size. ''; }; }; }; default = { }; description = "auditd configuration file contents. See {auditd.conf} for supported values."; }; }; config = lib.mkIf cfg.enable { assertions = [ { assertion = let cfg' = cfg.settings; in ( (lib.isInt cfg'.space_left && lib.isInt cfg'.admin_space_left) -> cfg'.space_left > cfg'.admin_space_left ) && ( let get_percent = s: lib.toInt (lib.strings.removeSuffix "%" s); in (lib.isString cfg'.space_left && lib.isString cfg'.admin_space_left) -> (get_percent cfg'.space_left) > (get_percent cfg'.admin_space_left) ); message = "`security.auditd.settings.space_left` must be larger than `security.auditd.settings.admin_space_left`"; } ]; # Starting auditd should also enable loading the audit rules.. security.audit.enable = lib.mkDefault true; environment.systemPackages = [ pkgs.audit ]; environment.etc = { "audit/auditd.conf".text = prepareConfigText cfg.settings; }; systemd.services.auditd = { description = "Security Audit Logging Service"; documentation = [ "man:auditd(8)" ]; wantedBy = [ "sysinit.target" ]; after = [ "local-fs.target" "systemd-tmpfiles-setup.service" ]; before = [ "sysinit.target" "shutdown.target" ]; conflicts = [ "shutdown.target" ]; unitConfig = { DefaultDependencies = false; RefuseManualStop = true; ConditionVirtualization = "!container"; ConditionKernelCommandLine = [ "!audit=0" "!audit=off" ]; }; serviceConfig = { 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; }; }; }; }