refactor: switch to upstream nixos kanidm module
This commit is contained in:
@@ -11,58 +11,21 @@ let
|
||||
;
|
||||
auth-passthru = config.selfprivacy.passthru.auth;
|
||||
keys-path = auth-passthru.keys-path;
|
||||
# generate OAuth2 client secret
|
||||
mkKanidmExecStartPreScript =
|
||||
oauthClientID: linuxGroup:
|
||||
let
|
||||
secretFP = auth-passthru.mkOAuth2ClientSecretFP linuxGroup;
|
||||
in
|
||||
pkgs.writeShellScript "${oauthClientID}-kanidm-ExecStartPre-script.sh" ''
|
||||
set -o pipefail
|
||||
set -o errexit
|
||||
if ! [ -f "${secretFP}" ]
|
||||
then
|
||||
"${lib.getExe pkgs.openssl}" rand -base64 32 \
|
||||
| tr "\n:@/+=" "012345" > "${secretFP}"
|
||||
chmod 640 "${secretFP}"
|
||||
fi
|
||||
'';
|
||||
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
|
||||
'';
|
||||
mkKanidmTokenCreationSnippet =
|
||||
{
|
||||
clientID,
|
||||
linuxGroupOfClient,
|
||||
isMailserver,
|
||||
...
|
||||
}:
|
||||
let
|
||||
kanidmServiceAccountName = "sp.${clientID}.service-account";
|
||||
kanidmServiceAccountTokenName = "${clientID}-service-account-token";
|
||||
kanidmServiceAccountTokenFP = auth-passthru.mkServiceAccountTokenFP linuxGroupOfClient;
|
||||
isRW = clientID == "selfprivacy-api";
|
||||
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"
|
||||
@@ -108,8 +71,8 @@ let
|
||||
''
|
||||
+ 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,
|
||||
...
|
||||
}:
|
||||
{
|
||||
serviceConfig = {
|
||||
ExecStartPre = [
|
||||
# "-" prefix means to ignore exit code of prefixed script
|
||||
("-" + mkKanidmExecStartPreScript clientID linuxGroupOfClient)
|
||||
(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.mkIf isTokenNeeded (
|
||||
lib.mkAfter [
|
||||
("-" + mkKanidmExecStartPostScript clientID linuxGroupOfClient isMailserver)
|
||||
]
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
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 = [
|
||||
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 ];
|
||||
});
|
||||
]
|
||||
++ 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
|
||||
|
@@ -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 =
|
||||
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
|
||||
|
1052
auth/kanidm.nix
1052
auth/kanidm.nix
File diff suppressed because it is too large
Load Diff
@@ -38,6 +38,7 @@ in
|
||||
{
|
||||
imports = [
|
||||
./selfprivacy-module.nix
|
||||
./auth/auth.nix
|
||||
./auth/auth-module.nix
|
||||
./volumes.nix
|
||||
./users.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, ... }:
|
||||
|
@@ -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;
|
||||
|
@@ -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 = [
|
||||
|
@@ -25,5 +25,6 @@
|
||||
[ "services", "postfix", "user" ],
|
||||
[ "services", "redis", "servers", "rspamd" ],
|
||||
[ "services", "rspamd" ],
|
||||
[ "services", "kanidm", "package" ]
|
||||
[ "services", "kanidm", "package" ],
|
||||
[ "services", "kanidm", "provision", "instanceUrl" ]
|
||||
]
|
||||
|
Reference in New Issue
Block a user