summaryrefslogtreecommitdiffstats
path: root/nixos/modules/security/auditd.nix
blob: d971b5db2e86b45ecbdb288ec6e18277267181f6 (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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
{
  config,
  lib,
  pkgs,
  ...
}:
let
  cfg = config.security.auditd;

  settingsType =
    with lib.types;
    nullOr (oneOf [
      bool
      nonEmptyStr
      path
      int
    ]);

  pluginOptions = lib.types.submodule {
    options = {
      active = lib.mkEnableOption "this plugin";
      direction = lib.mkOption {
        type = lib.types.enum [
          "in"
          "out"
        ];
        default = "out";
        description = ''
          The option is dictated by the plugin. In or out are the only choices.
          You cannot make a plugin operate in a way it wasn't  designed just by
          changing this option. This option is to give a clue to the event dispatcher
          about which direction events flow.

          ::: {.note}
          Inbound events are not supported yet.
          :::
        '';
      };
      path = lib.mkOption {
        type = lib.types.path;
        description = "This is the absolute path to the plugin executable.";
      };
      type = lib.mkOption {
        type = lib.types.enum [ "always" ];
        readOnly = true;
        default = "always";
        description = ''
          This tells the dispatcher how the plugin wants to be run. There is only
          one valid option, `always`, which means the plugin is external and should
          always be run. The default is `always` since there are no more builtin plugins.
        '';
      };
      args = lib.mkOption {
        type = lib.types.nullOr (lib.types.listOf lib.types.nonEmptyStr);
        default = null;
        description = ''
          This allows you to pass arguments to the child program.
          Generally plugins do not take arguments and have their own
          config file that instructs them how they should be configured.
        '';
      };
      format = lib.mkOption {
        type = lib.types.enum [
          "binary"
          "string"
        ];
        default = "string";
        description = ''
          Binary passes the data exactly as the audit event dispatcher gets it from
          the audit daemon. The string option tells the dispatcher to completely change
          the event into a string suitable for parsing with the audit parsing library.
        '';
      };
      settings = lib.mkOption {
        type = lib.types.nullOr (
          lib.types.submodule {
            freeformType = lib.types.attrsOf settingsType;
          }
        );
        default = null;
        description = "Plugin-specific config file to link to /etc/audit/<plugin>.conf";
      };
    };
  };

  prepareConfigValue =
    v:
    if lib.isBool v then
      lib.boolToYesNo v
    else if lib.isList v then
      lib.concatStringsSep " " (map prepareConfigValue v)
    else
      toString v;
  prepareConfigText =
    conf:
    lib.concatLines (
      lib.mapAttrsToList (k: v: if v == null then "#${k} =" else "${k} = ${prepareConfigValue v}") conf
    );
in
{
  options.security.auditd = {
    enable = lib.mkEnableOption "the Linux Audit daemon";

    package = lib.mkPackageOption pkgs "auditd" { default = "audit"; };

    settings = lib.mkOption {
      type = lib.types.submodule {
        freeformType = lib.types.attrsOf settingsType;
        options = {
          # space_left needs to be larger than admin_space_left, yet they default to be the same if left open.
          space_left = lib.mkOption {
            type = lib.types.either lib.types.int (lib.types.strMatching "[0-9]+%");
            default = 75;
            description = ''
              If the free space in the filesystem containing log_file drops below this value, the audit daemon takes the action specified by
              {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
              (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
              the absolute size in megabytes based on the size of the filesystem containing {option}`log_file`. (E.g., if the filesystem containing
              {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.

              ::: {.note}
              This calculation is performed when the audit daemon starts, so if you resize the filesystem containing {option}`log_file` while the
              audit daemon is running, you should send the audit daemon SIGHUP to re-read the configuration file and recalculate the correct per‐
              centage.
              :::
            '';
          };
          admin_space_left = lib.mkOption {
            type = lib.types.either lib.types.int (lib.types.strMatching "[0-9]+%");
            default = 50;
            description = ''
              This is a numeric value in mebibytes (MiB) that tells the audit daemon when to perform a configurable action because the system is running
              low on disk space. This should be considered the last chance to do something before running out of disk space. The numeric value for
              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
              the audit daemon calculate the number based on the disk partition size.
            '';
          };
        };
      };

      default = { };
      description = "auditd configuration file contents. See {auditd.conf} for supported values.";
    };

    plugins = lib.mkOption {
      type = lib.types.attrsOf pluginOptions;
      default = { };
      defaultText = lib.literalExpression ''
        {
          af_unix = {
            path = lib.getExe' config.security.auditd.package "audisp-af_unix";
            args = [
              "0640"
              "/run/audit/audispd_events"
              "string"
            ];
          };
          remote = {
            path = lib.getExe' config.security.auditd.package "audisp-remote";
            settings = { };
          };
          filter = {
            path = lib.getExe' config.security.auditd.package "audisp-filter";
            args = [
              "allowlist"
              "/etc/audit/audisp-filter.conf"
              (lib.getExe' config.security.auditd.package "audisp-syslog")
              "LOG_USER"
              "LOG_INFO"
              "interpret"
            ];
            settings = { };
          };
          syslog = {
            path = lib.getExe' config.security.auditd.package "audisp-syslog";
            args = [ "LOG_INFO" ];
          };
        }
      '';
      description = "Plugin definitions to register with auditd";
    };
  };

  config = lib.mkIf cfg.enable {
    assertions = [
      {
        assertion =
          let
            cfg' = cfg.settings;
          in
          (
            (lib.isInt cfg'.space_left && lib.isInt cfg'.admin_space_left)
            -> cfg'.space_left > cfg'.admin_space_left
          )
          && (
            let
              get_percent = s: lib.toInt (lib.strings.removeSuffix "%" s);
            in
            (lib.isString cfg'.space_left && lib.isString cfg'.admin_space_left)
            -> (get_percent cfg'.space_left) > (get_percent cfg'.admin_space_left)
          );
        message = "`security.auditd.settings.space_left` must be larger than `security.auditd.settings.admin_space_left`";
      }
    ];

    # Starting the userspace daemon should also enable audit in the kernel
    security.audit.enable = lib.mkDefault true;

    # setting this to anything other than /etc/audit/plugins.d will break, so we pin it here
    security.auditd.settings.plugin_dir = "/etc/audit/plugins.d";

    environment.etc = {
      "audit/auditd.conf".text = prepareConfigText cfg.settings;
    }
    // (lib.mapAttrs' (
      pluginName: pluginDefinitionConfigValue:
      lib.nameValuePair "audit/plugins.d/${pluginName}.conf" {
        text = prepareConfigText (lib.removeAttrs pluginDefinitionConfigValue [ "settings" ]);
      }
    ) cfg.plugins)
    // (lib.mapAttrs' (
      pluginName: pluginDefinitionConfigValue:
      lib.nameValuePair "audit/audisp-${pluginName}.conf" {
        text = prepareConfigText pluginDefinitionConfigValue.settings;
      }
    ) (lib.filterAttrs (_: v: v.settings != null) cfg.plugins));

    security.auditd.plugins = {
      af_unix = {
        path = lib.getExe' cfg.package "audisp-af_unix";
        args = [
          "0640"
          "/run/audit/audispd_events"
          "string"
        ];
      };
      remote = {
        path = lib.getExe' cfg.package "audisp-remote";
        settings = { };
      };
      filter = {
        path = lib.getExe' cfg.package "audisp-filter";
        args = [
          "allowlist"
          "/etc/audit/audisp-filter.conf"
          (lib.getExe' cfg.package "audisp-syslog")
          "LOG_USER"
          "LOG_INFO"
          "interpret"
        ];
        settings = { };
      };
      syslog = {
        path = lib.getExe' cfg.package "audisp-syslog";
        args = [ "LOG_INFO" ];
      };
    };

    systemd.packages = [ cfg.package.out ];

    systemd.services.auditd = {
      wantedBy = [ "multi-user.target" ];

      serviceConfig = {
        # https://github.com/linux-audit/audit-userspace/pull/501
        # set up audit directories using systemd service instead of tmpfiles
        LogsDirectory = "audit";
        LogsDirectoryMode = "0700";
        RuntimeDirectory = "audit";
        RuntimeDirectoryMode = "0755";
        ExecStart = [
          # the upstream unit does not allow symlinks, so clear and rewrite the ExecStart
          ""
          "${lib.getExe' cfg.package "auditd"} -l -s nochange"
        ];
      };
    };
  };
}