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