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