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