dput-ng: fix eval (#364540)
[NixPkgs.git] / nixos / modules / services / security / authelia.nix
blobcd5273bd2163b39a2e1366d251f8fe39e5f53f49
2   lib,
3   pkgs,
4   config,
5   ...
6 }:
8 let
9   cfg = config.services.authelia;
11   format = pkgs.formats.yaml { };
13   autheliaOpts =
14     with lib;
15     { name, ... }:
16     {
17       options = {
18         enable = mkEnableOption "Authelia instance";
20         name = mkOption {
21           type = types.str;
22           default = name;
23           description = ''
24             Name is used as a suffix for the service name, user, and group.
25             By default it takes the value you use for `<instance>` in:
26             {option}`services.authelia.<instance>`
27           '';
28         };
30         package = mkPackageOption pkgs "authelia" { };
32         user = mkOption {
33           default = "authelia-${name}";
34           type = types.str;
35           description = "The name of the user for this authelia instance.";
36         };
38         group = mkOption {
39           default = "authelia-${name}";
40           type = types.str;
41           description = "The name of the group for this authelia instance.";
42         };
44         secrets = mkOption {
45           description = ''
46             It is recommended you keep your secrets separate from the configuration.
47             It's especially important to keep the raw secrets out of your nix configuration,
48             as the values will be preserved in your nix store.
49             This attribute allows you to configure the location of secret files to be loaded at runtime.
51             https://www.authelia.com/configuration/methods/secrets/
52           '';
53           default = { };
54           type = types.submodule {
55             options = {
56               manual = mkOption {
57                 default = false;
58                 example = true;
59                 description = ''
60                   Configuring authelia's secret files via the secrets attribute set
61                   is intended to be convenient and help catch cases where values are required
62                   to run at all.
63                   If a user wants to set these values themselves and bypass the validation they can set this value to true.
64                 '';
65                 type = types.bool;
66               };
68               # required
69               jwtSecretFile = mkOption {
70                 type = types.nullOr types.path;
71                 default = null;
72                 description = ''
73                   Path to your JWT secret used during identity verificaton.
74                 '';
75               };
77               oidcIssuerPrivateKeyFile = mkOption {
78                 type = types.nullOr types.path;
79                 default = null;
80                 description = ''
81                   Path to your private key file used to encrypt OIDC JWTs.
82                 '';
83               };
85               oidcHmacSecretFile = mkOption {
86                 type = types.nullOr types.path;
87                 default = null;
88                 description = ''
89                   Path to your HMAC secret used to sign OIDC JWTs.
90                 '';
91               };
93               sessionSecretFile = mkOption {
94                 type = types.nullOr types.path;
95                 default = null;
96                 description = ''
97                   Path to your session secret. Only used when redis is used as session storage.
98                 '';
99               };
101               # required
102               storageEncryptionKeyFile = mkOption {
103                 type = types.nullOr types.path;
104                 default = null;
105                 description = ''
106                   Path to your storage encryption key.
107                 '';
108               };
109             };
110           };
111         };
113         environmentVariables = mkOption {
114           type = types.attrsOf types.str;
115           description = ''
116             Additional environment variables to provide to authelia.
117             If you are providing secrets please consider the options under {option}`services.authelia.<instance>.secrets`
118             or make sure you use the `_FILE` suffix.
119             If you provide the raw secret rather than the location of a secret file that secret will be preserved in the nix store.
120             For more details: https://www.authelia.com/configuration/methods/secrets/
121           '';
122           default = { };
123         };
125         settings = mkOption {
126           description = ''
127             Your Authelia config.yml as a Nix attribute set.
128             There are several values that are defined and documented in nix such as `default_2fa_method`,
129             but additional items can also be included.
131             https://github.com/authelia/authelia/blob/master/config.template.yml
132           '';
133           default = { };
134           example = ''
135             {
136               theme = "light";
137               default_2fa_method = "totp";
138               log.level = "debug";
139               server.disable_healthcheck = true;
140             }
141           '';
142           type = types.submodule {
143             freeformType = format.type;
144             options = {
145               theme = mkOption {
146                 type = types.enum [
147                   "light"
148                   "dark"
149                   "grey"
150                   "auto"
151                 ];
152                 default = "light";
153                 example = "dark";
154                 description = "The theme to display.";
155               };
157               default_2fa_method = mkOption {
158                 type = types.enum [
159                   ""
160                   "totp"
161                   "webauthn"
162                   "mobile_push"
163                 ];
164                 default = "";
165                 example = "webauthn";
166                 description = ''
167                   Default 2FA method for new users and fallback for preferred but disabled methods.
168                 '';
169               };
171               server = {
172                 address = mkOption {
173                   type = types.str;
174                   default = "tcp://:9091/";
175                   example = "unix:///var/run/authelia.sock?path=authelia&umask=0117";
176                   description = "The address to listen on.";
177                 };
178               };
180               log = {
181                 level = mkOption {
182                   type = types.enum [
183                     "trace"
184                     "debug"
185                     "info"
186                     "warn"
187                     "error"
188                   ];
189                   default = "debug";
190                   example = "info";
191                   description = "Level of verbosity for logs.";
192                 };
194                 format = mkOption {
195                   type = types.enum [
196                     "json"
197                     "text"
198                   ];
199                   default = "json";
200                   example = "text";
201                   description = "Format the logs are written as.";
202                 };
204                 file_path = mkOption {
205                   type = types.nullOr types.path;
206                   default = null;
207                   example = "/var/log/authelia/authelia.log";
208                   description = "File path where the logs will be written. If not set logs are written to stdout.";
209                 };
211                 keep_stdout = mkOption {
212                   type = types.bool;
213                   default = false;
214                   example = true;
215                   description = "Whether to also log to stdout when a `file_path` is defined.";
216                 };
217               };
219               telemetry = {
220                 metrics = {
221                   enabled = mkOption {
222                     type = types.bool;
223                     default = false;
224                     example = true;
225                     description = "Enable Metrics.";
226                   };
228                   address = mkOption {
229                     type = types.str;
230                     default = "tcp://127.0.0.1:9959";
231                     example = "tcp://0.0.0.0:8888";
232                     description = "The address to listen on for metrics. This should be on a different port to the main `server.port` value.";
233                   };
234                 };
235               };
236             };
237           };
238         };
240         settingsFiles = mkOption {
241           type = types.listOf types.path;
242           default = [ ];
243           example = [
244             "/etc/authelia/config.yml"
245             "/etc/authelia/access-control.yml"
246             "/etc/authelia/config/"
247           ];
248           description = ''
249             Here you can provide authelia with configuration files or directories.
250             It is possible to give authelia multiple files and use the nix generated configuration
251             file set via {option}`services.authelia.<instance>.settings`.
252           '';
253         };
254       };
255     };
257   writeOidcJwksConfigFile =
258     oidcIssuerPrivateKeyFile:
259     pkgs.writeText "oidc-jwks.yaml" ''
260       identity_providers:
261         oidc:
262           jwks:
263             - key: {{ secret "${oidcIssuerPrivateKeyFile}" | mindent 10 "|" | msquote }}
264     '';
266   # Remove an attribute in a nested set
267   # https://discourse.nixos.org/t/modify-an-attrset-in-nix/29919/5
268   removeAttrByPath =
269     set: pathList:
270     lib.updateManyAttrsByPath [
271       {
272         path = lib.init pathList;
273         update = old: lib.filterAttrs (n: v: n != (lib.last pathList)) old;
274       }
275     ] set;
278   options.services.authelia.instances =
279     with lib;
280     mkOption {
281       default = { };
282       type = types.attrsOf (types.submodule autheliaOpts);
283       description = ''
284         Multi-domain protection currently requires multiple instances of Authelia.
285         If you don't require multiple instances of Authelia you can define just the one.
287         https://www.authelia.com/roadmap/active/multi-domain-protection/
288       '';
289       example = ''
290         {
291           main = {
292             enable = true;
293             secrets.storageEncryptionKeyFile = "/etc/authelia/storageEncryptionKeyFile";
294             secrets.jwtSecretFile = "/etc/authelia/jwtSecretFile";
295             settings = {
296               theme = "light";
297               default_2fa_method = "totp";
298               log.level = "debug";
299               server.disable_healthcheck = true;
300             };
301           };
302           preprod = {
303             enable = false;
304             secrets.storageEncryptionKeyFile = "/mnt/pre-prod/authelia/storageEncryptionKeyFile";
305             secrets.jwtSecretFile = "/mnt/pre-prod/jwtSecretFile";
306             settings = {
307               theme = "dark";
308               default_2fa_method = "webauthn";
309               server.host = "0.0.0.0";
310             };
311           };
312           test.enable = true;
313           test.secrets.manual = true;
314           test.settings.theme = "grey";
315           test.settings.server.disable_healthcheck = true;
316           test.settingsFiles = [ "/mnt/test/authelia" "/mnt/test-authelia.conf" ];
317           };
318         }
319       '';
320     };
322   config =
323     let
324       mkInstanceServiceConfig =
325         instance:
326         let
327           cleanedSettings =
328             if
329               (
330                 instance.settings.server ? host
331                 || instance.settings.server ? port
332                 || instance.settings.server ? path
333               )
334             then
335               # Old settings are used: display a warning and remove the default value of server.address
336               # as authelia does not allow both old and new settings to be set
337               lib.warn
338                 "Please replace services.authelia.instances.${instance.name}.settings.{host,port,path} with services.authelia.instances.${instance.name}.settings.address, before release 5.0.0"
339                 (
340                   removeAttrByPath instance.settings [
341                     "server"
342                     "address"
343                   ]
344                 )
345             else
346               instance.settings;
348           execCommand = "${instance.package}/bin/authelia";
349           configFile = format.generate "config.yml" cleanedSettings;
350           oidcJwksConfigFile = lib.optional (instance.secrets.oidcIssuerPrivateKeyFile != null) (
351             writeOidcJwksConfigFile instance.secrets.oidcIssuerPrivateKeyFile
352           );
353           configArg = "--config ${
354             builtins.concatStringsSep "," (
355               lib.concatLists [
356                 [ configFile ]
357                 instance.settingsFiles
358                 oidcJwksConfigFile
359               ]
360             )
361           }";
362         in
363         {
364           description = "Authelia authentication and authorization server";
365           wantedBy = [ "multi-user.target" ];
366           after = [ "network-online.target" ]; # Checks SMTP notifier creds during startup
367           wants = [ "network-online.target" ];
368           environment =
369             (lib.filterAttrs (_: v: v != null) {
370               X_AUTHELIA_CONFIG_FILTERS = lib.mkIf (oidcJwksConfigFile != [ ]) "template";
371               AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET_FILE = instance.secrets.jwtSecretFile;
372               AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE = instance.secrets.storageEncryptionKeyFile;
373               AUTHELIA_SESSION_SECRET_FILE = instance.secrets.sessionSecretFile;
374               AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE = instance.secrets.oidcHmacSecretFile;
375             })
376             // instance.environmentVariables;
378           preStart = "${execCommand} ${configArg} validate-config";
379           serviceConfig = {
380             User = instance.user;
381             Group = instance.group;
382             ExecStart = "${execCommand} ${configArg}";
383             Restart = "always";
384             RestartSec = "5s";
385             StateDirectory = "authelia-${instance.name}";
386             StateDirectoryMode = "0700";
388             # Security options:
389             AmbientCapabilities = "";
390             CapabilityBoundingSet = "";
391             DeviceAllow = "";
392             LockPersonality = true;
393             MemoryDenyWriteExecute = true;
394             NoNewPrivileges = true;
396             PrivateTmp = true;
397             PrivateDevices = true;
398             PrivateUsers = true;
400             ProtectClock = true;
401             ProtectControlGroups = true;
402             ProtectHome = "read-only";
403             ProtectHostname = true;
404             ProtectKernelLogs = true;
405             ProtectKernelModules = true;
406             ProtectKernelTunables = true;
407             ProtectProc = "noaccess";
408             ProtectSystem = "strict";
410             RestrictAddressFamilies = [
411               "AF_INET"
412               "AF_INET6"
413               "AF_UNIX"
414             ];
415             RestrictNamespaces = true;
416             RestrictRealtime = true;
417             RestrictSUIDSGID = true;
419             SystemCallArchitectures = "native";
420             SystemCallErrorNumber = "EPERM";
421             SystemCallFilter = [
422               "@system-service"
423               "~@cpu-emulation"
424               "~@debug"
425               "~@keyring"
426               "~@memlock"
427               "~@obsolete"
428               "~@privileged"
429               "~@setuid"
430             ];
431           };
432         };
433       mkInstanceUsersConfig = instance: {
434         groups."authelia-${instance.name}" = lib.mkIf (instance.group == "authelia-${instance.name}") {
435           name = "authelia-${instance.name}";
436         };
437         users."authelia-${instance.name}" = lib.mkIf (instance.user == "authelia-${instance.name}") {
438           name = "authelia-${instance.name}";
439           isSystemUser = true;
440           group = instance.group;
441         };
442       };
443       instances = lib.attrValues cfg.instances;
444     in
445     {
446       assertions = lib.flatten (
447         lib.flip lib.mapAttrsToList cfg.instances (
448           name: instance: [
449             {
450               assertion =
451                 instance.secrets.manual
452                 || (instance.secrets.jwtSecretFile != null && instance.secrets.storageEncryptionKeyFile != null);
453               message = ''
454                 Authelia requires a JWT Secret and a Storage Encryption Key to work.
455                 Either set them like so:
456                 services.authelia.${name}.secrets.jwtSecretFile = /my/path/to/jwtsecret;
457                 services.authelia.${name}.secrets.storageEncryptionKeyFile = /my/path/to/encryptionkey;
458                 Or set services.authelia.${name}.secrets.manual = true and provide them yourself via
459                 environmentVariables or settingsFiles.
460                 Do not include raw secrets in nix settings.
461               '';
462             }
463           ]
464         )
465       );
467       systemd.services = lib.mkMerge (
468         map (
469           instance:
470           lib.mkIf instance.enable {
471             "authelia-${instance.name}" = mkInstanceServiceConfig instance;
472           }
473         ) instances
474       );
475       users = lib.mkMerge (
476         map (instance: lib.mkIf instance.enable (mkInstanceUsersConfig instance)) instances
477       );
478     };