1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 cfg = config.security.audit;
9
10 failureModes = {
11 silent = 0;
12 printk = 1;
13 panic = 2;
14 };
15
16 # The order of the fixed rules is determined by augenrules(8)
17 rules = pkgs.writeTextDir "audit.rules" ''
18 -D
19 -b ${toString cfg.backlogLimit}
20 -f ${toString failureModes.${cfg.failureMode}}
21 -r ${toString cfg.rateLimit}
22 ${lib.concatLines cfg.rules}
23 -e ${if cfg.enable == "lock" then "2" else "1"}
24 '';
25in
26{
27 options = {
28 security.audit = {
29 enable = lib.mkOption {
30 type = lib.types.enum [
31 false
32 true
33 "lock"
34 ];
35 default = false;
36 description = ''
37 Whether to enable the Linux audit system. The special `lock` value can be used to
38 enable auditing and prevent disabling it until a restart. Be careful about locking
39 this, as it will prevent you from changing your audit configuration until you
40 restart. If possible, test your configuration using build-vm beforehand.
41 '';
42 };
43
44 failureMode = lib.mkOption {
45 type = lib.types.enum [
46 "silent"
47 "printk"
48 "panic"
49 ];
50 default = "printk";
51 description = "How to handle critical errors in the auditing system";
52 };
53
54 backlogLimit = lib.mkOption {
55 type = lib.types.int;
56 # Significantly increase from the kernel default of 64 because a
57 # normal systems generates way more logs.
58 default = 1024;
59 description = ''
60 The maximum number of outstanding audit buffers allowed; exceeding this is
61 considered a failure and handled in a manner specified by failureMode.
62 '';
63 };
64
65 rateLimit = lib.mkOption {
66 type = lib.types.int;
67 default = 0;
68 description = ''
69 The maximum messages per second permitted before triggering a failure as
70 specified by failureMode. Setting it to zero disables the limit.
71 '';
72 };
73
74 rules = lib.mkOption {
75 type = lib.types.listOf lib.types.str; # (types.either types.str (types.submodule rule));
76 default = [ ];
77 example = [ "-a exit,always -F arch=b64 -S execve" ];
78 description = ''
79 The ordered audit rules, with each string appearing as one line of the audit.rules file.
80 '';
81 };
82 };
83 };
84
85 config = lib.mkIf (cfg.enable == "lock" || cfg.enable) {
86 boot.kernelParams = [
87 # A lot of audit events happen before the systemd service starts. Thus
88 # enable it via the kernel commandline to have the audit subsystem ready
89 # as soon as the kernel starts.
90 "audit=1"
91 # Also set the backlog limit because the kernel default is too small to
92 # capture all of them before the service starts.
93 "audit_backlog_limit=${toString cfg.backlogLimit}"
94 ];
95
96 environment.systemPackages = [ pkgs.audit ];
97
98 systemd.services.audit-rules = {
99 description = "Load Audit Rules";
100 wantedBy = [ "sysinit.target" ];
101 before = [
102 "sysinit.target"
103 "shutdown.target"
104 ];
105 conflicts = [ "shutdown.target" ];
106
107 unitConfig = {
108 DefaultDependencies = false;
109 ConditionVirtualization = "!container";
110 ConditionKernelCommandLine = [
111 "!audit=0"
112 "!audit=off"
113 ];
114 };
115
116 serviceConfig = {
117 Type = "oneshot";
118 RemainAfterExit = true;
119 ExecStart = "${lib.getExe' pkgs.audit "auditctl"} -R ${rules}/audit.rules";
120 ExecStopPost = [
121 # Disable auditing
122 "${lib.getExe' pkgs.audit "auditctl"} -e 0"
123 # Delete all rules
124 "${lib.getExe' pkgs.audit "auditctl"} -D"
125 ];
126 };
127 };
128 };
129}