diff options
author | Alexander Foremny <aforemny@posteo.de> | 2024-02-23 08:07:11 +0100 |
---|---|---|
committer | Alexander Foremny <aforemny@posteo.de> | 2024-02-26 04:36:24 +0100 |
commit | 597ec76b7cb1527b1df215548a8f50bddccd8606 (patch) | |
tree | 9ea88686f3b15689e222a1d286a6726f6ce59ace | |
parent | d2873fe0f6a117d7157c2a6f204a864f9edeb668 (diff) |
apps/authelia: init
-rw-r--r-- | apps/authelia/appspec.nix | 33 | ||||
-rw-r--r-- | apps/authelia/integration.nix | 15 | ||||
-rw-r--r-- | apps/authelia/module.nix | 61 | ||||
-rw-r--r-- | apps/authelia/secrets.nix | 5 | ||||
-rw-r--r-- | apps/cgit/appspec.nix | 8 | ||||
-rw-r--r-- | apps/static-users/appspec.nix | 17 | ||||
-rw-r--r-- | apps/static-users/capabilities.nix | 22 | ||||
-rw-r--r-- | apps/static-users/secrets.nix | 7 | ||||
-rw-r--r-- | configs/default.nix | 3 | ||||
-rw-r--r-- | modules/fysiweb-capabilities/default.nix | 17 | ||||
-rw-r--r-- | modules/fysiweb-host-modules/default.nix | 14 | ||||
-rw-r--r-- | modules/fysiweb-secrets/default.nix | 27 | ||||
m--------- | secrets | 0 | ||||
-rw-r--r-- | shell.nix | 1 | ||||
-rw-r--r-- | systems/system1/configuration.nix | 6 |
15 files changed, 217 insertions, 19 deletions
diff --git a/apps/authelia/appspec.nix b/apps/authelia/appspec.nix new file mode 100644 index 0000000..60e2695 --- /dev/null +++ b/apps/authelia/appspec.nix @@ -0,0 +1,33 @@ +{ appConfig, lib, ... }: { + name = "authelia"; + endOfLife = null; + options = { + domain = lib.mkOption { + type = lib.types.str; + }; + users = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule { + options.username = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + }; + options.passwordFile = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + }; + }); + }; + jwtSecret = lib.mkOption { + type = lib.types.str; + default = "system-secrets/${appConfig.appId}/jwtSecret"; + }; + storageEncryptionKey = lib.mkOption { + type = lib.types.str; + default = "system-secrets/${appConfig.appId}/storageEncryptionKey"; + }; + sessionSecret = lib.mkOption { + type = lib.types.str; + default = "system-secrets/${appConfig.appId}/sessionSecret"; + }; + }; +} diff --git a/apps/authelia/integration.nix b/apps/authelia/integration.nix new file mode 100644 index 0000000..a7b71a6 --- /dev/null +++ b/apps/authelia/integration.nix @@ -0,0 +1,15 @@ +{ appConfig, lib, ... }: lib.mkMerge [ + { + port = 9091; + } + { + container.extraFlags = [ + "--load-credential jwtSecret:/etc/nixos/${appConfig.jwtSecret}" + "--load-credential sessionSecret:/etc/nixos/${appConfig.sessionSecret}" + "--load-credential storageEncryptionKey:/etc/nixos/${appConfig.storageEncryptionKey}" + ] ++ (lib.mapAttrsToList + (username: args: + "--load-credential ${args.username}.password:/etc/nixos/${args.passwordFile}") + appConfig.users); + } +] diff --git a/apps/authelia/module.nix b/apps/authelia/module.nix new file mode 100644 index 0000000..fa4d35d --- /dev/null +++ b/apps/authelia/module.nix @@ -0,0 +1,61 @@ +{ appConfig, lib, pkgs, ... }: lib.mkMerge [ + # authelia + { + services.authelia.instances.default.enable = true; + services.authelia.instances.default.settings.access_control.default_policy = "one_factor"; + services.authelia.instances.default.settings.log.format = "text"; + services.authelia.instances.default.settings.log.level = "info"; + services.authelia.instances.default.settings.notifier.filesystem.filename = "/var/lib/authelia-default/notifier.txt"; + services.authelia.instances.default.settings.server.host = "0.0.0.0"; + services.authelia.instances.default.settings.server.port = 9091; + services.authelia.instances.default.settings.session.domain = appConfig.domain; + services.authelia.instances.default.settings.storage.local.path = "/var/lib/authelia-default/storage.sqlite3"; + } + # configure secrets + { + services.authelia.instances.default.secrets.manual = true; + systemd.services.authelia-default.environment.AUTHELIA_JWT_SECRET_FILE = "%d/jwtSecret"; + systemd.services.authelia-default.environment.AUTHELIA_SESSION_SECRET_FILE = "%d/sessionSecret"; + systemd.services.authelia-default.environment.AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE = "%d/storageEncryptionKey"; + systemd.services.authelia-default.serviceConfig.LoadCredential = [ + "jwtSecret:jwtSecret" + "sessionSecret:sessionSecret" + "storageEncryptionKey:storageEncryptionKey" + ]; + } + # configure users + { + services.authelia.instances.default.settings.authentication_backend.file.path = "/var/lib/authelia-default/users.yaml"; + services.authelia.instances.default.settings.authentication_backend.file.watch = true; + + systemd.services.authelia-default-users.before = [ "authelia-default.service" ]; + systemd.services.authelia-default-users.environment.CREDENTIALS_DIRECTORY = "%d"; + systemd.services.authelia-default-users.serviceConfig.Group = "authelia-default"; + systemd.services.authelia-default-users.serviceConfig.LoadCredential = lib.mapAttrsToList (username: attrs: "${username}:${username}.password") appConfig.users; + systemd.services.authelia-default-users.serviceConfig.User = "authelia-default"; + systemd.services.authelia-default-users.wantedBy = [ "authelia-default.service" ]; + + # TODO password is used on command line + # + # @topic apps/authelia + systemd.services.authelia-default-users.script = "${pkgs.writers.writeDashBin "script" '' + set -efu + umask 0177 + PATH=${lib.makeBinPath [ pkgs.authelia pkgs.coreutils pkgs.jq pkgs.json2yaml ]} + users=$( + echo ${lib.escapeShellArg (lib.generators.toJSON {} (lib.attrValues appConfig.users))} | jq -c .[] | while read -r account; do + username=$(echo "$account" | jq -r .username) + passwordFile=$(echo "$account" | jq -r .passwordFile) + hashedPassword=$(authelia crypto hash generate argon2 --password "$(cat "$CREDENTIALS_DIRECTORY"/"$username")" | cut -d' ' -f2-) + jq -cn \ + --arg username "$username" \ + --arg password "$hashedPassword" \ + '{ key: $username, value: { displayname: $username, $password } }' + done | + jq -s from_entries + ) + jq -cn --argjson users "$users" '{ $users }' | + json2yaml > /var/lib/authelia-default/users.yaml + ''}/bin/script"; + } +] diff --git a/apps/authelia/secrets.nix b/apps/authelia/secrets.nix new file mode 100644 index 0000000..1ad5f1a --- /dev/null +++ b/apps/authelia/secrets.nix @@ -0,0 +1,5 @@ +{ appConfig, ... }: [ + { type = "random-string"; path = appConfig.jwtSecret; } + { type = "random-string"; path = appConfig.sessionSecret; } + { type = "random-string"; path = appConfig.storageEncryptionKey; } +] diff --git a/apps/cgit/appspec.nix b/apps/cgit/appspec.nix index 243f477..a7744d5 100644 --- a/apps/cgit/appspec.nix +++ b/apps/cgit/appspec.nix @@ -25,12 +25,12 @@ default = { }; }; users = lib.mkOption { - type = lib.types.nullOr (lib.types.attrsOf (lib.types.submodule { + type = lib.types.attrsOf (lib.types.submodule { options.publicKeyFile = lib.mkOption { - type = lib.types.str; + type = lib.types.nullOr lib.types.str; }; - })); - default = null; + }); + default = { }; }; }; } diff --git a/apps/static-users/appspec.nix b/apps/static-users/appspec.nix index 6ab5c7d..cb55ea7 100644 --- a/apps/static-users/appspec.nix +++ b/apps/static-users/appspec.nix @@ -1,12 +1,21 @@ -{ lib, ... }: { +{ appConfig, lib, ... }: { description = "static-users"; endOfLife = null; options.users = lib.mkOption { - type = lib.types.attrsOf (lib.types.submodule { + type = lib.types.attrsOf (lib.types.submodule ({ name, ... }: { + options.passwordFile = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = "system-secrets/${appConfig.appId}/${appConfig.users.${name}.username}.password"; + }; options.publicKeyFile = lib.mkOption { - type = lib.types.path; + type = lib.types.nullOr lib.types.path; + default = null; + }; + options.username = lib.mkOption { + type = lib.types.str; + default = name; }; - }); + })); default = { }; }; } diff --git a/apps/static-users/capabilities.nix b/apps/static-users/capabilities.nix index de8d1f0..1861888 100644 --- a/apps/static-users/capabilities.nix +++ b/apps/static-users/capabilities.nix @@ -1,8 +1,14 @@ -{ appConfig, lib, ... }: -lib.concatMapAttrs - (name: attrs: lib.optionalAttrs (attrs ? publicKeyFile) { - ${name} = { - inherit (attrs) publicKeyFile; - }; - }) - appConfig.users +{ appConfig, config, lib, ... }: +{ + password-credentials = lib.concatMapAttrs + (name: attrs: lib.optionalAttrs (attrs.passwordFile != null) { + ${name} = { inherit (attrs) username passwordFile; }; + }) + # TODO appConfig should come from config to have been fully evaluated + config.fysiweb-apps.${appConfig.owner}.${appConfig.appName}.${appConfig.appInstanceName}.users; + ssh-credentials = lib.concatMapAttrs + (name: attrs: lib.optionalAttrs (attrs.publicKeyFile != null) { + ${name} = { inherit (attrs) publicKeyFile; }; + }) + appConfig.users; +} diff --git a/apps/static-users/secrets.nix b/apps/static-users/secrets.nix new file mode 100644 index 0000000..ef6f35f --- /dev/null +++ b/apps/static-users/secrets.nix @@ -0,0 +1,7 @@ +{ appConfig, lib, ... }: +lib.mapAttrsToList + (username: _: { + type = "random-string"; + path = "system-secrets/${appConfig.appId}/${username}.password"; + }) + appConfig.users diff --git a/configs/default.nix b/configs/default.nix index c15bd1e..812f365 100644 --- a/configs/default.nix +++ b/configs/default.nix @@ -11,8 +11,11 @@ security.acme.acceptTerms = true; # TODO why do defaults not suffice here? + #security.acme.certs.defaults.email = "aforemny@posteo.de"; #security.acme.certs.defaults.webroot = "/var/lib/acme/acme-challenge"; + security.acme.certs."auth.nomath.org".email = "aforemny@posteo.de"; + security.acme.certs."auth.nomath.org".webroot = "/var/lib/acme/acme-challenge"; security.acme.certs."code.nomath.org".email = "aforemny@posteo.de"; security.acme.certs."code.nomath.org".webroot = "/var/lib/acme/acme-challenge"; security.acme.certs."nomath.org".email = "aforemny@posteo.de"; diff --git a/modules/fysiweb-capabilities/default.nix b/modules/fysiweb-capabilities/default.nix index cbb57eb..bf1937b 100644 --- a/modules/fysiweb-capabilities/default.nix +++ b/modules/fysiweb-capabilities/default.nix @@ -3,6 +3,17 @@ let allApps = lib.concatMap lib.attrValues (lib.concatMap lib.attrValues (lib.attrValues config.fysiweb-apps)); in { + options.fysiweb.capabilities.password-credentials = lib.mkOption { + type = lib.types.attrsOf (lib.types.attrsOf (lib.types.submodule { + options.username = lib.mkOption { + type = lib.types.str; + }; + options.passwordFile = lib.mkOption { + type = lib.types.str; + }; + })); + default = { }; + }; options.fysiweb.capabilities.ssh-credentials = lib.mkOption { type = lib.types.attrsOf (lib.types.attrsOf (lib.types.submodule { options.publicKeyFile = lib.mkOption { @@ -12,12 +23,12 @@ in default = { }; }; config = { - fysiweb.capabilities.ssh-credentials = lib.listToAttrs (lib.concatMap + fysiweb.capabilities = lib.attrsets.mergeAttrsList (lib.concatMap (appConfig: let path = (toString ../../apps) + "/${appConfig.appName}/capabilities.nix"; in lib.optionals (lib.pathIsRegularFile path) [ - (lib.nameValuePair appConfig.appId - (import path { inherit appConfig lib; })) + (lib.mapAttrs (_: value: { ${appConfig.appId} = value; }) + (import path { inherit appConfig config lib; })) ]) allApps); }; diff --git a/modules/fysiweb-host-modules/default.nix b/modules/fysiweb-host-modules/default.nix new file mode 100644 index 0000000..d38ba9f --- /dev/null +++ b/modules/fysiweb-host-modules/default.nix @@ -0,0 +1,14 @@ +{ config, lib, ... }: +let + allApps = lib.concatMap lib.attrValues (lib.concatMap lib.attrValues (lib.attrValues config.fysiweb-apps)); +in +{ + config = lib.mkMerge (map + (appConfig: + let path = (toString ../../apps) + "/${appConfig.appName}/host-module.nix"; in + lib.optionalAttr (lib.pathIsRegularFile path) { } + #(import path { }) + ) + #allApps); + [ ]); +} diff --git a/modules/fysiweb-secrets/default.nix b/modules/fysiweb-secrets/default.nix new file mode 100644 index 0000000..e494dde --- /dev/null +++ b/modules/fysiweb-secrets/default.nix @@ -0,0 +1,27 @@ +{ config, lib, ... }: +let + allApps = lib.concatMap lib.attrValues (lib.concatMap lib.attrValues (lib.attrValues config.fysiweb-apps)); +in +{ + options.fysiweb.secrets = lib.mkOption { + type = lib.types.listOf (lib.types.submodule { + options = { + type = lib.mkOption { + type = lib.types.enum [ "random-string" ]; + }; + path = lib.mkOption { + type = lib.types.str; + }; + }; + }); + default = [ ]; + }; + config = { + fysiweb.secrets = lib.concatMap + (appConfig: + let path = (toString ../../apps) + "/${appConfig.appName}/secrets.nix"; in + lib.optionals (lib.pathIsRegularFile path) + (import path { inherit appConfig lib; })) + allApps; + }; +} diff --git a/secrets b/secrets -Subproject 3609101b200901c55227a453ee36bbdfde9a708 +Subproject 1efddacabaf31e5fcb0db4f2797224dc6a9e976 @@ -17,6 +17,7 @@ let inherit (pkgs) lib; in pkgs.mkShell { buildInputs = [ + pkgs.authelia pkgs.fysiweb-cli pkgs.git pkgs.niv diff --git a/systems/system1/configuration.nix b/systems/system1/configuration.nix index dc9422d..7f42b1a 100644 --- a/systems/system1/configuration.nix +++ b/systems/system1/configuration.nix @@ -8,6 +8,7 @@ # TODO auto-load modules ../../modules/abuilder ../../modules/fysiweb-capabilities + ../../modules/fysiweb-secrets ]; config = lib.mkMerge [ @@ -19,6 +20,11 @@ fysiweb-apps.public.static-users.public.users.aforemny.publicKeyFile = toString ../../public + "/aforemny.id_rsa.pub"; fysiweb-apps.public.static-users.public.users.kirchner.publicKeyFile = toString ../../public + "/kirchner.id_rsa.pub"; } + # enable authelia + { + fysiweb-apps.public.authelia.public.domain = "auth.nomath.org"; + fysiweb-apps.public.authelia.public.users = config.fysiweb.capabilities.password-credentials.public-static-users-public; + } # enable static website "nomath.org" { fysiweb-apps.public.static-website."nomath-org".domain = "nomath.org"; |