1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 cfg = config.security.audit;
9 enabled = cfg.enable == "lock" || cfg.enable;
10
11 failureModes = {
12 silent = 0;
13 printk = 1;
14 panic = 2;
15 };
16
17 disableScript = pkgs.writeScript "audit-disable" ''
18 #!${pkgs.runtimeShell} -eu
19 # Explicitly disable everything, as otherwise journald might start it.
20 auditctl -D
21 auditctl -e 0 -a task,never
22 '';
23
24 # TODO: it seems like people like their rules to be somewhat secret, yet they will not be if
25 # put in the store like this. At the same time, it doesn't feel like a huge deal and working
26 # around that is a pain so I'm leaving it like this for now.
27 startScript = pkgs.writeScript "audit-start" ''
28 #!${pkgs.runtimeShell} -eu
29 # Clear out any rules we may start with
30 auditctl -D
31
32 # Put the rules in a temporary file owned and only readable by root
33 rulesfile="$(mktemp)"
34 ${lib.concatMapStrings (x: "echo '${x}' >> $rulesfile\n") cfg.rules}
35
36 # Apply the requested rules
37 auditctl -R "$rulesfile"
38
39 # Enable and configure auditing
40 auditctl \
41 -e ${if cfg.enable == "lock" then "2" else "1"} \
42 -b ${toString cfg.backlogLimit} \
43 -f ${toString failureModes.${cfg.failureMode}} \
44 -r ${toString cfg.rateLimit}
45 '';
46
47 stopScript = pkgs.writeScript "audit-stop" ''
48 #!${pkgs.runtimeShell} -eu
49 # Clear the rules
50 auditctl -D
51
52 # Disable auditing
53 auditctl -e 0
54 '';
55in
56{
57 options = {
58 security.audit = {
59 enable = lib.mkOption {
60 type = lib.types.enum [
61 false
62 true
63 "lock"
64 ];
65 default = false;
66 description = ''
67 Whether to enable the Linux audit system. The special `lock` value can be used to
68 enable auditing and prevent disabling it until a restart. Be careful about locking
69 this, as it will prevent you from changing your audit configuration until you
70 restart. If possible, test your configuration using build-vm beforehand.
71 '';
72 };
73
74 failureMode = lib.mkOption {
75 type = lib.types.enum [
76 "silent"
77 "printk"
78 "panic"
79 ];
80 default = "printk";
81 description = "How to handle critical errors in the auditing system";
82 };
83
84 backlogLimit = lib.mkOption {
85 type = lib.types.int;
86 default = 64; # Apparently the kernel default
87 description = ''
88 The maximum number of outstanding audit buffers allowed; exceeding this is
89 considered a failure and handled in a manner specified by failureMode.
90 '';
91 };
92
93 rateLimit = lib.mkOption {
94 type = lib.types.int;
95 default = 0;
96 description = ''
97 The maximum messages per second permitted before triggering a failure as
98 specified by failureMode. Setting it to zero disables the limit.
99 '';
100 };
101
102 rules = lib.mkOption {
103 type = lib.types.listOf lib.types.str; # (types.either types.str (types.submodule rule));
104 default = [ ];
105 example = [ "-a exit,always -F arch=b64 -S execve" ];
106 description = ''
107 The ordered audit rules, with each string appearing as one line of the audit.rules file.
108 '';
109 };
110 };
111 };
112
113 config = {
114 systemd.services.audit = {
115 description = "Kernel Auditing";
116 wantedBy = [ "basic.target" ];
117
118 unitConfig = {
119 ConditionVirtualization = "!container";
120 ConditionSecurity = [ "audit" ];
121 };
122
123 path = [ pkgs.audit ];
124
125 serviceConfig = {
126 Type = "oneshot";
127 RemainAfterExit = true;
128 ExecStart = "@${if enabled then startScript else disableScript} audit-start";
129 ExecStop = "@${stopScript} audit-stop";
130 };
131 };
132 };
133}