diff --git a/auth/auth-module.nix b/auth/auth-module.nix index 005ece9..24cc877 100644 --- a/auth/auth-module.nix +++ b/auth/auth-module.nix @@ -11,105 +11,68 @@ let ; auth-passthru = config.selfprivacy.passthru.auth; keys-path = auth-passthru.keys-path; - # generate OAuth2 client secret - mkKanidmExecStartPreScript = - oauthClientID: linuxGroup: + + mkKanidmTokenCreationSnippet = + { + clientID, + linuxGroupOfClient, + isMailserver, + ... + }: let - secretFP = auth-passthru.mkOAuth2ClientSecretFP linuxGroup; + kanidmServiceAccountName = "sp.${clientID}.service-account"; + kanidmServiceAccountTokenName = "${clientID}-service-account-token"; + kanidmServiceAccountTokenFP = auth-passthru.mkServiceAccountTokenFP linuxGroupOfClient; + isRW = clientID == "selfprivacy-api"; in - pkgs.writeShellScript "${oauthClientID}-kanidm-ExecStartPre-script.sh" '' - set -o pipefail - set -o errexit - if ! [ -f "${secretFP}" ] + '' + # try to get existing Kanidm service account + KANIDM_SERVICE_ACCOUNT="$($KANIDM service-account list --name idm_admin | grep -E "^name: ${kanidmServiceAccountName}$")" + echo KANIDM_SERVICE_ACCOUNT: "$KANIDM_SERVICE_ACCOUNT" + if [ -n "$KANIDM_SERVICE_ACCOUNT" ] then - "${lib.getExe pkgs.openssl}" rand -base64 32 \ - | tr "\n:@/+=" "012345" > "${secretFP}" - chmod 640 "${secretFP}" + echo "kanidm service account \"${kanidmServiceAccountName}\" is found" + else + echo "kanidm service account \"${kanidmServiceAccountName}\" is not found" + echo "creating new kanidm service account \"${kanidmServiceAccountName}\"" + if $KANIDM service-account create --name idm_admin "${kanidmServiceAccountName}" "${kanidmServiceAccountName}" idm_admin + then + echo "kanidm service account \"${kanidmServiceAccountName}\" created" + else + echo "error: cannot create kanidm service account \"${kanidmServiceAccountName}\"" + exit 1 + fi fi + + # create a new token for kanidm + if ! KANIDM_SERVICE_ACCOUNT_TOKEN_JSON="$($KANIDM service-account api-token generate --name idm_admin "${kanidmServiceAccountName}" "${kanidmServiceAccountTokenName}" ${lib.strings.optionalString isRW "--rw"} --output json)" + then + echo "error: kanidm CLI returns an error when trying to generate service-account api-token" + exit 1 + fi + if ! KANIDM_SERVICE_ACCOUNT_TOKEN="$(echo "$KANIDM_SERVICE_ACCOUNT_TOKEN_JSON" | ${lib.getExe pkgs.jq} -r .result)" + then + echo "error: cannot get service-account API token from JSON" + exit 1 + fi + + if ! install --mode=640 \ + <(printf "%s" "$KANIDM_SERVICE_ACCOUNT_TOKEN") \ + ${kanidmServiceAccountTokenFP} + then + echo "error: cannot write token to \"${kanidmServiceAccountTokenFP}\"" + exit 1 + fi + + '' + + lib.strings.optionalString isMailserver '' + # add Kanidm service account to `idm_mail_servers` group + $KANIDM group add-members idm_mail_servers "${kanidmServiceAccountName}" + '' + + lib.strings.optionalString isRW '' + $KANIDM group add-members idm_admins "${kanidmServiceAccountName}" ''; - mkKanidmExecStartPostScript = - oauthClientID: linuxGroup: isMailserver: - let - kanidmServiceAccountName = "sp.${oauthClientID}.service-account"; - kanidmServiceAccountTokenName = "${oauthClientID}-service-account-token"; - kanidmServiceAccountTokenFP = auth-passthru.mkServiceAccountTokenFP linuxGroup; - isRW = oauthClientID == "selfprivacy-api"; - # TODO: Copied from Forgejo module. Maybe generalize as lib. function? - waitForURL = url: maxRetries: delaySec: '' - for ((i=1; i<=${toString maxRetries}; i++)) - do - if ${lib.getExe pkgs.curl} -X GET --silent --fail "${url}" > /dev/null - then - echo "${url} responds to GET HTTP request (attempt #$i)" - break - else - echo "${url} does not respond to GET HTTP request (attempt #$i)" - echo sleeping for ${toString delaySec} seconds - fi - sleep ${toString delaySec} - done - if [[ "$i" -gt "${toString maxRetries}" ]] - then - echo "error, max attempts to access "${url}" have been used unsuccessfully!" - exit 124 - fi - ''; - in - pkgs.writeShellScript "${oauthClientID}-kanidm-ExecStartPost-script.sh" ( - '' - export HOME=$RUNTIME_DIRECTORY/client_home - readonly KANIDM="${config.services.kanidm.package}/bin/kanidm" - - ${waitForURL config.services.kanidm.serverSettings.origin 10 10} - - # try to get existing Kanidm service account - KANIDM_SERVICE_ACCOUNT="$($KANIDM service-account list --name idm_admin | grep -E "^name: ${kanidmServiceAccountName}$")" - echo KANIDM_SERVICE_ACCOUNT: "$KANIDM_SERVICE_ACCOUNT" - if [ -n "$KANIDM_SERVICE_ACCOUNT" ] - then - echo "kanidm service account \"${kanidmServiceAccountName}\" is found" - else - echo "kanidm service account \"${kanidmServiceAccountName}\" is not found" - echo "creating new kanidm service account \"${kanidmServiceAccountName}\"" - if $KANIDM service-account create --name idm_admin "${kanidmServiceAccountName}" "${kanidmServiceAccountName}" idm_admin - then - echo "kanidm service account \"${kanidmServiceAccountName}\" created" - else - echo "error: cannot create kanidm service account \"${kanidmServiceAccountName}\"" - exit 1 - fi - fi - - # create a new token for kanidm - if ! KANIDM_SERVICE_ACCOUNT_TOKEN_JSON="$($KANIDM service-account api-token generate --name idm_admin "${kanidmServiceAccountName}" "${kanidmServiceAccountTokenName}" ${lib.strings.optionalString isRW "--rw"} --output json)" - then - echo "error: kanidm CLI returns an error when trying to generate service-account api-token" - exit 1 - fi - if ! KANIDM_SERVICE_ACCOUNT_TOKEN="$(echo "$KANIDM_SERVICE_ACCOUNT_TOKEN_JSON" | ${lib.getExe pkgs.jq} -r .result)" - then - echo "error: cannot get service-account API token from JSON" - exit 1 - fi - - if ! install --mode=640 \ - <(printf "%s" "$KANIDM_SERVICE_ACCOUNT_TOKEN") \ - ${kanidmServiceAccountTokenFP} - then - echo "error: cannot write token to \"${kanidmServiceAccountTokenFP}\"" - exit 1 - fi - - '' - + lib.strings.optionalString isMailserver '' - # add Kanidm service account to `idm_mail_servers` group - $KANIDM group add-members idm_mail_servers "${kanidmServiceAccountName}" - '' - + lib.strings.optionalString isRW '' - $KANIDM group add-members idm_admins "${kanidmServiceAccountName}" - '' - ); in { options.selfprivacy.auth = { @@ -300,28 +263,64 @@ in # for each OAuth2 client: scripts with Kanidm CLI commands systemd.services.kanidm = { before = lib.lists.concatMap ({ clientSystemdUnits, ... }: clientSystemdUnits) clientsAttrsList; - serviceConfig = lib.mkMerge ( - lib.forEach clientsAttrsList ( - { - clientID, - isTokenNeeded, - linuxGroupOfClient, - isMailserver, - ... - }: - { - ExecStartPre = [ - # "-" prefix means to ignore exit code of prefixed script - ("-" + mkKanidmExecStartPreScript clientID linuxGroupOfClient) - ]; - ExecStartPost = lib.mkIf isTokenNeeded ( - lib.mkAfter [ - ("-" + mkKanidmExecStartPostScript clientID linuxGroupOfClient isMailserver) - ] - ); - } - ) - ); + serviceConfig = { + ExecStartPre = [ + (pkgs.writeShellScript "create-kanidm-client-secrets" '' + set -euo pipefail + ${lib.concatLines ( + map ( + { linuxGroupOfClient, ... }: + let + secretPath = auth-passthru.mkOAuth2ClientSecretFP linuxGroupOfClient; + in + '' + if ! [ -f "${secretPath}" ] + then + "${lib.getExe pkgs.openssl}" rand -base64 32 \ + | tr "\n:@/+=" "012345" > "${secretPath}" + chmod 640 "${secretPath}" + fi + '' + ) clientsAttrsList + )} + '') + ]; + ExecStartPost = lib.mkOrder 1300 [ + (pkgs.writeShellScript "create-kanidm-tokens" '' + set -euo pipefail + + readonly KANIDM="${config.services.kanidm.package}/bin/kanidm" + readonly CLIENT_HOME=$RUNTIME_DIRECTORY/client_home + mkdir -p "$CLIENT_HOME" + export HOME="$CLIENT_HOME" + export KANIDM_NAME=idm_admin + export KANIDM_URL="${config.services.kanidm.provision.instanceUrl}" + export KANIDM_SKIP_HOSTNAME_VERIFICATION="true" + + if ! recover_out=$(${config.services.kanidm.package}/bin/kanidmd recover-account -c ${ + config.environment.etc."kanidm/server.toml".source + } idm_admin -o json); then + echo "$recover_out" >&2 + echo "kanidm provision: Failed to recover admin account" >&2 + exit 1 + fi + if ! KANIDM_IDM_ADMIN_PASSWORD=$(grep '{"password' <<< "$recover_out" | ${lib.getExe pkgs.jq} -r .password); then + echo "$recover_out" >&2 + echo "kanidm provision: Failed to parse password for idm_admin account" >&2 + exit 1 + fi + + KANIDM_PASSWORD="$KANIDM_IDM_ADMIN_PASSWORD" $KANIDM login + + # disable anonymous account because it allows to freely iterate over all users on kanidm instance. + $KANIDM service-account validity expire-at anonymous epoch + + ${lib.concatLines ( + lib.map mkKanidmTokenCreationSnippet (lib.filter (x: x.isTokenNeeded) clientsAttrsList) + )} + '') + ]; + }; }; # for each OAuth2 client: Kanidm provisioning options @@ -344,13 +343,23 @@ in ... }: { - groups = lib.mkIf (clientID != "selfprivacy-api") ({ - "${usersGroup}".members = [ - auth-passthru.full-users-group - ] ++ lib.optional adminsGroupDefined adminsGroup; - } // lib.optionalAttrs adminsGroupDefined { - "${adminsGroup}".members = [ auth-passthru.admins-group ]; - }); + groups = lib.mkIf (clientID != "selfprivacy-api") ( + { + "${usersGroup}" = { + members = [ + auth-passthru.full-users-group + ] + ++ lib.optional adminsGroupDefined adminsGroup; + overwriteMembers = false; # allow our api to modify group imperatively + }; + } + // lib.optionalAttrs adminsGroupDefined { + "${adminsGroup}" = { + members = [ auth-passthru.admins-group ]; + overwriteMembers = false; + }; + } + ); systems.oauth2.${clientID} = { inherit basicSecretFile diff --git a/auth/auth.nix b/auth/auth.nix index e0ccaf5..21fc3b1 100644 --- a/auth/auth.nix +++ b/auth/auth.nix @@ -89,8 +89,21 @@ lib.mkIf config.selfprivacy.sso.enable { provision = { enable = true; autoRemove = true; # if false, obsolete oauth2 scopeMaps remain - groups.${admins-group}.present = true; - groups.${full-users-group}.present = true; + groups.${admins-group} = { + present = true; + overwriteMembers = false; + }; + groups.${full-users-group} = { + present = true; + members = [ + admins-group # admins are full users too. + ]; + overwriteMembers = false; + }; + groups.idm_all_persons = { + present = true; + overwriteMembers = false; + }; }; enableClient = true; clientSettings = { @@ -156,11 +169,29 @@ lib.mkIf config.selfprivacy.sso.enable { }; }; - systemd.services.kanidm.serviceConfig.ExecStartPre = - # idempotent script to run on each startup only for kanidm v1.5.0 - lib.mkIf (lib.versionAtLeast config.services.kanidm.package.version "1.5.0") ( - lib.mkBefore [ kanidmMigrateDbScript ] - ); + systemd.services.kanidm.serviceConfig = { + BindPaths = [ + keys-path + ]; + # mkForce is used there to overwrite paths to secrets provisioning will use because those are created in ExecStartPre and systemd sandbox breaks. + BindReadOnlyPaths = lib.mkForce [ + "/nix/store" + "/run/systemd/notify" # For healthcheck notifications + "-/etc/resolv.conf" + "-/etc/nsswitch.conf" + "-/etc/hosts" + "-/etc/localtime" + "-/etc/passwd" + "-/etc/group" + config.services.kanidm.serverSettings.tls_chain + config.services.kanidm.serverSettings.tls_key + ]; + ExecStartPre = + # idempotent script to run on each startup only for kanidm v1.5.0 + lib.mkIf (lib.versionAtLeast config.services.kanidm.package.version "1.5.0") ( + lib.mkBefore [ kanidmMigrateDbScript ] + ); + }; selfprivacy.passthru.auth = { inherit diff --git a/auth/kanidm.nix b/auth/kanidm.nix deleted file mode 100644 index 6d6839f..0000000 --- a/auth/kanidm.nix +++ /dev/null @@ -1,1052 +0,0 @@ -{ - config, - lib, - options, - pkgs, - ... -}: -let - inherit (lib) - any - attrNames - attrValues - concatLines - concatLists - converge - filter - filterAttrs - filterAttrsRecursive - flip - foldl' - getExe - hasInfix - hasPrefix - isStorePath - last - mapAttrsToList - mkEnableOption - mkForce - mkIf - mkMerge - mkOption - mkPackageOption - optional - optionalString - splitString - subtractLists - types - unique - ; - - cfg = config.services.kanidm; - settingsFormat = pkgs.formats.toml { }; - # Remove null values, so we can document optional values that don't end up in the generated TOML file. - filterConfig = converge ( - a: filterAttrsRecursive (_: v: v != null) (builtins.removeAttrs a [ "provision" ]) - ); - serverConfigFile = settingsFormat.generate "server.toml" (filterConfig cfg.serverSettings); - clientConfigFile = settingsFormat.generate "kanidm-config.toml" (filterConfig cfg.clientSettings); - unixConfigFile = settingsFormat.generate "kanidm-unixd.toml" (filterConfig cfg.unixSettings); - certPaths = builtins.map builtins.dirOf [ - cfg.serverSettings.tls_chain - cfg.serverSettings.tls_key - ]; - - # Merge bind mount paths and remove paths where a prefix is already mounted. - # This makes sure that if e.g. the tls_chain is in the nix store and /nix/store is already in the mount - # paths, no new bind mount is added. Adding subpaths caused problems on ofborg. - hasPrefixInList = - list: newPath: any (path: hasPrefix (builtins.toString path) (builtins.toString newPath)) list; - mergePaths = foldl' ( - merged: newPath: - let - # If the new path is a prefix to some existing path, we need to filter it out - filteredPaths = filter (p: !hasPrefix (builtins.toString newPath) (builtins.toString p)) merged; - # If a prefix of the new path is already in the list, do not add it - filteredNew = optional (!hasPrefixInList filteredPaths newPath) newPath; - in - filteredPaths ++ filteredNew - ) [ ]; - - defaultServiceConfig = { - # Setting the type to notify enables additional healthchecks, ensuring units - # after and requiring kanidm-* wait for it to complete startup - Type = "notify"; - BindReadOnlyPaths = [ - "/nix/store" - # For healthcheck notifications - "/run/systemd/notify" - "-/etc/resolv.conf" - "-/etc/nsswitch.conf" - "-/etc/hosts" - "-/etc/localtime" - ]; - CapabilityBoundingSet = [ ]; - # ProtectClock= adds DeviceAllow=char-rtc r - DeviceAllow = ""; - # Implies ProtectSystem=strict, which re-mounts all paths - # DynamicUser = true; - LockPersonality = true; - MemoryDenyWriteExecute = true; - NoNewPrivileges = true; - PrivateDevices = true; - PrivateMounts = true; - PrivateNetwork = true; - PrivateTmp = true; - PrivateUsers = true; - ProcSubset = "pid"; - ProtectClock = true; - ProtectHome = true; - ProtectHostname = true; - # Would re-mount paths ignored by temporary root - #ProtectSystem = "strict"; - ProtectControlGroups = true; - ProtectKernelLogs = true; - ProtectKernelModules = true; - ProtectKernelTunables = true; - ProtectProc = "invisible"; - RestrictAddressFamilies = [ ]; - RestrictNamespaces = true; - RestrictRealtime = true; - RestrictSUIDSGID = true; - SystemCallArchitectures = "native"; - SystemCallFilter = [ - "@system-service" - "~@privileged @resources @setuid @keyring" - ]; - # Does not work well with the temporary root - #UMask = "0066"; - }; - - mkPresentOption = - what: - mkOption { - description = "Whether to ensure that this ${what} is present or absent."; - type = types.bool; - default = true; - }; - - filterPresent = filterAttrs (_: v: v.present); - - selfprivacy-admin-groups-regex = "^sp\.([[:alnum:]]+\.|)admins$"; - is-selfprivacy-admin-group = - name: !builtins.isNull (builtins.match selfprivacy-admin-groups-regex name); - - isGroupNonOverwritable = - g: - false - || !g ? members - || g ? members && g.members == [ ] - || g ? members && builtins.any is-selfprivacy-admin-group g.members; - - provisionStateJson = pkgs.writeText "provision-state.json" ( - builtins.toJSON { - inherit (cfg.provision) persons systems; - groups = lib.attrsets.filterAttrs (_n: v: !isGroupNonOverwritable v) cfg.provision.groups; - } - ); - - # Only recover the admin account if a password should explicitly be provisioned - # for the account. Otherwise it is not needed for provisioning. - maybeRecoverAdmin = optionalString (cfg.provision.adminPasswordFile != null) '' - KANIDM_ADMIN_PASSWORD=$(< ${cfg.provision.adminPasswordFile}) - # We always reset the admin account password if a desired password was specified. - if ! KANIDM_RECOVER_ACCOUNT_PASSWORD=$KANIDM_ADMIN_PASSWORD ${cfg.package}/bin/kanidmd recover-account -c ${serverConfigFile} admin --from-environment >/dev/null; then - echo "Failed to recover admin account" >&2 - exit 1 - fi - ''; - - # Recover the idm_admin account. If a password should explicitly be provisioned - # for the account we set it, otherwise we generate a new one because it is required - # for provisioning. - recoverIdmAdmin = - if cfg.provision.idmAdminPasswordFile != null then - '' - KANIDM_IDM_ADMIN_PASSWORD=$(< ${cfg.provision.idmAdminPasswordFile}) - # We always reset the idm_admin account password if a desired password was specified. - if ! KANIDM_RECOVER_ACCOUNT_PASSWORD=$KANIDM_IDM_ADMIN_PASSWORD ${cfg.package}/bin/kanidmd recover-account -c ${serverConfigFile} idm_admin --from-environment >/dev/null; then - echo "Failed to recover idm_admin account" >&2 - exit 1 - fi - '' - else - '' - # Recover idm_admin account - if ! recover_out=$(${cfg.package}/bin/kanidmd recover-account -c ${serverConfigFile} idm_admin -o json); then - echo "$recover_out" >&2 - echo "kanidm provision: Failed to recover admin account" >&2 - exit 1 - fi - if ! KANIDM_IDM_ADMIN_PASSWORD=$(grep '{"password' <<< "$recover_out" | ${getExe pkgs.jq} -r .password); then - echo "$recover_out" >&2 - echo "kanidm provision: Failed to parse password for idm_admin account" >&2 - exit 1 - fi - ''; - - groupsToCreateAndPopulate = lib.attrsets.filterAttrs ( - _n: isGroupNonOverwritable - ) cfg.provision.groups; - - createGroups = '' - for group_name in ${lib.strings.concatStringsSep " " (builtins.attrNames groupsToCreateAndPopulate)} - do - ${cfg.package}/bin/kanidm group create "$group_name" - done - ''; - - populateGroup = group_name: group: '' - for member_name in ${lib.strings.concatStringsSep " " group.members} - do - ${cfg.package}/bin/kanidm group add-members "${group_name}" "$member_name" - done - ''; - - createAndPopulateGroups = lib.concatLines ( - [ createGroups ] ++ (lib.mapAttrsToList populateGroup groupsToCreateAndPopulate) - ); - - postStartScript = pkgs.writeShellScript "post-start" '' - set -euo pipefail - - # Wait for the kanidm server to come online - count=0 - while ! ${getExe pkgs.curl} -L --silent --max-time 1 --connect-timeout 1 --fail \ - ${optionalString cfg.provision.acceptInvalidCerts "--insecure"} \ - ${cfg.provision.instanceUrl} >/dev/null - do - sleep 1 - if [[ "$count" -eq 30 ]]; then - echo "Tried for at least 30 seconds, giving up..." - exit 1 - fi - count=$((count++)) - done - - ${recoverIdmAdmin} - ${maybeRecoverAdmin} - - readonly CLIENT_HOME=$RUNTIME_DIRECTORY/client_home - mkdir -p "$CLIENT_HOME" - export HOME="$CLIENT_HOME" - export KANIDM_NAME=idm_admin - export KANIDM_URL="${cfg.provision.instanceUrl}" - export KANIDM_SKIP_HOSTNAME_VERIFICATION="true" - KANIDM_PASSWORD="$KANIDM_IDM_ADMIN_PASSWORD" ${cfg.package}/bin/kanidm login - - # disable anonymous account because it allows to freely iterate over all users on kanidm instance. - ${cfg.package}/bin/kanidm service-account validity expire-at anonymous epoch - - ${createAndPopulateGroups} - - unset HOME - unset KANIDM_NAME - unset KANIDM_URL - unset KANIDM_SKIP_HOSTNAME_VERIFICATION - - KANIDM_PROVISION_IDM_ADMIN_TOKEN=$KANIDM_IDM_ADMIN_PASSWORD \ - ${getExe pkgs.kanidm-provision} \ - ${optionalString (!cfg.provision.autoRemove) "--no-auto-remove"} \ - ${optionalString cfg.provision.acceptInvalidCerts "--accept-invalid-certs"} \ - --url "${cfg.provision.instanceUrl}" \ - --state ${provisionStateJson} - ''; - - serverPort = - # ipv6: - if hasInfix "]:" cfg.serverSettings.bindaddress then - last (splitString "]:" cfg.serverSettings.bindaddress) - else - # ipv4: - if hasInfix "." cfg.serverSettings.bindaddress then - last (splitString ":" cfg.serverSettings.bindaddress) - # default is 8443 - else - "8443"; -in -{ - options.services.kanidm = { - enableClient = mkEnableOption "the Kanidm client"; - enableServer = mkEnableOption "the Kanidm server"; - enablePam = mkEnableOption "the Kanidm PAM and NSS integration"; - - package = mkPackageOption pkgs "kanidm" { }; - - serverSettings = mkOption { - type = types.submodule { - freeformType = settingsFormat.type; - - options = { - bindaddress = mkOption { - description = "Address/port combination the webserver binds to."; - example = "[::1]:8443"; - type = types.str; - }; - # Should be optional but toml does not accept null - ldapbindaddress = mkOption { - description = '' - Address and port the LDAP server is bound to. Setting this to `null` disables the LDAP interface. - ''; - example = "[::1]:636"; - default = null; - type = types.nullOr types.str; - }; - origin = mkOption { - description = "The origin of your Kanidm instance. Must have https as protocol."; - example = "https://idm.example.org"; - type = types.strMatching "^https://.*"; - }; - domain = mkOption { - description = '' - The `domain` that Kanidm manages. Must be below or equal to the domain - specified in `serverSettings.origin`. - This can be left at `null`, only if your instance has the role `ReadOnlyReplica`. - While it is possible to change the domain later on, it requires extra steps! - Please consider the warnings and execute the steps described - [in the documentation](https://kanidm.github.io/kanidm/stable/administrivia.html#rename-the-domain). - ''; - example = "example.org"; - default = null; - type = types.nullOr types.str; - }; - db_path = mkOption { - description = "Path to Kanidm database."; - default = "/var/lib/kanidm/kanidm.db"; - readOnly = true; - type = types.path; - }; - tls_chain = mkOption { - description = "TLS chain in pem format."; - type = types.path; - }; - tls_key = mkOption { - description = "TLS key in pem format."; - type = types.path; - }; - log_level = mkOption { - description = "Log level of the server."; - default = "info"; - type = types.enum [ - "info" - "debug" - "trace" - ]; - }; - role = mkOption { - description = "The role of this server. This affects the replication relationship and thereby available features."; - default = "WriteReplica"; - type = types.enum [ - "WriteReplica" - "WriteReplicaNoUI" - "ReadOnlyReplica" - ]; - }; - online_backup = { - path = mkOption { - description = "Path to the output directory for backups."; - type = types.path; - default = "/var/lib/kanidm/backups"; - }; - schedule = mkOption { - description = "The schedule for backups in cron format."; - type = types.str; - default = "00 22 * * *"; - }; - versions = mkOption { - description = '' - Number of backups to keep. - - The default is set to `0`, in order to disable backups by default. - ''; - type = types.ints.unsigned; - default = 0; - example = 7; - }; - }; - }; - }; - default = { }; - description = '' - Settings for Kanidm, see - [the documentation](https://kanidm.github.io/kanidm/stable/server_configuration.html) - and [example configuration](https://github.com/kanidm/kanidm/blob/master/examples/server.toml) - for possible values. - ''; - }; - - clientSettings = mkOption { - type = types.submodule { - freeformType = settingsFormat.type; - - options.uri = mkOption { - description = "Address of the Kanidm server."; - example = "http://127.0.0.1:8080"; - type = types.str; - }; - }; - description = '' - Configure Kanidm clients, needed for the PAM daemon. See - [the documentation](https://kanidm.github.io/kanidm/stable/client_tools.html#kanidm-configuration) - and [example configuration](https://github.com/kanidm/kanidm/blob/master/examples/config) - for possible values. - ''; - }; - - unixSettings = mkOption { - type = types.submodule { - freeformType = settingsFormat.type; - - options = { - pam_allowed_login_groups = mkOption { - description = "Kanidm groups that are allowed to login using PAM."; - example = "my_pam_group"; - type = types.listOf types.str; - }; - hsm_pin_path = mkOption { - description = "Path to a HSM pin."; - default = "/var/cache/kanidm-unixd/hsm-pin"; - type = types.path; - }; - }; - }; - description = '' - Configure Kanidm unix daemon. - See [the documentation](https://kanidm.github.io/kanidm/stable/integrations/pam_and_nsswitch.html#the-unix-daemon) - and [example configuration](https://github.com/kanidm/kanidm/blob/master/examples/unixd) - for possible values. - ''; - }; - - provision = { - enable = mkEnableOption "provisioning of groups, users and oauth2 resource servers"; - - instanceUrl = mkOption { - description = "The instance url to which the provisioning tool should connect."; - default = "https://localhost:${serverPort}"; - defaultText = ''"https://localhost:"''; - type = types.str; - }; - - acceptInvalidCerts = mkOption { - description = '' - Whether to allow invalid certificates when provisioning the target instance. - By default this is only allowed when the instanceUrl is localhost. This is - dangerous when used with an external URL. - ''; - type = types.bool; - default = hasPrefix "https://localhost:" cfg.provision.instanceUrl; - defaultText = ''hasPrefix "https://localhost:" cfg.provision.instanceUrl''; - }; - - adminPasswordFile = mkOption { - description = "Path to a file containing the admin password for kanidm. Do NOT use a file from the nix store here!"; - example = "/run/secrets/kanidm-admin-password"; - default = null; - type = types.nullOr types.path; - }; - - idmAdminPasswordFile = mkOption { - description = '' - Path to a file containing the idm admin password for kanidm. Do NOT use a file from the nix store here! - If this is not given but provisioning is enabled, the idm_admin password will be reset on each restart. - ''; - example = "/run/secrets/kanidm-idm-admin-password"; - default = null; - type = types.nullOr types.path; - }; - - autoRemove = mkOption { - description = '' - Determines whether deleting an entity in this provisioning config should automatically - cause them to be removed from kanidm, too. This works because the provisioning tool tracks - all entities it has ever created. If this is set to false, you need to explicitly specify - `present = false` to delete an entity. - ''; - type = types.bool; - default = true; - }; - - groups = mkOption { - description = "Provisioning of kanidm groups"; - default = { }; - type = types.attrsOf ( - types.submodule (groupSubmod: { - options = { - present = mkPresentOption "group"; - - members = mkOption { - description = "List of kanidm entities (persons, groups, ...) which are part of this group."; - type = types.listOf types.str; - apply = unique; - default = [ ]; - }; - }; - config.members = concatLists ( - flip mapAttrsToList cfg.provision.persons ( - person: personCfg: - optional ( - personCfg.present && builtins.elem groupSubmod.config._module.args.name personCfg.groups - ) person - ) - ); - }) - ); - }; - - persons = mkOption { - description = "Provisioning of kanidm persons"; - default = { }; - type = types.attrsOf ( - types.submodule { - options = { - present = mkPresentOption "person"; - - displayName = mkOption { - description = "Display name"; - type = types.str; - example = "My User"; - }; - - legalName = mkOption { - description = "Full legal name"; - type = types.nullOr types.str; - example = "Jane Doe"; - default = null; - }; - - mailAddresses = mkOption { - description = "Mail addresses. First given address is considered the primary address."; - type = types.listOf types.str; - example = [ "jane.doe@example.com" ]; - default = [ ]; - }; - - groups = mkOption { - description = "List of groups this person should belong to."; - type = types.listOf types.str; - apply = unique; - default = [ ]; - }; - }; - } - ); - }; - - systems.oauth2 = mkOption { - description = "Provisioning of oauth2 resource servers"; - default = { }; - type = types.attrsOf ( - types.submodule { - options = { - present = mkPresentOption "oauth2 resource server"; - - public = mkOption { - description = "Whether this is a public client (enforces PKCE, doesn't use a basic secret)"; - type = types.bool; - default = false; - }; - - displayName = mkOption { - description = "Display name"; - type = types.str; - example = "Some Service"; - }; - - originUrl = mkOption { - description = "The origin URL of the service. OAuth2 redirects will only be allowed to sites under this origin."; - type = - let - originStrType = types.strMatching ".*://.*$"; - in - types.either originStrType (types.nonEmptyListOf originStrType); - example = "https://someservice.example.com/"; - }; - - originLanding = mkOption { - description = "When redirecting from the Kanidm Apps Listing page, some linked applications may need to land on a specific page to trigger oauth2/oidc interactions."; - type = types.str; - example = "https://someservice.example.com/home"; - }; - - basicSecretFile = mkOption { - description = '' - The basic secret to use for this service. If null, the random secret generated - by kanidm will not be touched. Do NOT use a path from the nix store here! - ''; - type = types.nullOr types.path; - example = "/run/secrets/some-oauth2-basic-secret"; - default = null; - }; - - imageFile = mkOption { - description = '' - Application image to display in the WebUI. - Kanidm supports "image/jpeg", "image/png", "image/gif", "image/svg+xml", and "image/webp". - The image will be uploaded each time kanidm-provision is run. - ''; - type = types.nullOr types.path; - default = null; - }; - - enableLocalhostRedirects = mkOption { - description = "Allow localhost redirects. Only for public clients."; - type = types.bool; - default = false; - }; - - enableLegacyCrypto = mkOption { - description = "Enable legacy crypto on this client. Allows JWT signing algorthms like RS256."; - type = types.bool; - default = false; - }; - - allowInsecureClientDisablePkce = mkOption { - description = '' - Disable PKCE on this oauth2 resource server to work around insecure clients - that may not support it. You should request the client to enable PKCE! - Only for non-public clients. - ''; - type = types.bool; - default = false; - }; - - preferShortUsername = mkOption { - description = "Use 'name' instead of 'spn' in the preferred_username claim"; - type = types.bool; - default = false; - }; - - scopeMaps = mkOption { - description = '' - Maps kanidm groups to returned oauth scopes. - See [Scope Relations](https://kanidm.github.io/kanidm/stable/integrations/oauth2.html#scope-relationships) for more information. - ''; - type = types.attrsOf (types.listOf types.str); - default = { }; - }; - - supplementaryScopeMaps = mkOption { - description = '' - Maps kanidm groups to additionally returned oauth scopes. - See [Scope Relations](https://kanidm.github.io/kanidm/stable/integrations/oauth2.html#scope-relationships) for more information. - ''; - type = types.attrsOf (types.listOf types.str); - default = { }; - }; - - removeOrphanedClaimMaps = mkOption { - description = "Whether claim maps not specified here but present in kanidm should be removed from kanidm."; - type = types.bool; - default = true; - }; - - claimMaps = mkOption { - description = '' - Adds additional claims (and values) based on which kanidm groups an authenticating party belongs to. - See [Claim Maps](https://kanidm.github.io/kanidm/master/integrations/oauth2.html#custom-claim-maps) for more information. - ''; - default = { }; - type = types.attrsOf ( - types.submodule { - options = { - joinType = mkOption { - description = '' - Determines how multiple values are joined to create the claim value. - See [Claim Maps](https://kanidm.github.io/kanidm/master/integrations/oauth2.html#custom-claim-maps) for more information. - ''; - type = types.enum [ - "array" - "csv" - "ssv" - ]; - default = "array"; - }; - - valuesByGroup = mkOption { - description = "Maps kanidm groups to values for the claim."; - default = { }; - type = types.attrsOf (types.listOf types.str); - }; - }; - } - ); - }; - }; - } - ); - }; - }; - }; - - config = mkIf (cfg.enableClient || cfg.enableServer || cfg.enablePam) { - assertions = - let - entityList = - type: attrs: flip mapAttrsToList (filterPresent attrs) (name: _: { inherit type name; }); - entities = - entityList "group" cfg.provision.groups - ++ entityList "person" cfg.provision.persons - ++ entityList "oauth2" cfg.provision.systems.oauth2; - - # Accumulate entities by name. Track corresponding entity types for later duplicate check. - entitiesByName = foldl' ( - acc: { type, name }: acc // { ${name} = (acc.${name} or [ ]) ++ [ type ]; } - ) { } entities; - - assertGroupsKnown = - opt: groups: - let - knownGroups = attrNames (filterPresent cfg.provision.groups) ++ [ "idm_all_persons" ]; - unknownGroups = subtractLists knownGroups groups; - in - { - assertion = (cfg.enableServer && cfg.provision.enable) -> unknownGroups == [ ]; - message = "${opt} refers to unknown groups: ${toString unknownGroups}"; - }; - - assertEntitiesKnown = - opt: entities: - let - unknownEntities = subtractLists (attrNames entitiesByName) entities; - in - { - assertion = (cfg.enableServer && cfg.provision.enable) -> unknownEntities == [ ]; - message = "${opt} refers to unknown entities: ${toString unknownEntities}"; - }; - in - [ - { - assertion = - !cfg.enableServer - || ((cfg.serverSettings.tls_chain or null) == null) - || (!isStorePath cfg.serverSettings.tls_chain); - message = '' - points to - a file in the Nix store. You should use a quoted absolute path to - prevent this. - ''; - } - { - assertion = - !cfg.enableServer - || ((cfg.serverSettings.tls_key or null) == null) - || (!isStorePath cfg.serverSettings.tls_key); - message = '' - points to - a file in the Nix store. You should use a quoted absolute path to - prevent this. - ''; - } - { - assertion = !cfg.enableClient || options.services.kanidm.clientSettings.isDefined; - message = '' - needs to be configured - if the client is enabled. - ''; - } - { - assertion = !cfg.enablePam || options.services.kanidm.clientSettings.isDefined; - message = '' - needs to be configured - for the PAM daemon to connect to the Kanidm server. - ''; - } - { - assertion = - !cfg.enableServer - || ( - cfg.serverSettings.domain == null - -> cfg.serverSettings.role == "WriteReplica" || cfg.serverSettings.role == "WriteReplicaNoUI" - ); - message = '' - can only be set if this instance - is not a ReadOnlyReplica. Otherwise the db would inherit it from - the instance it follows. - ''; - } - { - assertion = cfg.provision.enable -> cfg.enableServer; - message = " requires to be true"; - } - # If any secret is provisioned, the kanidm package must have some required patches applied to it - { - assertion = - ( - cfg.provision.enable - && ( - cfg.provision.adminPasswordFile != null - || cfg.provision.idmAdminPasswordFile != null - || any (x: x.basicSecretFile != null) (attrValues (filterPresent cfg.provision.systems.oauth2)) - ) - ) - -> cfg.package.enableSecretProvisioning; - message = '' - Specifying an admin account password or oauth2 basicSecretFile requires kanidm to be built with the secret provisioning patches. - You may want to set `services.kanidm.package = pkgs.kanidm.withSecretProvisioning;`. - ''; - } - # Entity names must be globally unique: - ( - let - # Filter all names that occurred in more than one entity type. - duplicateNames = filterAttrs (_: v: builtins.length v > 1) entitiesByName; - in - { - assertion = cfg.provision.enable -> duplicateNames == { }; - message = '' - services.kanidm.provision requires all entity names (group, person, oauth2, ...) to be unique! - ${concatLines ( - mapAttrsToList (name: xs: " - '${name}' used as: ${toString xs}") duplicateNames - )}''; - } - ) - ] - ++ flip mapAttrsToList (filterPresent cfg.provision.persons) ( - person: personCfg: - assertGroupsKnown "services.kanidm.provision.persons.${person}.groups" personCfg.groups - ) - ++ flip mapAttrsToList (filterPresent cfg.provision.groups) ( - group: groupCfg: - assertEntitiesKnown "services.kanidm.provision.groups.${group}.members" groupCfg.members - ) - ++ concatLists ( - flip mapAttrsToList (filterPresent cfg.provision.systems.oauth2) ( - oauth2: oauth2Cfg: - [ - (assertGroupsKnown "services.kanidm.provision.systems.oauth2.${oauth2}.scopeMaps" ( - attrNames oauth2Cfg.scopeMaps - )) - (assertGroupsKnown "services.kanidm.provision.systems.oauth2.${oauth2}.supplementaryScopeMaps" ( - attrNames oauth2Cfg.supplementaryScopeMaps - )) - ] - ++ concatLists ( - flip mapAttrsToList oauth2Cfg.claimMaps ( - claim: claimCfg: [ - (assertGroupsKnown "services.kanidm.provision.systems.oauth2.${oauth2}.claimMaps.${claim}.valuesByGroup" ( - attrNames claimCfg.valuesByGroup - )) - # At least one group must map to a value in each claim map - { - assertion = - (cfg.provision.enable && cfg.enableServer) - -> any (xs: xs != [ ]) (attrValues claimCfg.valuesByGroup); - message = "services.kanidm.provision.systems.oauth2.${oauth2}.claimMaps.${claim} does not specify any values for any group"; - } - # Public clients cannot define a basic secret - { - assertion = - (cfg.provision.enable && cfg.enableServer && oauth2Cfg.public) -> oauth2Cfg.basicSecretFile == null; - message = "services.kanidm.provision.systems.oauth2.${oauth2} is a public client and thus cannot specify a basic secret"; - } - # Public clients cannot disable PKCE - { - assertion = - (cfg.provision.enable && cfg.enableServer && oauth2Cfg.public) - -> !oauth2Cfg.allowInsecureClientDisablePkce; - message = "services.kanidm.provision.systems.oauth2.${oauth2} is a public client and thus cannot disable PKCE"; - } - # Non-public clients cannot enable localhost redirects - { - assertion = - (cfg.provision.enable && cfg.enableServer && !oauth2Cfg.public) - -> !oauth2Cfg.enableLocalhostRedirects; - message = "services.kanidm.provision.systems.oauth2.${oauth2} is a non-public client and thus cannot enable localhost redirects"; - } - ] - ) - ) - ) - ); - - environment.systemPackages = mkIf cfg.enableClient [ cfg.package ]; - - systemd.tmpfiles.settings."10-kanidm" = { - ${cfg.serverSettings.online_backup.path}.d = { - mode = "0700"; - user = "kanidm"; - group = "kanidm"; - }; - }; - - systemd.services.kanidm = mkIf cfg.enableServer { - description = "kanidm identity management daemon"; - wantedBy = [ "multi-user.target" ]; - after = [ "network.target" ]; - serviceConfig = mkMerge [ - # Merge paths and ignore existing prefixes needs to sidestep mkMerge - ( - defaultServiceConfig - // { - BindReadOnlyPaths = mergePaths (defaultServiceConfig.BindReadOnlyPaths ++ certPaths); - } - ) - { - StateDirectory = "kanidm"; - StateDirectoryMode = "0700"; - RuntimeDirectory = "kanidmd"; - ExecStart = "${cfg.package}/bin/kanidmd server -c ${serverConfigFile}"; - ExecStartPost = mkIf cfg.provision.enable postStartScript; - User = "kanidm"; - Group = "kanidm"; - - BindPaths = [ - # To create the socket - "/run/kanidmd:/run/kanidmd" - # To store backups - cfg.serverSettings.online_backup.path - ]; - - AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; - CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; - # This would otherwise override the CAP_NET_BIND_SERVICE capability. - PrivateUsers = mkForce false; - # Port needs to be exposed to the host network - PrivateNetwork = mkForce false; - RestrictAddressFamilies = [ - "AF_INET" - "AF_INET6" - "AF_UNIX" - ]; - #TemporaryFileSystem = "/:ro"; - } - ]; - environment.RUST_LOG = "info"; - }; - - systemd.services.kanidm-unixd = mkIf cfg.enablePam { - description = "Kanidm PAM daemon"; - wantedBy = [ "multi-user.target" ]; - after = [ "network.target" ]; - restartTriggers = [ - unixConfigFile - clientConfigFile - ]; - serviceConfig = mkMerge [ - defaultServiceConfig - { - CacheDirectory = "kanidm-unixd"; - CacheDirectoryMode = "0700"; - RuntimeDirectory = "kanidm-unixd"; - ExecStart = "${cfg.package}/bin/kanidm_unixd"; - User = "kanidm-unixd"; - Group = "kanidm-unixd"; - - BindReadOnlyPaths = [ - "-/etc/kanidm" - "-/etc/static/kanidm" - "-/etc/ssl" - "-/etc/static/ssl" - "-/etc/passwd" - "-/etc/group" - ]; - BindPaths = [ - # To create the socket - "/run/kanidm-unixd:/var/run/kanidm-unixd" - ]; - # Needs to connect to kanidmd - PrivateNetwork = mkForce false; - RestrictAddressFamilies = [ - "AF_INET" - "AF_INET6" - "AF_UNIX" - ]; - TemporaryFileSystem = "/:ro"; - } - ]; - environment.RUST_LOG = "info"; - }; - - systemd.services.kanidm-unixd-tasks = mkIf cfg.enablePam { - description = "Kanidm PAM home management daemon"; - wantedBy = [ "multi-user.target" ]; - after = [ - "network.target" - "kanidm-unixd.service" - ]; - partOf = [ "kanidm-unixd.service" ]; - restartTriggers = [ - unixConfigFile - clientConfigFile - ]; - serviceConfig = { - ExecStart = "${cfg.package}/bin/kanidm_unixd_tasks"; - - BindReadOnlyPaths = [ - "/nix/store" - "-/etc/resolv.conf" - "-/etc/nsswitch.conf" - "-/etc/hosts" - "-/etc/localtime" - "-/etc/kanidm" - "-/etc/static/kanidm" - ]; - BindPaths = [ - # To manage home directories - "/home" - # To connect to kanidm-unixd - "/run/kanidm-unixd:/var/run/kanidm-unixd" - ]; - # CAP_DAC_OVERRIDE is needed to ignore ownership of unixd socket - CapabilityBoundingSet = [ - "CAP_CHOWN" - "CAP_FOWNER" - "CAP_DAC_OVERRIDE" - "CAP_DAC_READ_SEARCH" - ]; - IPAddressDeny = "any"; - # Need access to users - PrivateUsers = false; - # Need access to home directories - ProtectHome = false; - RestrictAddressFamilies = [ "AF_UNIX" ]; - TemporaryFileSystem = "/:ro"; - Restart = "on-failure"; - }; - environment.RUST_LOG = "info"; - }; - - # These paths are hardcoded - environment.etc = mkMerge [ - (mkIf cfg.enableServer { "kanidm/server.toml".source = serverConfigFile; }) - (mkIf options.services.kanidm.clientSettings.isDefined { - "kanidm/config".source = clientConfigFile; - }) - (mkIf cfg.enablePam { "kanidm/unixd".source = unixConfigFile; }) - ]; - - system.nssModules = mkIf cfg.enablePam [ cfg.package ]; - - system.nssDatabases.group = optional cfg.enablePam "kanidm"; - system.nssDatabases.passwd = optional cfg.enablePam "kanidm"; - - users.groups = mkMerge [ - (mkIf cfg.enableServer { kanidm = { }; }) - (mkIf cfg.enablePam { kanidm-unixd = { }; }) - ]; - users.users = mkMerge [ - (mkIf cfg.enableServer { - kanidm = { - description = "Kanidm server"; - isSystemUser = true; - group = "kanidm"; - packages = [ cfg.package ]; - }; - }) - (mkIf cfg.enablePam { - kanidm-unixd = { - description = "Kanidm PAM daemon"; - isSystemUser = true; - group = "kanidm-unixd"; - }; - }) - ]; - }; - - meta.maintainers = with lib.maintainers; [ - Flakebi - oddlama - ]; - meta.buildDocsInSandbox = false; -} diff --git a/configuration.nix b/configuration.nix index 115e237..0d0df0c 100644 --- a/configuration.nix +++ b/configuration.nix @@ -38,6 +38,7 @@ in { imports = [ ./selfprivacy-module.nix + ./auth/auth.nix ./auth/auth-module.nix ./volumes.nix ./users.nix diff --git a/flake.nix b/flake.nix index cd5b6a1..4e1d78d 100644 --- a/flake.nix +++ b/flake.nix @@ -31,11 +31,6 @@ hardware-configuration deployment ./configuration.nix - ./auth/auth.nix - { - disabledModules = [ "services/security/kanidm.nix" ]; - imports = [ ./auth/kanidm.nix ]; - } selfprivacy-api.nixosModules.default ( { pkgs, lib, ... }: diff --git a/sp-modules/roundcube/module.nix b/sp-modules/roundcube/module.nix index f38ff48..606bb92 100644 --- a/sp-modules/roundcube/module.nix +++ b/sp-modules/roundcube/module.nix @@ -22,10 +22,6 @@ let oauth-donor = config.selfprivacy.passthru.mailserver; oauthClientSecretFP = auth-passthru.mkOAuth2ClientSecretFP linuxGroupOfService; - # copy client secret from mailserver - kanidmExecStartPreScriptRoot = pkgs.writeShellScript "${sp-module-name}-kanidm-ExecStartPre-root-script.sh" '' - install -v -m640 -o kanidm -g ${linuxGroupOfService} ${oauth-donor.oauth-client-secret-fp} ${oauthClientSecretFP} - ''; in { options.selfprivacy.modules.roundcube = { @@ -121,9 +117,16 @@ in after = [ "dovecot2.service" ]; requires = [ "dovecot2.service" ]; }; - systemd.services.kanidm.serviceConfig.ExecStartPre = lib.mkAfter [ - ("-+" + kanidmExecStartPreScriptRoot) - ]; + systemd.services.kanidm.serviceConfig = { + ExecStartPre = lib.mkAfter [ + (pkgs.writeShellScript "copy-mailserver-client-secret-to-roundcube" '' + install -v -m640 -o kanidm -g ${linuxGroupOfService} ${oauth-donor.oauth-client-secret-fp} ${oauthClientSecretFP} + '') + ]; + SystemCallFilter = [ + "@chown" + ]; + }; selfprivacy.auth.clients."${oauth-donor.oauth-client-id}" = { inherit adminsGroup usersGroup; diff --git a/sp-modules/simple-nixos-mailserver/auth-dovecot.nix b/sp-modules/simple-nixos-mailserver/auth-dovecot.nix index 34b8f47..0544543 100644 --- a/sp-modules/simple-nixos-mailserver/auth-dovecot.nix +++ b/sp-modules/simple-nixos-mailserver/auth-dovecot.nix @@ -22,10 +22,12 @@ let runtime-folder = group; keysPath = auth-passthru.keys-path; - # create service account token, needed for LDAP - kanidmExecStartPostScript = pkgs.writeShellScript "mailserver-kanidm-ExecStartPost-script.sh" '' + kanidmExecStartPostScript = pkgs.writeShellScript "create-dovecot-service-account-token-for-ldap" '' export HOME=$RUNTIME_DIRECTORY/client_home readonly KANIDM="${config.services.kanidm.package}/bin/kanidm" + export KANIDM_NAME=idm_admin + export KANIDM_URL="${config.services.kanidm.provision.instanceUrl}" + export KANIDM_SKIP_HOSTNAME_VERIFICATION="true" # get Kanidm service account for mailserver KANIDM_SERVICE_ACCOUNT="$($KANIDM service-account list --name idm_admin | grep -E "^name: ${mailserver-service-account-name}$")" @@ -111,7 +113,7 @@ let }; oauth-client-id = "mailserver"; oauth-client-secret-fp = "${keysPath}/${group}/kanidm-oauth-client-secret"; - oauth-secret-ExecStartPreScript = pkgs.writeShellScript "${oauth-client-id}-kanidm-ExecStartPre-script.sh" '' + oauth-secret-ExecStartPreScript = pkgs.writeShellScript "${oauth-client-id}-create-client-secret.sh" '' set -o xtrace [ -f "${oauth-client-secret-fp}" ] || \ "${lib.getExe pkgs.openssl}" rand -base64 32 | tr "\n:@/+=" "012345" > "${oauth-client-secret-fp}" @@ -202,10 +204,10 @@ in }; systemd.services.kanidm.serviceConfig.ExecStartPre = lib.mkBefore [ - ("-" + oauth-secret-ExecStartPreScript) + oauth-secret-ExecStartPreScript ]; systemd.services.kanidm.serviceConfig.ExecStartPost = lib.mkAfter [ - ("-" + kanidmExecStartPostScript) + kanidmExecStartPostScript ]; systemd.services.postfix.restartTriggers = [ diff --git a/sp-modules/simple-nixos-mailserver/config-paths-needed.json b/sp-modules/simple-nixos-mailserver/config-paths-needed.json index 55ac9c0..ca6a5be 100644 --- a/sp-modules/simple-nixos-mailserver/config-paths-needed.json +++ b/sp-modules/simple-nixos-mailserver/config-paths-needed.json @@ -25,5 +25,6 @@ [ "services", "postfix", "user" ], [ "services", "redis", "servers", "rspamd" ], [ "services", "rspamd" ], - [ "services", "kanidm", "package" ] + [ "services", "kanidm", "package" ], + [ "services", "kanidm", "provision", "instanceUrl" ] ]