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