at 25.11-pre 17 kB view raw
1{ 2 lib, 3 pkgs, 4 config, 5 ... 6}: 7 8let 9 cfg = config.services.authelia; 10 11 format = pkgs.formats.yaml { }; 12 13 autheliaOpts = 14 with lib; 15 { name, ... }: 16 { 17 options = { 18 enable = mkEnableOption "Authelia instance"; 19 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 }; 29 30 package = mkPackageOption pkgs "authelia" { }; 31 32 user = mkOption { 33 default = "authelia-${name}"; 34 type = types.str; 35 description = "The name of the user for this authelia instance."; 36 }; 37 38 group = mkOption { 39 default = "authelia-${name}"; 40 type = types.str; 41 description = "The name of the group for this authelia instance."; 42 }; 43 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. 50 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 }; 67 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 }; 76 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 }; 84 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 }; 92 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 }; 100 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 }; 112 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 }; 124 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. 130 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 }; 156 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 }; 170 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 }; 179 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 }; 193 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 }; 203 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 }; 210 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 }; 218 219 telemetry = { 220 metrics = { 221 enabled = mkOption { 222 type = types.bool; 223 default = false; 224 example = true; 225 description = "Enable Metrics."; 226 }; 227 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 }; 239 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 }; 256 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 ''; 265 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; 276in 277{ 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. 286 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 }; 321 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; 347 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; 377 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"; 387 388 # Security options: 389 AmbientCapabilities = ""; 390 CapabilityBoundingSet = ""; 391 DeviceAllow = ""; 392 LockPersonality = true; 393 MemoryDenyWriteExecute = true; 394 NoNewPrivileges = true; 395 396 PrivateTmp = true; 397 PrivateDevices = true; 398 PrivateUsers = true; 399 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"; 409 410 RestrictAddressFamilies = [ 411 "AF_INET" 412 "AF_INET6" 413 "AF_UNIX" 414 ]; 415 RestrictNamespaces = true; 416 RestrictRealtime = true; 417 RestrictSUIDSGID = true; 418 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 ); 466 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 }; 479}