summaryrefslogtreecommitdiffstats
path: root/nixos/modules/security/audit.nix
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"
        ];
      };
    };
  };
}