Compare commits
No commits in common. "37889dbdd3385376b6264dbc4861f31bc46120ef" and "2cbc6fa4383150888e16dbe83f1160bf2e95183d" have entirely different histories.
37889dbdd3
...
2cbc6fa438
|
@ -8,7 +8,7 @@
|
|||
...
|
||||
}: let
|
||||
adguardhomedomain = "adguardhome.${config.secrets.secrets.global.domains.web}";
|
||||
forgejoDomain = "git.${config.secrets.secrets.global.domains.web}";
|
||||
giteadomain = "git.${config.secrets.secrets.global.domains.web}";
|
||||
immichdomain = "immich.${config.secrets.secrets.global.domains.web}";
|
||||
nextclouddomain = "nc.${config.secrets.secrets.global.domains.web}";
|
||||
ollamadomain = "ollama.${config.secrets.secrets.global.domains.web}";
|
||||
|
@ -44,19 +44,19 @@ in {
|
|||
'';
|
||||
};
|
||||
|
||||
upstreams.forgejo = {
|
||||
servers."${ipOf "forgejo"}:3000" = {};
|
||||
upstreams.gitea = {
|
||||
servers."${ipOf "gitea"}:3000" = {};
|
||||
|
||||
extraConfig = ''
|
||||
zone forgejo 64k ;
|
||||
zone gitea 64k ;
|
||||
keepalive 5 ;
|
||||
'';
|
||||
};
|
||||
virtualHosts.${forgejoDomain} = {
|
||||
virtualHosts.${giteadomain} = {
|
||||
forceSSL = true;
|
||||
useACMEHost = "web";
|
||||
locations."/" = {
|
||||
proxyPass = "http://forgejo";
|
||||
proxyPass = "http://gitea";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
extraConfig = ''
|
||||
|
@ -326,7 +326,7 @@ in {
|
|||
// mkContainer "paperless" {
|
||||
enableSharedPaperless = true;
|
||||
}
|
||||
// mkContainer "forgejo" {
|
||||
// mkContainer "gitea" {
|
||||
enablePanzer = true;
|
||||
}
|
||||
// mkMicrovm "immich" {
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
age-encryption.org/v1
|
||||
-> X25519 gc7a2OUDl5ZLAWsWvg+Vq8OjjQ8gybzm6FFqUWCelWE
|
||||
BMYy6ekQeRNwNyUMZeSYH2ff5+1dfy+2A885JR5XRXo
|
||||
-> piv-p256 XTQkUA A0N6GlqsddLw7SHSBKQ8XdYJD/q42y9DY7A4DnAq8cHQ
|
||||
syaYAv5cXNcXZRs+Nzwj14Eai0EHGjtu3o3IBt1dg5k
|
||||
-> piv-p256 ZFgiIw A7zvUk0WKnNZG8fHeTTOqNAR7Gf1SVvEVINMOGH6fetZ
|
||||
6R2zFSXZXDFRhGg8+AFBQvTKWbDPZIYgkDY19cxmqi4
|
||||
-> piv-p256 5vmPtQ AoQwhdsUwjX8Dn63egOz92T+XNt5/MMxsL5T+7o1Jrvz
|
||||
TnN51QUPA1wcHBCR6KYT0xFH5WVT+FAI/QhMTW3ajGE
|
||||
-> piv-p256 ZFgiIw Azh9/Kn00w3JVQvlarDwwrXRnOGeTHYHtEF2gK6i2qW+
|
||||
gIp88ToCDc80WlJikNyKw8up+GWWexzMoaQVuLBRIfQ
|
||||
-> Zr~B*Za-grease
|
||||
20yMZ5Gv9nTK1qQVWe7lSAspqwdpUGVnX4r8M8UNvaQGNbSDFghj
|
||||
--- BleVfNvY9wSlTcM42gt7rKRxmDG4LUlWHHi3vWgz6Og
|
||||
8¨ð#e¢ó`¤½Í\ß|<7C>¤ã5Ø&\<5C>®0…#áºHZrnF¬“b<"¶mùšSP²úýãébLµ¿Õ†fVs.ù–¶?<3F>Üåän<>‘#\0ðÌw/Ïì.áh}w…›S¥"Eé*Ö¶<>ã’SÂ>׿huÜn)맃¿ÓÝE‘ãRðpBÀG<>àãà#!n4¡¥¾CKVÐöÇ?
|
16
hosts/elisabeth/secrets/gitea/generated/openid-secret.age
Normal file
16
hosts/elisabeth/secrets/gitea/generated/openid-secret.age
Normal file
|
@ -0,0 +1,16 @@
|
|||
age-encryption.org/v1
|
||||
-> X25519 nbSYyNcboZYI9JVKkImhppQToS3XQxjiriBS0TAQrE4
|
||||
/g8LhlSY1YxQQo47FuzzXNPTVsnBgC+TRh9BBq3WVLI
|
||||
-> piv-p256 XTQkUA AyItco2APIdcG+okYszuK5aqGOlkfD7at+sG53awGFAQ
|
||||
KTD5Nr2u6U6oVEYDNDLO8U3zVMLWypHHS0Y+9Q36TGM
|
||||
-> piv-p256 ZFgiIw AhZRrPWodlNzQAMgFZDOiamtdsWE8vtjGXNpbGsCLnpY
|
||||
MmHIE4jSpYZFseJpG2Tdj+CnqjIkk/dSUoOJaRoiWe0
|
||||
-> piv-p256 5vmPtQ AvgHDtaekIaAbj7gmUJP0xWwOpESKSgQAUd4voq2ee9c
|
||||
XIFV78uWrOgJrQ7cpYmIrMsGpkji1GqvSuAIKRq+Tt0
|
||||
-> piv-p256 ZFgiIw ApCcEoQQOcikT1hANSjOmzLiL6tNhk2vJ96HDNtJuMpd
|
||||
geXW5whCUN4Hr5zvko5B94aHhirsmUXpoMBGKrZtZGA
|
||||
-> '@Uo0`]-grease 5:un}:j
|
||||
8NEXuaIP6w
|
||||
--- jQD1YtAJ15d+eIy6nPqHHUMTLJtgAg/+5sqQbw0Q0Ko
|
||||
ë„sσŸ„ú=]Ý%ʹîÊL ÞÛÀËlÈáÑieMá—þŒØ\Y²CoÁˆ-sœ†«4M
|
||||
$\¡ÿwµ,šM‡g´tõñ 4Ë•#V<>p
|
BIN
hosts/elisabeth/secrets/gitea/secrets.nix.age
Normal file
BIN
hosts/elisabeth/secrets/gitea/secrets.nix.age
Normal file
Binary file not shown.
|
@ -1,16 +0,0 @@
|
|||
age-encryption.org/v1
|
||||
-> X25519 vc309RP8AQvszDq+D1f4f5dWrqOpjt2lKmci9507CUQ
|
||||
TKV60m9WQendmKOKVJuM6JvvQPb7dsAO8+Tn3NNjdt4
|
||||
-> piv-p256 XTQkUA A/zSSbrKLxl31vDGaKfSTqJfX/I0ESYjApL1FIcwbRT/
|
||||
f7tAcOo7eDCACUjO++5Ca8UYsEMyfEosMEhoRyZlv2s
|
||||
-> piv-p256 ZFgiIw AveXSkbOt33wUXtCc1IDmssYIJfUOSVm/zamehQDBaN6
|
||||
Cq7pLQL5ED3lSZrive0vyoipe1PN4FpbNAKDYIHWE1U
|
||||
-> piv-p256 5vmPtQ Atez78iMxWLAq114XO7wwU1JiSTU50wfohvJ0LQUL5JI
|
||||
oHv2dlB91XwPpL8OXFZyCE4CEGe7H+OKkef3m178a/w
|
||||
-> piv-p256 ZFgiIw AjTxVdEB+Wck0N07i2nvkWtzH13Vfznl7TqS6kqwvGet
|
||||
gN2xJG0OOfqtimsf9qwoYk8fB4A5GQILt6kWScZ+ono
|
||||
-> WR-grease ]x3SA4 w6 7\t D2b
|
||||
4QgiTLjrIaQBKUc3cNs0+Hyqo9ccxdAstepUX1Ps4HLqGBNrIUkM2Q
|
||||
--- CylSm6+Ki9f+ilXS7UTvDxDlLNLLvRaJNOw9kHt6TVk
|
||||
(<28>ó˜Fg*î†7ÝÒ"¢‡‚<E280A1>ä[šÌ¸…¼f<66>H®¶}¿Æ²Ìì†c*—l§JR˜cÁŽ}5ò™¬Ül
|
||||
§·ç}½ãáŸ"S™„ØÏ
|
|
@ -21,7 +21,7 @@
|
|||
nextcloud = uidGid 213;
|
||||
redis-nextcloud = uidGid 214;
|
||||
radicale = uidGid 215;
|
||||
forgejo = uidGid 215;
|
||||
gitea = uidGid 215;
|
||||
vaultwarden = uidGid 215;
|
||||
redis-paperless = uidGid 216;
|
||||
microvm = uidGid 217;
|
||||
|
@ -40,6 +40,5 @@
|
|||
helen = uidGid 2001;
|
||||
ggr = uidGid 2002;
|
||||
family = uidGid 2003;
|
||||
printer = uidGid 2005;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,854 +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
|
||||
mdDoc
|
||||
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 (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 = mdDoc "Whether to ensure that this ${what} is present or absent.";
|
||||
type = types.bool;
|
||||
default = true;
|
||||
};
|
||||
|
||||
filterPresent = filterAttrs (_: v: v.present);
|
||||
|
||||
provisionStateJson = pkgs.writeText "provision-state.json" (builtins.toJSON {
|
||||
inherit (cfg.provision) groups persons systems;
|
||||
});
|
||||
|
||||
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";
|
||||
|
||||
# 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
|
||||
'';
|
||||
|
||||
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
|
||||
sleep 0.1
|
||||
if [[ "$count" -eq 600 ]]; then
|
||||
echo "Tried for 60 seconds, giving up..."
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -d "/proc/$MAINPID" ]]; then
|
||||
echo "Main server died, giving up..."
|
||||
exit 1
|
||||
fi
|
||||
count=$((count++))
|
||||
done
|
||||
|
||||
${recoverIdmAdmin}
|
||||
${maybeRecoverAdmin}
|
||||
|
||||
KANIDM_PROVISION_IDM_ADMIN_TOKEN=$KANIDM_IDM_ADMIN_PASSWORD \
|
||||
${getExe pkgs.kanidm-provision} --url "${cfg.provision.instanceUrl}" --state ${provisionStateJson} ${optionalString cfg.provision.acceptInvalidCerts "--accept-invalid-certs"}
|
||||
'';
|
||||
in {
|
||||
options.services.kanidm = {
|
||||
enableClient = mkEnableOption (mdDoc "the Kanidm client");
|
||||
enableServer = mkEnableOption (mdDoc "the Kanidm server");
|
||||
enablePam = mkEnableOption (mdDoc "the Kanidm PAM and NSS integration");
|
||||
|
||||
package = mkPackageOption pkgs "kanidm" {};
|
||||
|
||||
serverSettings = mkOption {
|
||||
type = types.submodule {
|
||||
freeformType = settingsFormat.type;
|
||||
|
||||
options = {
|
||||
bindaddress = mkOption {
|
||||
description = mdDoc "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 = 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;
|
||||
};
|
||||
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;
|
||||
};
|
||||
db_path = mkOption {
|
||||
description = mdDoc "Path to Kanidm database.";
|
||||
default = "/var/lib/kanidm/kanidm.db";
|
||||
readOnly = true;
|
||||
type = types.path;
|
||||
};
|
||||
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"];
|
||||
};
|
||||
online_backup = {
|
||||
path = mkOption {
|
||||
description = mdDoc "Path to the output directory for backups.";
|
||||
type = types.path;
|
||||
default = "/var/lib/kanidm/backups";
|
||||
};
|
||||
schedule = mkOption {
|
||||
description = mdDoc "The schedule for backups in cron format.";
|
||||
type = types.str;
|
||||
default = "00 22 * * *";
|
||||
};
|
||||
versions = mkOption {
|
||||
description = mdDoc ''
|
||||
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 = mdDoc ''
|
||||
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 = 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://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 = mdDoc "Kanidm groups that are allowed to login using PAM.";
|
||||
example = "my_pam_group";
|
||||
type = types.listOf types.str;
|
||||
};
|
||||
hsm_pin_path = mkOption {
|
||||
description = mdDoc "Path to a HSM pin.";
|
||||
default = "/var/cache/kanidm-unixd/hsm-pin";
|
||||
type = types.path;
|
||||
};
|
||||
};
|
||||
};
|
||||
description = mdDoc ''
|
||||
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:<port from serverSettings.bindaddress>"'';
|
||||
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";
|
||||
|
||||
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. Must end with a slash.";
|
||||
type = types.strMatching ".*://.*/$";
|
||||
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.nullOr types.str;
|
||||
default = null;
|
||||
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;
|
||||
};
|
||||
|
||||
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!
|
||||
'';
|
||||
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);
|
||||
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 = ''
|
||||
<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";
|
||||
}
|
||||
# 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";
|
||||
}
|
||||
]))
|
||||
));
|
||||
|
||||
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; [erictapen Flakebi oddlama];
|
||||
meta.buildDocsInSandbox = false;
|
||||
}
|
|
@ -14,15 +14,7 @@
|
|||
settingsFormat = pkgs.formats.json {};
|
||||
in {
|
||||
home-manager.sharedModules = [
|
||||
({config, ...}: let
|
||||
cfg = settingsFormat.generate "config.json" {
|
||||
streamdeck_ui_version = 1;
|
||||
state = config.programs.streamdeck-ui.settings;
|
||||
};
|
||||
preStart = pkgs.writeShellScript "streamdeck-setup-config" ''
|
||||
cp "${cfg}" "$XDG_RUNTIME_DIR/streamdeck/config.json"
|
||||
'';
|
||||
in {
|
||||
({config, ...}: {
|
||||
options.programs.streamdeck-ui = {
|
||||
enable = mkEnableOption "streamdeck-ui";
|
||||
package = mkPackageOption pkgs "streamdeck-ui" {};
|
||||
|
@ -44,14 +36,20 @@ in {
|
|||
Service = {
|
||||
Type = "exec";
|
||||
ExecStart = "${pkgs.streamdeck-ui}/bin/streamdeck --no-ui";
|
||||
ExecStartPre = preStart;
|
||||
Environment = ''STREAMDECK_UI_CONFIG=%t/streamdeck/config.json'';
|
||||
RuntimeDirectory = "streamdeck";
|
||||
Environment = "STREAMDECK_UI_CONFIG=${config.xdg.configHome}/streamdeck-ui/config.json";
|
||||
};
|
||||
Install.WantedBy = ["graphical-session.target"];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
xdg.configFile.streamdeck-ui = {
|
||||
target = "streamdeck-ui/config.json";
|
||||
source = settingsFormat.generate "config.json" {
|
||||
streamdeck_ui_version = 1;
|
||||
state = config.programs.streamdeck-ui.settings;
|
||||
};
|
||||
};
|
||||
};
|
||||
})
|
||||
];
|
||||
|
|
104
modules/services/authelia.nix
Normal file
104
modules/services/authelia.nix
Normal file
|
@ -0,0 +1,104 @@
|
|||
{
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
stateDir = "/var/lib/authelia-main";
|
||||
in {
|
||||
age.secrets.jwtSecretFile = {
|
||||
generator.script = "alnum";
|
||||
mode = "440";
|
||||
inherit (config.services.authelia.instances.main) group;
|
||||
};
|
||||
|
||||
age.secrets.sessionSecretFile = {
|
||||
generator.script = "alnum";
|
||||
mode = "440";
|
||||
inherit (config.services.authelia.instances.main) group;
|
||||
};
|
||||
|
||||
age.secrets.storageEncryptionKeyFile = {
|
||||
generator.script = "alnum";
|
||||
mode = "440";
|
||||
inherit (config.services.authelia.instances.main) group;
|
||||
};
|
||||
|
||||
age.secrets.oidcHmacSecretFile = {
|
||||
generator.script = "alnum";
|
||||
mode = "440";
|
||||
inherit (config.services.authelia.instances.main) group;
|
||||
};
|
||||
|
||||
age.secrets.oidcIssuerPrivateKeyFile = {
|
||||
generator.script = {pkgs, ...}: ''
|
||||
${pkgs.openssl}/bin/openssl genrsa 4096
|
||||
'';
|
||||
mode = "440";
|
||||
inherit (config.services.authelia.instances.main) group;
|
||||
};
|
||||
networking.firewall.allowedTCPPorts = [config.services.authelia.instances.main.settings.server.port];
|
||||
|
||||
services.authelia.instances.main = {
|
||||
enable = true;
|
||||
secrets = {
|
||||
jwtSecretFile = config.age.secrets.jwtSecretFile.path;
|
||||
sessionSecretFile = config.age.secrets.sessionSecretFile.path;
|
||||
storageEncryptionKeyFile = config.age.secrets.storageEncryptionKeyFile.path;
|
||||
oidcHmacSecretFile = config.age.secrets.oidcHmacSecretFile.path;
|
||||
oidcIssuerPrivateKeyFile = config.age.secrets.oidcIssuerPrivateKeyFile.path;
|
||||
}; # TODO
|
||||
settings = {
|
||||
session = {
|
||||
domain = config.secrets.secrets.global.domains.web;
|
||||
};
|
||||
webauthn.disable = true;
|
||||
duo_api.disable = true;
|
||||
ntp.disable_startup_check = true;
|
||||
theme = "dark";
|
||||
default_2fa_method = "totp";
|
||||
server.host = "0.0.0.0";
|
||||
access_control.default_policy = "one_factor";
|
||||
webauthn = {
|
||||
attestation_conveyance_preference = "none";
|
||||
user_verification = "discouraged";
|
||||
};
|
||||
|
||||
authentication_backend = {
|
||||
password_reset.disable = true;
|
||||
file = {
|
||||
path = pkgs.writeText "user-db" (builtins.toJSON {
|
||||
users.patrick = {
|
||||
disabled = false;
|
||||
displayname = "Patrick";
|
||||
password = "$argon2id$v=19$m=4096,t=3,p=1$Ym5yc3VhZHJub2I$ihbPHC697Nybk1H7WHCMKi+2KkvNhdwvScaorkkj5yM";
|
||||
email = "patrick@${config.secrets.secrets.global.domains.mail_public}";
|
||||
groups = ["admin" "forgejo_admin"];
|
||||
};
|
||||
users.test = {
|
||||
disabled = false;
|
||||
displayname = "Test";
|
||||
password = "$argon2id$v=19$m=4096,t=3,p=1$cmJuaWJldGRheA$kG4NCJRryXTCe/8Jc2/BBnEmlWSRwq4pZG7LH7fKs/o";
|
||||
email = "test@${config.secrets.secrets.global.domains.mail_public}";
|
||||
groups = [];
|
||||
};
|
||||
});
|
||||
};
|
||||
};
|
||||
password_policy.standard = {
|
||||
enabled = true;
|
||||
min_length = 32;
|
||||
};
|
||||
notifier.filesystem.filename = "${stateDir}/notifications.txt";
|
||||
storage.local.path = "${stateDir}/db.sqlite3";
|
||||
identity_providers.oidc.clients = [
|
||||
{
|
||||
id = "forgejo";
|
||||
secret = "$argon2id$v=19$m=4096,t=3,p=1$Ym5yc3VhZHJub2I$0gZRilVu8O1rmVxX+ZTMFFHqya6YN8l+8QXFIorhtKM";
|
||||
redirect_uris = ["https://git.${config.secrets.secrets.global.domains.web}/user/oauth2/authelia/callback"];
|
||||
public = false;
|
||||
scopes = ["openid" "email" "profile" "groups"];
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
|
@ -1,11 +1,10 @@
|
|||
{
|
||||
config,
|
||||
nodes,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}: let
|
||||
forgejoDomain = "git.${config.secrets.secrets.global.domains.web}";
|
||||
giteaDomain = "git.${config.secrets.secrets.global.domains.web}";
|
||||
in {
|
||||
age.secrets.resticpasswd = {
|
||||
generator.script = "alnum";
|
||||
|
@ -29,7 +28,7 @@ in {
|
|||
inherit (config.secrets.secrets.global.hetzner.users.forgejo) subUid path;
|
||||
sshAgeSecret = "forgejoHetznerSsh";
|
||||
};
|
||||
paths = [config.services.forgejo.stateDir];
|
||||
paths = [config.services.gitea.stateDir];
|
||||
pruneOpts = [
|
||||
"--keep-daily 10"
|
||||
"--keep-weekly 7"
|
||||
|
@ -45,27 +44,29 @@ in {
|
|||
|
||||
environment.persistence."/panzer".directories = [
|
||||
{
|
||||
directory = config.services.forgejo.stateDir;
|
||||
user = "forgejo";
|
||||
group = "forgejo";
|
||||
directory = config.services.gitea.stateDir;
|
||||
user = "gitea";
|
||||
group = "gitea";
|
||||
mode = "0700";
|
||||
}
|
||||
];
|
||||
age.secrets.forgejo-mailer-passwd = {
|
||||
rekeyFile = config.node.secretsDir + "/forgejo-passwd.age";
|
||||
owner = "forgejo";
|
||||
group = "forgejo";
|
||||
age.secrets.gitea-mailer-passwd = {
|
||||
rekeyFile = config.node.secretsDir + "/gitea-passwd.age";
|
||||
owner = "gitea";
|
||||
group = "gitea";
|
||||
mode = "0700";
|
||||
};
|
||||
|
||||
services.forgejo = {
|
||||
services.gitea = {
|
||||
enable = true;
|
||||
package = pkgs.forgejo;
|
||||
appName = "Patricks tolles git"; # tungsten inert gas?
|
||||
stateDir = "/var/lib/forgejo";
|
||||
# TODO db backups
|
||||
# dump.enable = true;
|
||||
lfs.enable = true;
|
||||
mailerPasswordFile = config.age.secrets.forgejo-mailer-passwd.path;
|
||||
mailerPasswordFile = config.age.secrets.gitea-mailer-passwd.path;
|
||||
settings = {
|
||||
DEFAULT.APP_NAME = "Patricks tolles git"; # tungsten inert gas?
|
||||
actions = {
|
||||
ENABLED = true;
|
||||
DEFAULT_ACTIONS_URL = "github";
|
||||
|
@ -77,17 +78,17 @@ in {
|
|||
# federation.ENABLED = true;
|
||||
mailer = {
|
||||
ENABLED = true;
|
||||
SMTP_ADDR = config.secrets.secrets.local.forgejo.mail.host;
|
||||
FROM = config.secrets.secrets.local.forgejo.mail.from;
|
||||
USER = config.secrets.secrets.local.forgejo.mail.user;
|
||||
SMTP_ADDR = config.secrets.secrets.local.gitea.mail.host;
|
||||
FROM = config.secrets.secrets.local.gitea.mail.from;
|
||||
USER = config.secrets.secrets.local.gitea.mail.user;
|
||||
SEND_AS_PLAIN_TEXT = true;
|
||||
};
|
||||
oauth2_client = {
|
||||
ACCOUNT_LINKING = "login";
|
||||
ENABLE_AUTO_REGISTRATION = false;
|
||||
ENABLE_AUTO_REGISTRATION = true;
|
||||
REGISTER_EMAIL_CONFIRM = false;
|
||||
UPDATE_AVATAR = true;
|
||||
USERNAME = "nickname";
|
||||
USERNAME = "email";
|
||||
};
|
||||
# packages.ENABLED = true;
|
||||
repository = {
|
||||
|
@ -98,8 +99,8 @@ in {
|
|||
server = {
|
||||
HTTP_ADDR = "0.0.0.0";
|
||||
HTTP_PORT = 3000;
|
||||
DOMAIN = forgejoDomain;
|
||||
ROOT_URL = "https://${forgejoDomain}/";
|
||||
DOMAIN = giteaDomain;
|
||||
ROOT_URL = "https://${giteaDomain}/";
|
||||
LANDING_PAGE = "login";
|
||||
SSH_PORT = 9922;
|
||||
# TODO
|
||||
|
@ -107,9 +108,9 @@ in {
|
|||
# port forwarding in elisabeth
|
||||
};
|
||||
service = {
|
||||
DISABLE_REGISTRATION = false;
|
||||
DISABLE_REGISTRATION = true;
|
||||
ALLOW_ONLY_EXTERNAL_REGISTRATION = true;
|
||||
SHOW_REGISTRATION_BUTTON = false;
|
||||
SHOW_REGISTRATION_BUTTON = true;
|
||||
REGISTER_EMAIL_CONFIRM = false;
|
||||
ENABLE_NOTIFY_MAIL = true;
|
||||
DEFAULT_KEEP_EMAIL_PRIVATE = true;
|
||||
|
@ -125,11 +126,13 @@ in {
|
|||
|
||||
# XXX: PKCE is currently not supported by gitea/forgejo,
|
||||
# see https://github.com/go-gitea/gitea/issues/21376.
|
||||
systemd.services.forgejo = {
|
||||
# Disable PKCE manually in kanidm for now.
|
||||
# `kanidm system oauth2 warning-insecure-client-disable-pkce forgejo`
|
||||
systemd.services.gitea = {
|
||||
serviceConfig.RestartSec = "600"; # Retry every 10 minutes
|
||||
preStart = let
|
||||
exe = lib.getExe config.services.forgejo.package;
|
||||
providerName = "kanidm";
|
||||
exe = lib.getExe config.services.gitea.package;
|
||||
providerName = "authelia";
|
||||
clientId = "forgejo";
|
||||
args = lib.escapeShellArgs [
|
||||
"--name"
|
||||
|
@ -140,14 +143,18 @@ in {
|
|||
clientId
|
||||
"--auto-discover-url"
|
||||
"https://auth.${config.secrets.secrets.global.domains.web}/oauth2/openid/${clientId}/.well-known/openid-configuration"
|
||||
"--required-claim-name"
|
||||
"groups"
|
||||
"--scopes"
|
||||
"email"
|
||||
"--scopes"
|
||||
"profile"
|
||||
"--scopes"
|
||||
"groups"
|
||||
"--group-claim-name"
|
||||
"groups"
|
||||
"--admin-group"
|
||||
"admin"
|
||||
"forgejo_admin"
|
||||
"--skip-local-2fa"
|
||||
];
|
||||
in
|
||||
|
@ -163,8 +170,8 @@ in {
|
|||
};
|
||||
|
||||
age.secrets.openid-secret = {
|
||||
inherit (nodes.elisabeth-kanidm.config.age.secrets.oauth2-forgejo) rekeyFile;
|
||||
generator.script = "alnum";
|
||||
mode = "440";
|
||||
inherit (config.services.forgejo) group;
|
||||
inherit (config.services.gitea) group;
|
||||
};
|
||||
}
|
|
@ -1,8 +1,6 @@
|
|||
{config, ...}: let
|
||||
kanidmdomain = "auth.${config.secrets.secrets.global.domains.web}";
|
||||
in {
|
||||
imports = [../kanidm.nix];
|
||||
disabledModules = ["services/security/kanidm.nix"];
|
||||
networking.firewall.allowedTCPPorts = [3000];
|
||||
environment.persistence."/persist".directories = [
|
||||
{
|
||||
|
@ -23,11 +21,6 @@ in {
|
|||
group = "kanidm";
|
||||
mode = "440";
|
||||
};
|
||||
oauth2-forgejo = {
|
||||
generator.script = "alnum";
|
||||
mode = "440";
|
||||
group = "kanidm";
|
||||
};
|
||||
};
|
||||
services.kanidm = {
|
||||
enableServer = true;
|
||||
|
@ -45,39 +38,5 @@ in {
|
|||
verify_ca = true;
|
||||
verify_hostnames = true;
|
||||
};
|
||||
provision = {
|
||||
enable = true;
|
||||
|
||||
persons = {
|
||||
"patrick" = {
|
||||
displayName = "Patrick";
|
||||
mailAddresses = ["patrick@${config.secrets.secrets.global.domains.mail}"];
|
||||
groups = ["forgejo.admins"];
|
||||
};
|
||||
"test" = {
|
||||
displayName = "test";
|
||||
mailAddresses = ["test@${config.secrets.secrets.global.domains.mail}"];
|
||||
groups = ["forgejo.access"];
|
||||
};
|
||||
};
|
||||
|
||||
groups."forgejo.access" = {
|
||||
members = ["forgejo.admins"];
|
||||
};
|
||||
groups."forgejo.admins" = {};
|
||||
systems.oauth2.forgejo = {
|
||||
displayName = "Forgejo";
|
||||
originUrl = "https://git.${config.secrets.secrets.global.domains.web}/";
|
||||
basicSecretFile = config.age.secrets.oauth2-forgejo.path;
|
||||
scopeMaps."forgejo.access" = ["openid" "email" "profile"];
|
||||
allowInsecureClientDisablePkce = true;
|
||||
preferShortUsername = true;
|
||||
claimMaps.groups = {
|
||||
joinType = "array";
|
||||
valuesByGroup."forgejo.admins" = ["admin"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
systemd.services.kanidm.serviceConfig.RestartSec = "60"; # Retry every minute
|
||||
}
|
||||
|
|
|
@ -168,11 +168,6 @@
|
|||
hasBunker = true;
|
||||
hasPaperless = true;
|
||||
} {})
|
||||
(mkShare {
|
||||
name = "printer";
|
||||
user = "printer";
|
||||
group = "printer";
|
||||
} {})
|
||||
(mkShare {
|
||||
name = "family-data";
|
||||
user = "family";
|
||||
|
@ -216,12 +211,7 @@
|
|||
${group} = {
|
||||
};
|
||||
}))
|
||||
++ [
|
||||
{
|
||||
family.members = ["patrick" "david" "helen" "ggr"];
|
||||
printer.members = ["patrick" "david" "helen" "ggr"];
|
||||
}
|
||||
]);
|
||||
++ [{family.members = ["patrick" "david" "helen" "ggr"];}]);
|
||||
};
|
||||
|
||||
fileSystems = lib.mkMerge (lib.flip lib.mapAttrsToList config.services.samba.shares (_: v:
|
||||
|
|
|
@ -16,23 +16,5 @@
|
|||
wrapProgram $out/bin/nvim --add-flags "--clean"
|
||||
'';
|
||||
});
|
||||
kanidm = super.kanidm.overrideAttrs (old: let
|
||||
provisionSrc = super.fetchFromGitHub {
|
||||
owner = "oddlama";
|
||||
repo = "kanidm-provision";
|
||||
rev = "aa7a1c8ec04622745b385bd3b0462e1878f56b51";
|
||||
hash = "sha256-NRolS3l2kARjkhWP7FYUG//KCEiueh48ZrADdCDb9Zg=";
|
||||
};
|
||||
in {
|
||||
patches =
|
||||
old.patches
|
||||
++ [
|
||||
"${provisionSrc}/patches/${old.version}-oauth2-basic-secret-modify.patch"
|
||||
"${provisionSrc}/patches/${old.version}-recover-account.patch"
|
||||
];
|
||||
passthru.enableSecretProvisioning = true;
|
||||
doCheck = false;
|
||||
});
|
||||
kanidm-provision = super.callPackage ./kanidm-provision.nix {};
|
||||
})
|
||||
]
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
{
|
||||
lib,
|
||||
rustPlatform,
|
||||
fetchFromGitHub,
|
||||
}:
|
||||
rustPlatform.buildRustPackage rec {
|
||||
pname = "kanidm-provision";
|
||||
version = "1.0.0";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "oddlama";
|
||||
repo = "kanidm-provision";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-T6kiBUdOMHCWRUF/vepoPrvaULDQrUGYsd/3I11HCLY=";
|
||||
};
|
||||
|
||||
cargoHash = "sha256-nHp3C6szJxOogH/kETIqcQQNhFqBCO0P66j7n3UHuwo=";
|
||||
|
||||
meta = with lib; {
|
||||
description = "A small utility to help with kanidm provisioning";
|
||||
homepage = "https://github.com/oddlama/kanidm-provision";
|
||||
license = with licenses; [asl20 mit];
|
||||
maintainers = with maintainers; [oddlama];
|
||||
mainProgram = "kanidm-provision";
|
||||
};
|
||||
}
|
Binary file not shown.
Binary file not shown.
|
@ -23,11 +23,6 @@
|
|||
remotePath = "family-data";
|
||||
automatic = true;
|
||||
}
|
||||
{
|
||||
inherit address credentials;
|
||||
remotePath = "printer";
|
||||
automatic = true;
|
||||
}
|
||||
{
|
||||
inherit address credentials;
|
||||
remotePath = "media";
|
||||
|
|
|
@ -59,12 +59,11 @@
|
|||
"1" = {
|
||||
"0" = {
|
||||
icon = config.images.images."back.png";
|
||||
switch_page = 1;
|
||||
switch_page = 0;
|
||||
background_color = config.lib.stylix.colors.withHashtag.base0C;
|
||||
};
|
||||
};
|
||||
};
|
||||
page = 0; # The startup page
|
||||
brightness = 99; # brighness value between 0 and 99
|
||||
display_timeout = 0; # dimmer timeout in seconds
|
||||
brightness_dimmed = 99; # dimmed brighness
|
||||
|
|
Loading…
Reference in a new issue