mirror of
https://github.com/oddlama/nix-config.git
synced 2025-10-10 23:00:39 +02:00
feat: switch to upstreamed influxdb2 provisioning, add kanidm provisioning module
This commit is contained in:
parent
9533e760e4
commit
522de920bb
11 changed files with 776 additions and 1325 deletions
|
@ -168,7 +168,7 @@ openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \
|
|||
```bash
|
||||
# Recover admin account
|
||||
kanidmd recover-account admin
|
||||
> AhNeQgKkwwEHZ85dxj1GPjx58vWsBU8QsvKSyYwUL7bz57bp
|
||||
> FrEELN4tfyVbUAfhGeuUyZyaKk8cbpFufuDwyCPhY3xhb3X2
|
||||
# Login with recovered root account
|
||||
kanidm login --name admin
|
||||
# Generate new credentials for idm_admin account
|
||||
|
|
48
flake.lock
generated
48
flake.lock
generated
|
@ -47,11 +47,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1691966209,
|
||||
"narHash": "sha256-0L2vP/QEi8W1s9dViB0dEgKRXa7vjPdyxvDD1oyZbwA=",
|
||||
"lastModified": 1692783612,
|
||||
"narHash": "sha256-Mz1xv45Rjzet1D2bMGKapgw1JCHaD60dBs4sE6Dz2+A=",
|
||||
"owner": "oddlama",
|
||||
"repo": "agenix-rekey",
|
||||
"rev": "7e8f11a3cf6786477c420f2166a0c713bb706941",
|
||||
"rev": "52695865488742e0b34a56111cd40e229b3ab90a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -159,11 +159,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1691999995,
|
||||
"narHash": "sha256-8DyiH3zEdouwNhW68BkHrfoDYX9Cf1So6u8mCWN0iIo=",
|
||||
"lastModified": 1692199161,
|
||||
"narHash": "sha256-GqKApvQ1JCf5DzH/Q+P4nwuHb6MaQGaWTu41lYzveF4=",
|
||||
"owner": "nix-community",
|
||||
"repo": "disko",
|
||||
"rev": "6388d2859c91adab847b4922b726f61920074494",
|
||||
"rev": "4eed2457b053c4bbad7d90d2b3a1d539c2c9009c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -301,11 +301,11 @@
|
|||
"systems": "systems_3"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1689068808,
|
||||
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
|
||||
"lastModified": 1692799911,
|
||||
"narHash": "sha256-3eihraek4qL744EvQXsK1Ha6C3CR7nnT8X2qWap4RNk=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
|
||||
"rev": "f9e7cf818399d17d347f847525c5a5a8032e4e44",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -364,11 +364,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1692131549,
|
||||
"narHash": "sha256-MFjI8NL63/6HjMZpvJgnB/Pgg2dht22t45jOYtipZig=",
|
||||
"lastModified": 1692763155,
|
||||
"narHash": "sha256-qMrGKZ8c/q/mHO3ZdrcBPwiVVXPLLgXjY98Ejqb5kAA=",
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"rev": "75cfe974e2ca05a61b66768674032b4c079e55d4",
|
||||
"rev": "6a20e40acaebf067da682661aa67da8b36812606",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -414,11 +414,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1691325831,
|
||||
"narHash": "sha256-/S1A8FpFE6yiIzFIAYTQCSn9uqOUziu92iRTokI0eiQ=",
|
||||
"lastModified": 1692274616,
|
||||
"narHash": "sha256-UttCk5/sl0lLrBVO9kpmtDlFXcI2UkyOaSp7+grLRRE=",
|
||||
"owner": "astro",
|
||||
"repo": "microvm.nix",
|
||||
"rev": "d5c5bb4cebbd9f59b7ab81a4b36fea10b6016d38",
|
||||
"rev": "a291d324915f26d1fd86443bd486089099e8b541",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -465,11 +465,11 @@
|
|||
},
|
||||
"nixos-hardware": {
|
||||
"locked": {
|
||||
"lastModified": 1691871742,
|
||||
"narHash": "sha256-6yDNjfbAMpwzWL4y75fxs6beXHRANfYX8BNSPjYehck=",
|
||||
"lastModified": 1692952286,
|
||||
"narHash": "sha256-TsrtPv3+Q1KR0avZxpiJH+b6fX/R/hEQVHbjl1ebotY=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixos-hardware",
|
||||
"rev": "430a56dd16fe583a812b2df44dca002acab2f4f6",
|
||||
"rev": "817e297fc3352fadc15f2c5306909aa9192d7d97",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -501,11 +501,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1692084312,
|
||||
"narHash": "sha256-Za++qKVK6ovjNL9poQZtLKRM/re663pxzbJ+9M4Pgwg=",
|
||||
"lastModified": 1692913444,
|
||||
"narHash": "sha256-1SvMQm2DwofNxXVtNWWtIcTh7GctEVrS/Xel/mdc6iY=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "8353344d3236d3fda429bb471c1ee008857d3b7c",
|
||||
"rev": "18324978d632ffc55ef1d928e81630c620f4f447",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -588,11 +588,11 @@
|
|||
"nixpkgs-stable": "nixpkgs-stable_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1691747570,
|
||||
"narHash": "sha256-J3fnIwJtHVQ0tK2JMBv4oAmII+1mCdXdpeCxtIsrL2A=",
|
||||
"lastModified": 1692274144,
|
||||
"narHash": "sha256-BxTQuRUANQ81u8DJznQyPmRsg63t4Yc+0kcyq6OLz8s=",
|
||||
"owner": "cachix",
|
||||
"repo": "pre-commit-hooks.nix",
|
||||
"rev": "c5ac3aa3324bd8aebe8622a3fc92eeb3975d317a",
|
||||
"rev": "7e3517c03d46159fdbf8c0e5c97f82d5d4b0c8fa",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
@ -36,16 +36,11 @@ in {
|
|||
group = "influxdb2";
|
||||
};
|
||||
|
||||
services.influxdb2.provision.ensureApiTokens = [
|
||||
{
|
||||
name = "grafana servers:telegraf (${config.node.name})";
|
||||
org = "servers";
|
||||
user = "admin";
|
||||
readBuckets = ["telegraf"];
|
||||
writeBuckets = ["telegraf"];
|
||||
tokenFile = nodes.ward-influxdb.config.age.secrets."grafana-influxdb-token-${config.node.name}".path;
|
||||
}
|
||||
];
|
||||
services.influxdb2.provision.organization.servers.auths."grafana servers:telegraf (${config.node.name})" = {
|
||||
readBuckets = ["telegraf"];
|
||||
writeBuckets = ["telegraf"];
|
||||
tokenFile = nodes.ward-influxdb.config.age.secrets."grafana-influxdb-token-${config.node.name}".path;
|
||||
};
|
||||
};
|
||||
|
||||
nodes.sentinel = {
|
||||
|
|
42
hosts/ward/microvms/immich.nix
Normal file
42
hosts/ward/microvms/immich.nix
Normal file
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
nodes,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
sentinelCfg = nodes.sentinel.config;
|
||||
immichDomain = "immich.${sentinelCfg.repo.secrets.local.personalDomain}";
|
||||
in {
|
||||
meta.wireguard-proxy.sentinel.allowedTCPPorts = [config.services.immich.web_port];
|
||||
|
||||
nodes.sentinel = {
|
||||
networking.providedDomains.immich = immichDomain;
|
||||
|
||||
services.nginx = {
|
||||
upstreams.immich = {
|
||||
servers."${config.meta.wireguard.proxy-sentinel.ipv4}:${toString config.services.immich.settings.bind_port}" = {};
|
||||
extraConfig = ''
|
||||
zone immich 64k;
|
||||
keepalive 2;
|
||||
'';
|
||||
};
|
||||
virtualHosts.${immichDomain} = {
|
||||
forceSSL = true;
|
||||
useACMEWildcardHost = true;
|
||||
oauth2.enable = true;
|
||||
oauth2.allowedGroups = ["access_immich"];
|
||||
locations."/" = {
|
||||
proxyPass = "http://immich";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.immich = {
|
||||
enable = true;
|
||||
};
|
||||
|
||||
systemd.services.grafana.serviceConfig.RestartSec = "600"; # Retry every 10 minutes
|
||||
}
|
|
@ -82,17 +82,7 @@ in {
|
|||
passwordFile = config.age.secrets.influxdb-admin-password.path;
|
||||
tokenFile = config.age.secrets.influxdb-admin-token.path;
|
||||
};
|
||||
ensureOrganizations = [
|
||||
{
|
||||
name = "servers";
|
||||
}
|
||||
];
|
||||
ensureBuckets = [
|
||||
{
|
||||
name = "telegraf";
|
||||
org = "servers";
|
||||
}
|
||||
];
|
||||
organizations.servers.buckets.telegraf = {};
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
disabledModules = ["services/databases/influxdb2.nix"];
|
||||
disabledModules = ["services/security/kanidm.nix"];
|
||||
imports = [
|
||||
../users/root
|
||||
|
||||
|
@ -19,7 +19,7 @@
|
|||
./config/users.nix
|
||||
./config/xdg.nix
|
||||
|
||||
./meta/influxdb2.nix
|
||||
./meta/kanidm.nix
|
||||
./meta/microvms.nix
|
||||
./meta/nginx.nix
|
||||
./meta/oauth2-proxy.nix
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,32 +1,113 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
options,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
inherit
|
||||
(lib)
|
||||
all
|
||||
any
|
||||
attrNames
|
||||
attrValues
|
||||
concatLines
|
||||
concatLists
|
||||
concatMap
|
||||
concatMapStrings
|
||||
converge
|
||||
elem
|
||||
escapeShellArg
|
||||
escapeShellArgs
|
||||
filter
|
||||
filterAttrsRecursive
|
||||
flip
|
||||
foldl'
|
||||
getExe
|
||||
hasInfix
|
||||
hasPrefix
|
||||
isStorePath
|
||||
mapAttrs
|
||||
mapAttrsToList
|
||||
mdDoc
|
||||
mkEnableOption
|
||||
mkForce
|
||||
mkIf
|
||||
mkMerge
|
||||
mkOption
|
||||
mkPackageOptionMD
|
||||
optional
|
||||
optionals
|
||||
subtractLists
|
||||
types
|
||||
;
|
||||
|
||||
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 (filterAttrsRecursive (_: v: v != null));
|
||||
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 = {
|
||||
BindReadOnlyPaths = [
|
||||
"/nix/store"
|
||||
"-/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.";
|
||||
description = mdDoc "Whether to ensure that this ${what} is present or absent.";
|
||||
type = types.bool;
|
||||
default = true;
|
||||
};
|
||||
|
@ -39,14 +120,39 @@
|
|||
default = script;
|
||||
};
|
||||
|
||||
provisionScript = pkgs.writeShellScript "post-start-provision" ''
|
||||
mappingsJson = pkgs.writeText "mappings.json" (builtins.toJSON {
|
||||
account_credentials.admin = cfg.provision.adminPasswordFile;
|
||||
account_credentials.idm_admin = cfg.provision.idmAdminPasswordFile;
|
||||
oauth2_basic_secrets = mapAttrs (_: x: x.basicSecretFile) cfg.provision.systems.oauth2;
|
||||
});
|
||||
|
||||
preStartScript = pkgs.writeShellScript "pre-start-manipulate" ''
|
||||
if ! test -e ${escapeShellArg cfg.serverSettings.db_path}; then
|
||||
touch "$STATE_DIRECTORY/.first_startup"
|
||||
else
|
||||
${getExe pkgs.kanidm-secret-manipulator} ${escapeShellArg cfg.serverSettings.db_path} ${mappingsJson}
|
||||
fi
|
||||
'';
|
||||
|
||||
restarterScript = pkgs.writeShellScript "post-start-restarter" ''
|
||||
set -euo pipefail
|
||||
if test -e "$STATE_DIRECTORY/.needs_restart"; then
|
||||
rm -f "$STATE_DIRECTORY/.needs_restart"
|
||||
echo "Restarting kanidm.service..."
|
||||
#kill -TERM $MAINPID
|
||||
#echo "Restarting kanidm.service via dbus..."
|
||||
${pkgs.dbus}/bin/dbus-send --system --type=method_call --print-reply --dest=org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager.RestartUnit string:"kanidm.service" string:"replace"
|
||||
fi
|
||||
'';
|
||||
|
||||
postStartScript = pkgs.writeShellScript "post-start" ''
|
||||
set -euo pipefail
|
||||
|
||||
# Wait for the kanidm server to come online
|
||||
count=0
|
||||
while ! test -e /run/kanidmd/sock; do
|
||||
if [ "$count" -eq 300 ]; then
|
||||
echo "Tried for 30 seconds, giving up..."
|
||||
if [ "$count" -eq 600 ]; then
|
||||
echo "Tried for 60 seconds, giving up..."
|
||||
exit 1
|
||||
fi
|
||||
if ! kill -0 "$MAINPID"; then
|
||||
|
@ -60,29 +166,97 @@
|
|||
# If this is the first start, we login this time by recovering the admin account
|
||||
# and force a restart afterwards to rewrite the password.
|
||||
if test -e "$STATE_DIRECTORY/.first_startup"; then
|
||||
KANIDM_PASSWORD="$(${cfg.package}/bin/kanidmd recover-account admin)"
|
||||
# Recover admin account
|
||||
if ! recover_out=$(${cfg.package}/bin/kanidmd recover-account -c ${serverConfigFile} admin); then
|
||||
echo "$recover_out" >&2
|
||||
echo "kanidm provision: Failed to recover admin account" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! KANIDM_PASSWORD_ADMIN=$(grep -o '[A-Za-z0-9]\{48\}' <<< "$recover_out"); then
|
||||
echo "$recover_out" >&2
|
||||
echo "kanidm provision: Failed to parse password for admin account" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Recover idm_admin account
|
||||
if ! recover_out=$(${cfg.package}/bin/kanidmd recover-account -c ${serverConfigFile} idm_admin); then
|
||||
echo "$recover_out" >&2
|
||||
echo "kanidm provision: Failed to recover admin account" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! KANIDM_PASSWORD_IDM=$(grep -o '[A-Za-z0-9]\{48\}' <<< "$recover_out"); then
|
||||
echo "$recover_out" >&2
|
||||
echo "kanidm provision: Failed to parse password for idm_admin account" >&2
|
||||
exit 1
|
||||
fi
|
||||
needs_rewrite=1
|
||||
rm -f "$STATE_DIRECTORY/.first_startup"
|
||||
else
|
||||
# Login using the admin password
|
||||
KANIDM_PASSWORD="$(< ${escapeShellArg cfg.provision.adminPasswordFile})"
|
||||
KANIDM_PASSWORD_ADMIN="$(< ${escapeShellArg cfg.provision.adminPasswordFile})"
|
||||
KANIDM_PASSWORD_IDM="$(< ${escapeShellArg cfg.provision.idmAdminPasswordFile})"
|
||||
fi
|
||||
|
||||
${cfg.package}/bin/kanidm login --name admin <<< "$KANIDM_PASSWORD"
|
||||
# Login to admin and idm_admin
|
||||
export TMPDIR=$(mktemp -d)
|
||||
trap 'rm -rf $TMPDIR' EXIT
|
||||
# Set $HOME so kanidm can save the token temporarily
|
||||
export HOME=$TMPDIR
|
||||
KANIDM_PASSWORD=$KANIDM_PASSWORD_ADMIN ${cfg.package}/bin/kanidm login --name admin \
|
||||
|| { echo "kanidm provision: Failed to login as admin, see kanidm logs." >&2; exit 1; }
|
||||
KANIDM_PASSWORD=$KANIDM_PASSWORD_IDM ${cfg.package}/bin/kanidm login --name idm_admin \
|
||||
|| { echo "kanidm provision: Failed to login as idm_admin, see kanidm logs." >&2; exit 1; }
|
||||
|
||||
known_groups=$(kanidm group list --output=json)
|
||||
# Wrapper function that detects kanidm errors by detecting any output to stderr
|
||||
# (stderr and stdout are swapped when calling this)
|
||||
function kanidm-detect-err() {
|
||||
if ! err=$(${cfg.package}/bin/kanidm "$@" 3>&2 2>&1 1>&3-); then
|
||||
echo "$err"
|
||||
echo "kanidm ''${*@Q}: failed with status $?, see error above"
|
||||
exit 1
|
||||
fi
|
||||
if [[ -n "$err" ]]; then
|
||||
echo "$err"
|
||||
echo "kanidm ''${*@Q}: failed, see error above"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Wrapper function to easily execute commands as admin or idm_admin
|
||||
function kanidm-as-user() {
|
||||
name=$1
|
||||
shift
|
||||
kanidm-detect-err "$@" --name "$name" 3>&2 2>&1 1>&3-
|
||||
}
|
||||
|
||||
function kanidm-admin() { kanidm-as-user admin "$@"; }
|
||||
function kanidm-idm() { kanidm-as-user idm_admin "$@"; }
|
||||
|
||||
known_groups=$(kanidm-admin group list --output=json)
|
||||
function group_exists() {
|
||||
[[ -n "$(${getExe jq} <<< "$known_groups" '. | select(.name[0] == "$1")')" ]]
|
||||
if ! x=$(${getExe pkgs.jq} <<< "$known_groups" ".[] | select(.name[0] == \"$1\")"); then
|
||||
echo "kanidm provision: Failed to parse groups list." >&2
|
||||
exit 1
|
||||
fi
|
||||
[[ -n "$x" ]]
|
||||
}
|
||||
|
||||
known_persons=$(kanidm person list --output=json)
|
||||
known_persons=$(kanidm-admin person list --output=json)
|
||||
function person_exists() {
|
||||
[[ -n "$(${getExe jq} <<< "$known_persons" '. | select(.name[0] == "$1")')" ]]
|
||||
if ! x=$(${getExe pkgs.jq} <<< "$known_persons" ".[] | select(.name[0] == \"$1\")"); then
|
||||
echo "kanidm provision: Failed to parse persons list." >&2
|
||||
exit 1
|
||||
fi
|
||||
[[ -n "$x" ]]
|
||||
}
|
||||
|
||||
known_oauth2_systems=$(kanidm person list --output=json)
|
||||
known_oauth2_systems=$(kanidm-admin system oauth2 list --output=json)
|
||||
function oauth2_system_exists() {
|
||||
[[ -n "$(${getExe jq} <<< "$known_oauth2_systems" '. | select(.oauth2_rs_name[0] == "$1")')" ]]
|
||||
if ! x=$(${getExe pkgs.jq} <<< "$known_oauth2_systems" ".[] | select(.oauth2_rs_name[0] == \"$1\")"); then
|
||||
echo "kanidm provision: Failed to parse oauth2 systems list." >&2
|
||||
exit 1
|
||||
fi
|
||||
[[ -n "$x" ]]
|
||||
}
|
||||
|
||||
${concatMapStrings (x: x._script) (attrValues cfg.provision.groups)}
|
||||
|
@ -94,226 +268,555 @@
|
|||
touch "$STATE_DIRECTORY/.needs_restart"
|
||||
fi
|
||||
'';
|
||||
|
||||
restarterScript = pkgs.writeShellScript "post-start-restarter" ''
|
||||
set -euo pipefail
|
||||
if test -e "$STATE_DIRECTORY/.needs_restart"; then
|
||||
rm -f "$STATE_DIRECTORY/.needs_restart"
|
||||
/run/current-system/systemd/bin/systemctl restart kanidm
|
||||
fi
|
||||
'';
|
||||
in {
|
||||
options.services.kanidm.provision = {
|
||||
enable = mkEnableOption "provisioning of systems (oauth2), groups and users";
|
||||
options.services.kanidm = {
|
||||
enableClient = mkEnableOption (mdDoc "the Kanidm client");
|
||||
enableServer = mkEnableOption (mdDoc "the Kanidm server");
|
||||
enablePam = mkEnableOption (mdDoc "the Kanidm PAM and NSS integration");
|
||||
|
||||
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";
|
||||
type = types.path;
|
||||
package = mkPackageOptionMD pkgs "kanidm" {};
|
||||
|
||||
provision = {
|
||||
enable = mkEnableOption "provisioning of systems (oauth2), groups and users";
|
||||
|
||||
adminPasswordFile = mkOption {
|
||||
description = mdDoc "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";
|
||||
type = types.path;
|
||||
};
|
||||
|
||||
idmAdminPasswordFile = mkOption {
|
||||
description = mdDoc "Path to a file containing the idm admin password for kanidm. Do NOT use a file from the nix store here!";
|
||||
example = "/run/secrets/kanidm-idm-admin-password";
|
||||
type = types.path;
|
||||
};
|
||||
|
||||
persons = mkOption {
|
||||
description = mdDoc "Provisioning of kanidm persons";
|
||||
default = {};
|
||||
type = types.attrsOf (types.submodule (personSubmod: let
|
||||
inherit (personSubmod.config._module.args) name;
|
||||
updateArgs =
|
||||
["--displayname" personSubmod.config.displayName]
|
||||
++ optionals (personSubmod.config.legalName != null)
|
||||
["--legalname" personSubmod.config.legalName]
|
||||
# mail addresses
|
||||
++ concatMap (addr: ["--mail" addr]) personSubmod.config.mailAddresses;
|
||||
in {
|
||||
options = {
|
||||
_script = mkScript (
|
||||
if personSubmod.config.present
|
||||
then
|
||||
''
|
||||
if ! person_exists ${escapeShellArg name}; then
|
||||
kanidm-idm person create ${escapeShellArg name} \
|
||||
${escapeShellArg personSubmod.config.displayName}
|
||||
fi
|
||||
kanidm-idm person update ${escapeShellArg name} ${escapeShellArgs updateArgs}
|
||||
''
|
||||
+ flip concatMapStrings personSubmod.config.groups (group: ''
|
||||
kanidm-idm group add-members ${escapeShellArg group} ${escapeShellArg name}
|
||||
'')
|
||||
else ''
|
||||
if person_exists ${escapeShellArg name}; then
|
||||
kanidm-idm person delete ${escapeShellArg name}
|
||||
fi
|
||||
''
|
||||
);
|
||||
|
||||
present = mkPresentOption "person";
|
||||
|
||||
displayName = mkOption {
|
||||
description = mdDoc "Display name";
|
||||
type = types.str;
|
||||
example = "My User";
|
||||
};
|
||||
|
||||
legalName = mkOption {
|
||||
description = mdDoc "Full legal name";
|
||||
type = types.nullOr types.str;
|
||||
example = "Jane Doe";
|
||||
default = null;
|
||||
};
|
||||
|
||||
mailAddresses = mkOption {
|
||||
description = mdDoc "Mail addresses. First given address is considered the primary address.";
|
||||
type = types.listOf types.str;
|
||||
example = ["jane.doe@example.com"];
|
||||
default = [];
|
||||
};
|
||||
|
||||
groups = mkOption {
|
||||
description = mdDoc "List of kanidm groups to which this user belongs.";
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
};
|
||||
};
|
||||
}));
|
||||
};
|
||||
|
||||
groups = mkOption {
|
||||
description = mdDoc "Provisioning of kanidm groups";
|
||||
default = {};
|
||||
type = types.attrsOf (types.submodule (groupSubmod: let
|
||||
inherit (groupSubmod.config._module.args) name;
|
||||
in {
|
||||
options = {
|
||||
_script = mkScript (
|
||||
if groupSubmod.config.present
|
||||
then ''
|
||||
if ! group_exists ${escapeShellArg name}; then
|
||||
kanidm-admin group create ${escapeShellArg name}
|
||||
fi
|
||||
''
|
||||
else ''
|
||||
if group_exists ${escapeShellArg name}; then
|
||||
kanidm-admin group delete ${escapeShellArg name}
|
||||
fi
|
||||
''
|
||||
);
|
||||
|
||||
present = mkPresentOption "group";
|
||||
};
|
||||
}));
|
||||
};
|
||||
|
||||
systems.oauth2 = mkOption {
|
||||
description = mdDoc "Provisioning of oauth2 systems";
|
||||
default = {};
|
||||
type = types.attrsOf (types.submodule (oauth2Submod: let
|
||||
inherit (oauth2Submod.config._module.args) name;
|
||||
in {
|
||||
options = {
|
||||
_script = mkScript (
|
||||
if oauth2Submod.config.present
|
||||
then
|
||||
''
|
||||
if ! oauth2_system_exists ${escapeShellArg name}; then
|
||||
kanidm-admin system oauth2 create \
|
||||
${escapeShellArg name} \
|
||||
${escapeShellArg oauth2Submod.config.displayName} \
|
||||
${escapeShellArg oauth2Submod.config.originUrl}
|
||||
needs_rewrite=1
|
||||
fi
|
||||
''
|
||||
+ concatLines (flip mapAttrsToList oauth2Submod.config.scopeMaps (group: scopes: ''
|
||||
kanidm-admin system oauth2 update-scope-map ${escapeShellArg name} \
|
||||
${escapeShellArg group} ${escapeShellArgs scopes}
|
||||
''))
|
||||
+ concatLines (flip mapAttrsToList oauth2Submod.config.supplementaryScopeMaps (group: scopes: ''
|
||||
kanidm-admin system oauth2 update-sup-scope-map ${escapeShellArg name} \
|
||||
${escapeShellArg group} ${escapeShellArgs scopes}
|
||||
''))
|
||||
else ''
|
||||
if oauth2_system_exists ${escapeShellArg name}; then
|
||||
kanidm-admin system oauth2 delete ${escapeShellArg name}
|
||||
fi
|
||||
''
|
||||
);
|
||||
|
||||
present = mkPresentOption "oauth2 system";
|
||||
|
||||
displayName = mkOption {
|
||||
description = mdDoc "Display name";
|
||||
type = types.str;
|
||||
example = "Some Service";
|
||||
};
|
||||
|
||||
originUrl = mkOption {
|
||||
description = mdDoc "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.str;
|
||||
example = "https://someservice.example.com/";
|
||||
};
|
||||
|
||||
basicSecretFile = mkOption {
|
||||
description = mdDoc "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;
|
||||
};
|
||||
|
||||
scopeMaps = mkOption {
|
||||
description = mdDoc "Maps kanidm groups to provided 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 = mdDoc "Maps kanidm groups to provided supplementary 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 = {};
|
||||
};
|
||||
};
|
||||
}));
|
||||
};
|
||||
};
|
||||
|
||||
persons = mkOption {
|
||||
description = "Provisioning of kanidm persons";
|
||||
default = {};
|
||||
type = types.attrsOf (types.submodule (personSubmod: let
|
||||
inherit (personSubmod.module._args) name;
|
||||
updateArgs =
|
||||
["--displayname" personSubmod.config.displayName]
|
||||
++ optionals (personSubmod.config.legalName != null)
|
||||
["--legalname" personSubmod.config.legalName]
|
||||
# mail addresses
|
||||
++ concatMap (addr: ["--mail" addr]) personSubmod.config.mailAddresses;
|
||||
in {
|
||||
serverSettings = mkOption {
|
||||
type = types.submodule {
|
||||
freeformType = settingsFormat.type;
|
||||
|
||||
options = {
|
||||
_script = mkScript (
|
||||
if personSubmod.config.present
|
||||
then
|
||||
''
|
||||
if ! person_exists ${escapeShellArg name}; then
|
||||
kanidm person create ${escapeShellArg name} \
|
||||
${escapeShellArg personSubmod.config.displayName}
|
||||
fi
|
||||
kanidm person update ${escapeShellArg name} ${escapeShellArgs updateArgs}
|
||||
''
|
||||
+ flip concatMapStrings personSubmod.config.groups (group: ''
|
||||
kanidm group add-members ${escapeShellArg group} ${escapeShellArg name}
|
||||
'')
|
||||
else ''
|
||||
if oauth2_system_exists ${escapeShellArg name}; then
|
||||
kanidm group delete ${escapeShellArg name}
|
||||
fi
|
||||
''
|
||||
);
|
||||
|
||||
present = mkPresentOption "person";
|
||||
|
||||
displayName = mkOption {
|
||||
description = "Display name";
|
||||
bindaddress = mkOption {
|
||||
description = mdDoc "Address/port combination the webserver binds to.";
|
||||
example = "[::1]:8443";
|
||||
type = types.str;
|
||||
example = "My User";
|
||||
};
|
||||
|
||||
legalName = mkOption {
|
||||
description = "Full legal name";
|
||||
# Should be optional but toml does not accept null
|
||||
ldapbindaddress = mkOption {
|
||||
description = mdDoc ''
|
||||
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;
|
||||
example = "Jane Doe";
|
||||
};
|
||||
origin = mkOption {
|
||||
description = mdDoc "The origin of your Kanidm instance. Must have https as protocol.";
|
||||
example = "https://idm.example.org";
|
||||
type = types.strMatching "^https://.*";
|
||||
};
|
||||
domain = mkOption {
|
||||
description = mdDoc ''
|
||||
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;
|
||||
};
|
||||
|
||||
mailAddresses = mkOption {
|
||||
description = "Mail addresses. First given address is considered the primary address.";
|
||||
type = types.listOf types.str;
|
||||
example = ["jane.doe@example.com"];
|
||||
default = [];
|
||||
db_path = mkOption {
|
||||
description = mdDoc "Path to Kanidm database.";
|
||||
default = "/var/lib/kanidm/kanidm.db";
|
||||
readOnly = true;
|
||||
type = types.path;
|
||||
};
|
||||
|
||||
groups = mkOption {
|
||||
description = "List of kanidm groups to which this user belongs.";
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
tls_chain = mkOption {
|
||||
description = mdDoc "TLS chain in pem format.";
|
||||
type = types.path;
|
||||
};
|
||||
tls_key = mkOption {
|
||||
description = mdDoc "TLS key in pem format.";
|
||||
type = types.path;
|
||||
};
|
||||
log_level = mkOption {
|
||||
description = mdDoc "Log level of the server.";
|
||||
default = "info";
|
||||
type = types.enum ["info" "debug" "trace"];
|
||||
};
|
||||
role = mkOption {
|
||||
description = mdDoc "The role of this server. This affects the replication relationship and thereby available features.";
|
||||
default = "WriteReplica";
|
||||
type = types.enum ["WriteReplica" "WriteReplicaNoUI" "ReadOnlyReplica"];
|
||||
};
|
||||
};
|
||||
}));
|
||||
};
|
||||
default = {};
|
||||
description = mdDoc ''
|
||||
Settings for Kanidm, see
|
||||
[the documentation](https://github.com/kanidm/kanidm/blob/master/kanidm_book/src/server_configuration.md)
|
||||
and [example configuration](https://github.com/kanidm/kanidm/blob/master/examples/server.toml)
|
||||
for possible values.
|
||||
'';
|
||||
};
|
||||
|
||||
groups = mkOption {
|
||||
description = "Provisioning of kanidm groups";
|
||||
default = {};
|
||||
type = types.attrsOf (types.submodule (groupSubmod: let
|
||||
inherit (groupSubmod.module._args) name;
|
||||
in {
|
||||
options = {
|
||||
_script = mkScript (
|
||||
if groupSubmod.config.present
|
||||
then ''
|
||||
if ! group_exists ${escapeShellArg name}; then
|
||||
kanidm group create ${escapeShellArg name}
|
||||
fi
|
||||
''
|
||||
else ''
|
||||
if group_exists ${escapeShellArg name}; then
|
||||
kanidm group delete ${escapeShellArg name}
|
||||
fi
|
||||
''
|
||||
);
|
||||
clientSettings = mkOption {
|
||||
type = types.submodule {
|
||||
freeformType = settingsFormat.type;
|
||||
|
||||
present = mkPresentOption "group";
|
||||
options.uri = mkOption {
|
||||
description = mdDoc "Address of the Kanidm server.";
|
||||
example = "http://127.0.0.1:8080";
|
||||
type = types.str;
|
||||
};
|
||||
}));
|
||||
};
|
||||
description = mdDoc ''
|
||||
Configure Kanidm clients, needed for the PAM daemon. See
|
||||
[the documentation](https://github.com/kanidm/kanidm/blob/master/kanidm_book/src/client_tools.md#kanidm-configuration)
|
||||
and [example configuration](https://github.com/kanidm/kanidm/blob/master/examples/config)
|
||||
for possible values.
|
||||
'';
|
||||
};
|
||||
|
||||
systems.oauth2 = mkOption {
|
||||
description = "Provisioning of oauth2 systems";
|
||||
default = {};
|
||||
type = types.attrsOf (types.submodule (oauth2Submod: let
|
||||
inherit (oauth2Submod.module._args) name;
|
||||
in {
|
||||
options = {
|
||||
_script = mkScript (
|
||||
if oauth2Submod.config.present
|
||||
then
|
||||
''
|
||||
if ! oauth2_system_exists ${escapeShellArg name}; then
|
||||
kanidm system oauth2 create \
|
||||
${escapeShellArg name} \
|
||||
${escapeShellArg oauth2Submod.config.displayName} \
|
||||
${escapeShellArg oauth2Submod.config.originUrl}
|
||||
needs_rewrite=1
|
||||
fi
|
||||
''
|
||||
+ concatLines (flip mapAttrsToList oauth2Submod.config.scopeMaps (group: scopes: ''
|
||||
kanidm system oauth2 update-scope-map ${escapeShellArg name} \
|
||||
${escapeShellArg group} ${escapeShellArgs scopes}
|
||||
''))
|
||||
+ concatLines (flip mapAttrsToList oauth2Submod.config.supplementaryScopeMaps (group: scopes: ''
|
||||
kanidm system oauth2 update-sup-scope-map ${escapeShellArg name} \
|
||||
${escapeShellArg group} ${escapeShellArgs scopes}
|
||||
''))
|
||||
else ''
|
||||
if oauth2_system_exists ${escapeShellArg name}; then
|
||||
kanidm group delete ${escapeShellArg name}
|
||||
fi
|
||||
''
|
||||
);
|
||||
unixSettings = mkOption {
|
||||
type = types.submodule {
|
||||
freeformType = settingsFormat.type;
|
||||
|
||||
present = mkPresentOption "oauth2 system";
|
||||
|
||||
displayName = mkOption {
|
||||
description = "Display name";
|
||||
type = types.str;
|
||||
example = "Some Service";
|
||||
};
|
||||
|
||||
originUrl = 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.str;
|
||||
example = "https://someservice.example.com/";
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
scopeMaps = mkOption {
|
||||
description = "Maps kanidm groups to provided scopes. See [Scope Relations](https://kanidm.github.io/kanidm/stable/integrations/oauth2.html#scope-relationships) for more information.";
|
||||
type = types.attrsOf types.str;
|
||||
default = {};
|
||||
};
|
||||
|
||||
supplementaryScopeMaps = mkOption {
|
||||
description = "Maps kanidm groups to provided supplementary scopes. See [Scope Relations](https://kanidm.github.io/kanidm/stable/integrations/oauth2.html#scope-relationships) for more information.";
|
||||
type = types.attrsOf types.str;
|
||||
default = {};
|
||||
};
|
||||
options.pam_allowed_login_groups = mkOption {
|
||||
description = mdDoc "Kanidm groups that are allowed to login using PAM.";
|
||||
example = "my_pam_group";
|
||||
type = types.listOf types.str;
|
||||
};
|
||||
}));
|
||||
};
|
||||
description = mdDoc ''
|
||||
Configure Kanidm unix daemon.
|
||||
See [the documentation](https://github.com/kanidm/kanidm/blob/master/kanidm_book/src/pam_and_nsswitch.md#the-unix-daemon)
|
||||
and [example configuration](https://github.com/kanidm/kanidm/blob/master/examples/unixd)
|
||||
for possible values.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf (cfg.enableServer && cfg.provision.enable) {
|
||||
config = mkIf (cfg.enableClient || cfg.enableServer || cfg.enablePam) {
|
||||
assertions =
|
||||
flip mapAttrsToList cfg.provision.persons (person: personCfg: let
|
||||
[
|
||||
{
|
||||
assertion = !cfg.enableServer || ((cfg.serverSettings.tls_chain or null) == null) || (!isStorePath cfg.serverSettings.tls_chain);
|
||||
message = ''
|
||||
<option>services.kanidm.serverSettings.tls_chain</option> 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 = ''
|
||||
<option>services.kanidm.serverSettings.tls_key</option> 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 = ''
|
||||
<option>services.kanidm.clientSettings</option> needs to be configured
|
||||
if the client is enabled.
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion = !cfg.enablePam || options.services.kanidm.clientSettings.isDefined;
|
||||
message = ''
|
||||
<option>services.kanidm.clientSettings</option> 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 = ''
|
||||
<option>services.kanidm.serverSettings.domain</option> 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 = "<option>services.kanidm.provision</option> requires <option>services.kanidm.enableServer</option> to be true";
|
||||
}
|
||||
{
|
||||
assertion = cfg.provision.enable -> cfg.enableClient;
|
||||
message = "<option>services.kanidm.provision</option> requires <option>services.kanidm.enableClient</option> to be able to use the kanidm client locally for provisioning.";
|
||||
}
|
||||
]
|
||||
++ flip mapAttrsToList cfg.provision.persons (person: personCfg: let
|
||||
unknownGroups = subtractLists (attrNames cfg.provision.groups) personCfg.groups;
|
||||
in {
|
||||
assertion = unknownGroups == [];
|
||||
assertion = (cfg.enableServer && cfg.provision.enable) -> unknownGroups == [];
|
||||
message = "kanidm: provision.persons.${person}.groups: Refers to unknown groups: ${unknownGroups}";
|
||||
})
|
||||
+ concatLists (flip mapAttrsToList cfg.provision.systems.oauth2 (oauth2: oauth2Cfg: [
|
||||
++ concatLists (flip mapAttrsToList cfg.provision.systems.oauth2 (oauth2: oauth2Cfg: [
|
||||
{
|
||||
assertion = (cfg.enableServer && cfg.provision.enable) -> hasInfix "://" oauth2Cfg.originUrl;
|
||||
message = "kanidm: provision.systems.oauth2.${oauth2}.originUrl: Missing a schema like 'https://': ${oauth2Cfg.originUrl}";
|
||||
}
|
||||
(let
|
||||
unknownGroups = subtractLists (attrNames cfg.provision.groups) (attrNames oauth2Cfg.scopeMaps);
|
||||
in {
|
||||
assertion = unknownGroups == [];
|
||||
assertion = (cfg.enableServer && cfg.provision.enable) -> unknownGroups == [];
|
||||
message = "kanidm: provision.systems.oauth2.${oauth2}.scopeMaps: Refers to unknown groups: ${unknownGroups}";
|
||||
})
|
||||
(let
|
||||
unknownGroups = subtractLists (attrNames cfg.provision.groups) (attrNames oauth2Cfg.supplementaryScopeMaps);
|
||||
in {
|
||||
assertion = unknownGroups == [];
|
||||
assertion = (cfg.enableServer && cfg.provision.enable) -> unknownGroups == [];
|
||||
message = "kanidm: provision.systems.oauth2.${oauth2}.supplementaryScopeMaps: Refers to unknown groups: ${unknownGroups}";
|
||||
})
|
||||
]));
|
||||
|
||||
systemd.services.kanidm = {
|
||||
serviceConfig.ExecStartPost =
|
||||
[provisioningScript]
|
||||
# Only the restarter runs with elevated privileges
|
||||
++ optional (cfg.provision.systems.oauth2 != {}) "+${restarterScript}";
|
||||
environment.systemPackages = mkIf cfg.enableClient [cfg.package];
|
||||
|
||||
preStart = let
|
||||
mappingsJson = pkgs.writeText "mappings.json" (builtins.toJSON {
|
||||
account_secrets.admin = cfg.provision.adminPasswordFile;
|
||||
oauth2_basic_secrets = mapAttrs (_: x: v.basicSecretFile) cfg.provision.systems.oauth2;
|
||||
});
|
||||
in ''
|
||||
if ! test -e ${escapeShellArg cfg.serverSettings.db_path}; then
|
||||
touch "$STATE_DIRECTORY/.first_startup"
|
||||
else
|
||||
${getExe pkgs.kanidm-secret-manipulator} ${escapeShellArg cfg.serverSettings.db_path} ${tokenMappings}
|
||||
fi
|
||||
'';
|
||||
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
|
||||
# If provisioning is enabled, we need access to the client config to use the kanidm cli,
|
||||
# and to the installed system certificates.
|
||||
++ optionals cfg.provision.enable [
|
||||
"-/etc/ssl/certs"
|
||||
"-/etc/static/ssl/certs"
|
||||
"-/etc/kanidm"
|
||||
"-/etc/static/kanidm"
|
||||
]
|
||||
);
|
||||
})
|
||||
{
|
||||
StateDirectory = "kanidm";
|
||||
StateDirectoryMode = "0700";
|
||||
RuntimeDirectory = "kanidmd";
|
||||
ExecStartPre = mkIf cfg.provision.enable [preStartScript];
|
||||
ExecStart = "${cfg.package}/bin/kanidmd server -c ${serverConfigFile}";
|
||||
ExecStartPost =
|
||||
mkIf cfg.provision.enable
|
||||
[
|
||||
postStartScript
|
||||
# Only the restarter runs with elevated privileges
|
||||
"+${restarterScript}"
|
||||
];
|
||||
User = "kanidm";
|
||||
Group = "kanidm";
|
||||
|
||||
BindPaths = [
|
||||
# To create the socket
|
||||
"/run/kanidmd:/run/kanidmd"
|
||||
"/run/dbus/system_bus_socket"
|
||||
];
|
||||
|
||||
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; [erictapen Flakebi oddlama];
|
||||
meta.buildDocsInSandbox = false;
|
||||
}
|
||||
|
|
|
@ -65,16 +65,11 @@ in {
|
|||
group = "influxdb2";
|
||||
};
|
||||
|
||||
services.influxdb2.provision.ensureApiTokens = [
|
||||
{
|
||||
name = "telegraf (${config.node.name})";
|
||||
org = "servers";
|
||||
user = "admin";
|
||||
readBuckets = ["telegraf"];
|
||||
writeBuckets = ["telegraf"];
|
||||
tokenFile = nodes.${cfg.influxdb2.node}.config.age.secrets."telegraf-influxdb-token-${config.node.name}".path;
|
||||
}
|
||||
];
|
||||
services.influxdb2.provision.organization.servers.auths."telegraf (${config.node.name})" = {
|
||||
readBuckets = ["telegraf"];
|
||||
writeBuckets = ["telegraf"];
|
||||
tokenFile = nodes.${cfg.influxdb2.node}.config.age.secrets."telegraf-influxdb-token-${config.node.name}".path;
|
||||
};
|
||||
};
|
||||
|
||||
age.secrets.telegraf-influxdb-token = {
|
||||
|
|
|
@ -47,6 +47,6 @@ in {
|
|||
networking.providedDomains = mergeFromOthers ["networking" "providedDomains"];
|
||||
services.nginx.upstreams = mergeFromOthers ["services" "nginx" "upstreams"];
|
||||
services.nginx.virtualHosts = mergeFromOthers ["services" "nginx" "virtualHosts"];
|
||||
services.influxdb2.provision.ensureApiTokens = mergeFromOthers ["services" "influxdb2" "provision" "ensureApiTokens"];
|
||||
services.influxdb2.provision.organizations = mergeFromOthers ["services" "influxdb2" "organizations"];
|
||||
};
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ rustPlatform.buildRustPackage rec {
|
|||
owner = "oddlama";
|
||||
repo = "kanidm-secret-manipulator";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-mLOTnOsbUozYRDBXVYtIrUE5jDXEqW8HMO57qWsp1Go=";
|
||||
hash = "sha256-Hn/143YJ0rn9AihuI/wsDlqtnGi/LBzbfdMNTukc34c=";
|
||||
};
|
||||
|
||||
cargoHash = "sha256-L//ZtfbOxV6Hf5x5tLAQ52MChSclzJlhI7sZKqvByMo=";
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue