at master 10 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7let 8 cfg = config.security.auditd; 9 10 settingsType = 11 with lib.types; 12 nullOr (oneOf [ 13 bool 14 nonEmptyStr 15 path 16 int 17 ]); 18 19 pluginOptions = lib.types.submodule { 20 options = { 21 active = lib.mkEnableOption "Whether to enable this plugin"; 22 direction = lib.mkOption { 23 type = lib.types.enum [ 24 "in" 25 "out" 26 ]; 27 default = "out"; 28 description = '' 29 The option is dictated by the plugin. In or out are the only choices. 30 You cannot make a plugin operate in a way it wasn't designed just by 31 changing this option. This option is to give a clue to the event dispatcher 32 about which direction events flow. 33 34 ::: {.note} 35 Inbound events are not supported yet. 36 ::: 37 ''; 38 }; 39 path = lib.mkOption { 40 type = lib.types.path; 41 description = "This is the absolute path to the plugin executable."; 42 }; 43 type = lib.mkOption { 44 type = lib.types.enum [ "always" ]; 45 readOnly = true; 46 default = "always"; 47 description = '' 48 This tells the dispatcher how the plugin wants to be run. There is only 49 one valid option, `always`, which means the plugin is external and should 50 always be run. The default is `always` since there are no more builtin plugins. 51 ''; 52 }; 53 args = lib.mkOption { 54 type = lib.types.nullOr (lib.types.listOf lib.types.nonEmptyStr); 55 default = null; 56 description = '' 57 This allows you to pass arguments to the child program. 58 Generally plugins do not take arguments and have their own 59 config file that instructs them how they should be configured. 60 ''; 61 }; 62 format = lib.mkOption { 63 type = lib.types.enum [ 64 "binary" 65 "string" 66 ]; 67 default = "string"; 68 description = '' 69 Binary passes the data exactly as the audit event dispatcher gets it from 70 the audit daemon. The string option tells the dispatcher to completely change 71 the event into a string suitable for parsing with the audit parsing library. 72 ''; 73 }; 74 settings = lib.mkOption { 75 type = lib.types.nullOr ( 76 lib.types.submodule { 77 freeformType = lib.types.attrsOf settingsType; 78 } 79 ); 80 default = null; 81 description = "Plugin-specific config file to link to /etc/audit/<plugin>.conf"; 82 }; 83 }; 84 }; 85 86 prepareConfigValue = 87 v: 88 if lib.isBool v then 89 (if v then "yes" else "no") 90 else if lib.isList v then 91 lib.concatStringsSep " " (map prepareConfigValue v) 92 else 93 builtins.toString v; 94 prepareConfigText = 95 conf: 96 lib.concatLines ( 97 lib.mapAttrsToList (k: v: if v == null then "#${k} =" else "${k} = ${prepareConfigValue v}") conf 98 ); 99in 100{ 101 options.security.auditd = { 102 enable = lib.mkEnableOption "the Linux Audit daemon"; 103 104 settings = lib.mkOption { 105 type = lib.types.submodule { 106 freeformType = lib.types.attrsOf settingsType; 107 options = { 108 # space_left needs to be larger than admin_space_left, yet they default to be the same if left open. 109 space_left = lib.mkOption { 110 type = lib.types.either lib.types.int (lib.types.strMatching "[0-9]+%"); 111 default = 75; 112 description = '' 113 If the free space in the filesystem containing log_file drops below this value, the audit daemon takes the action specified by 114 {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 115 (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 116 the absolute size in megabytes based on the size of the filesystem containing {option}`log_file`. (E.g., if the filesystem containing 117 {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. 118 119 ::: {.note} 120 This calculation is performed when the audit daemon starts, so if you resize the filesystem containing {option}`log_file` while the 121 audit daemon is running, you should send the audit daemon SIGHUP to re-read the configuration file and recalculate the correct per 122 centage. 123 ::: 124 ''; 125 }; 126 admin_space_left = lib.mkOption { 127 type = lib.types.either lib.types.int (lib.types.strMatching "[0-9]+%"); 128 default = 50; 129 description = '' 130 This is a numeric value in mebibytes (MiB) that tells the audit daemon when to perform a configurable action because the system is running 131 low on disk space. This should be considered the last chance to do something before running out of disk space. The numeric value for 132 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 133 the audit daemon calculate the number based on the disk partition size. 134 ''; 135 }; 136 }; 137 }; 138 139 default = { }; 140 description = "auditd configuration file contents. See {auditd.conf} for supported values."; 141 }; 142 143 plugins = lib.mkOption { 144 type = lib.types.attrsOf pluginOptions; 145 default = { }; 146 defaultText = lib.literalExpression '' 147 { 148 af_unix = { 149 path = lib.getExe' pkgs.audit "audisp-af_unix"; 150 args = [ 151 "0640" 152 "/var/run/audispd_events" 153 "string" 154 ]; 155 format = "binary"; 156 }; 157 remote = { 158 path = lib.getExe' pkgs.audit "audisp-remote"; 159 settings = { }; 160 }; 161 filter = { 162 path = lib.getExe' pkgs.audit "audisp-filter"; 163 args = [ 164 "allowlist" 165 "/etc/audit/audisp-filter.conf" 166 (lib.getExe' pkgs.audit "audisp-syslog") 167 "LOG_USER" 168 "LOG_INFO" 169 "interpret" 170 ]; 171 settings = { }; 172 }; 173 syslog = { 174 path = lib.getExe' pkgs.audit "audisp-syslog"; 175 args = [ "LOG_INFO" ]; 176 }; 177 } 178 ''; 179 description = "Plugin definitions to register with auditd"; 180 }; 181 }; 182 183 config = lib.mkIf cfg.enable { 184 assertions = [ 185 { 186 assertion = 187 let 188 cfg' = cfg.settings; 189 in 190 ( 191 (lib.isInt cfg'.space_left && lib.isInt cfg'.admin_space_left) 192 -> cfg'.space_left > cfg'.admin_space_left 193 ) 194 && ( 195 let 196 get_percent = s: lib.toInt (lib.strings.removeSuffix "%" s); 197 in 198 (lib.isString cfg'.space_left && lib.isString cfg'.admin_space_left) 199 -> (get_percent cfg'.space_left) > (get_percent cfg'.admin_space_left) 200 ); 201 message = "`security.auditd.settings.space_left` must be larger than `security.auditd.settings.admin_space_left`"; 202 } 203 ]; 204 205 # Starting the userspace daemon should also enable audit in the kernel 206 security.audit.enable = lib.mkDefault true; 207 208 # setting this to anything other than /etc/audit/plugins.d will break, so we pin it here 209 security.auditd.settings.plugin_dir = "/etc/audit/plugins.d"; 210 211 environment.etc = { 212 "audit/auditd.conf".text = prepareConfigText cfg.settings; 213 } 214 // (lib.mapAttrs' ( 215 pluginName: pluginDefinitionConfigValue: 216 lib.nameValuePair "audit/plugins.d/${pluginName}.conf" { 217 text = prepareConfigText (lib.removeAttrs pluginDefinitionConfigValue [ "settings" ]); 218 } 219 ) cfg.plugins) 220 // (lib.mapAttrs' ( 221 pluginName: pluginDefinitionConfigValue: 222 lib.nameValuePair "audit/audisp-${pluginName}.conf" { 223 text = prepareConfigText pluginDefinitionConfigValue.settings; 224 } 225 ) (lib.filterAttrs (_: v: v.settings != null) cfg.plugins)); 226 227 security.auditd.plugins = { 228 af_unix = { 229 path = lib.getExe' pkgs.audit "audisp-af_unix"; 230 args = [ 231 "0640" 232 "/var/run/audispd_events" 233 "string" 234 ]; 235 format = "binary"; 236 }; 237 remote = { 238 path = lib.getExe' pkgs.audit "audisp-remote"; 239 settings = { }; 240 }; 241 filter = { 242 path = lib.getExe' pkgs.audit "audisp-filter"; 243 args = [ 244 "allowlist" 245 "/etc/audit/audisp-filter.conf" 246 (lib.getExe' pkgs.audit "audisp-syslog") 247 "LOG_USER" 248 "LOG_INFO" 249 "interpret" 250 ]; 251 settings = { }; 252 }; 253 syslog = { 254 path = lib.getExe' pkgs.audit "audisp-syslog"; 255 args = [ "LOG_INFO" ]; 256 }; 257 }; 258 259 systemd.services.auditd = { 260 description = "Security Audit Logging Service"; 261 documentation = [ "man:auditd(8)" ]; 262 wantedBy = [ "sysinit.target" ]; 263 after = [ 264 "local-fs.target" 265 "systemd-tmpfiles-setup.service" 266 ]; 267 before = [ 268 "sysinit.target" 269 "shutdown.target" 270 ]; 271 conflicts = [ "shutdown.target" ]; 272 273 unitConfig = { 274 DefaultDependencies = false; 275 RefuseManualStop = true; 276 ConditionVirtualization = "!container"; 277 ConditionKernelCommandLine = [ 278 "!audit=0" 279 "!audit=off" 280 ]; 281 }; 282 283 serviceConfig = { 284 LogsDirectory = "audit"; 285 ExecStart = "${pkgs.audit}/bin/auditd -l -n -s nochange"; 286 Restart = "on-failure"; 287 # Do not restart for intentional exits. See EXIT CODES section in auditd(8). 288 RestartPreventExitStatus = "2 4 6"; 289 290 # Upstream hardening settings 291 MemoryDenyWriteExecute = true; 292 LockPersonality = true; 293 RestrictRealtime = true; 294 }; 295 }; 296 }; 297}